Zend Framework分類文章 顯示方式:簡文 | 列表

2008年06月23日

[PHP-ZF] 解決 ZF 在 Proxy 模式下無法正確解析 Controller Name 的問題

說明

因為要和客戶的內部網路連線,便透過 PieTTY 的 SSH Tunnels 模式來做 Proxy 。 但是這個方式卻讓 Zend Framework 出現了以下的錯誤訊息:

An error occurred with this request: Invalid controller specified (http:).

解決方式是修改 Zend Framework 的 Zend_Controller_Request_Http 這個類別。

先開啟 Zend/Controller/Request/Http.php ,並找到:

$requestUri = $_SERVER['REQUEST_URI'];

改為:

$requestUri = preg_replace(
    '/^https?:\/\/' . $_SERVER['HTTP_HOST'] . '/i',
    '',
    $_SERVER['REQUEST_URI']
);

這樣就能避掉 Proxy 所帶來的問題了。

註 1 :這個方法的風險在於會修改原生的 ZF 程式碼,如果會用到 Subversion 來管理程式碼的話,要特別注意未來更新版本時可能會發生衝突的問題。

註 2 :未來版本應該會修正這個問題。

參考

ZF MVC is not compatible with apache + http protocol proxy requests


Posted by jaceju at 樂多Roodo!11:09回應(0)

2008年03月31日

[PHP-ZF] 如何學習 Zend Framework

今天又被問到怎麼開始學 Zend Framework ,我是覺得要上手並不難,但是基礎一定要有。

那麼哪些東西算是基礎呢?當然 HTML 、 JavaScript (Ajax) 或 CSS 等必備技能就不必多說了;以下我介紹幾個我個人認為滿重要的部份以及學習的次序:

物件導向

學習 Zend Framework 時,絕大部份時間你一定會接觸到物件導向,因此物件導向的基本觀念是一定要有的;否則你在運用 ZF 的各種套件時,一定會陷入困惑的泥沼中。

至於物件導向的三大要素:封裝繼承多型,詳細的說明在書本或網路有很多,我就不在這裡野人獻曝囉。這裡推薦:「深入淺出 Java 程式設計」,其中幾章的物件導向概念值得一讀。

設計模式

有了物件導向的觀念後,我強烈建議去瞭解設計模式,因為在 Zend Framework 的套件在設計上用到了許多設計模式。而且我個人也認為適當運用設計模式,能讓我們的程式更強固且具有彈性。

不過我很難用明確的字眼去表達設計模式是什麼東西,那遠遠超出我的能力範圍。因此我推薦這本:「深入淺出設計模式」,它簡單易懂的範例與說明,看完後保證你在物件導向上的功力能增進一甲子。

MVC 概念

MVC 觀念常常是傳統 PHP 開發者所不瞭解的,因為如果能快速把工作完成,誰會想去搞一個看起來那麼複雜的玩意兒?但是 MVC 在運用得當的情況下,卻能讓程式的意圖更加清晰,而且也更容易維護。

事實上 MVC 也是設計模式的一種,而上面那本書裡裡也有提到,建議大家參考看看。不過 Zend Framework 的 WebMVC 在實作上的概念上有稍有不同,我個人自薦這篇:「透視 WebMVC 」;該文會告訴你如何將舊有的 PHP 專案轉換成 MVC 架構。

PHP5

由於 Zend Framework 在開發上採用了 PHP5 的特性,所以 PHP5 也變成學習 Zend Framework 時最重要的基礎之一。而 PHP5 在物件導向也著墨甚多,在語法上也參考 Java 的許多特點,學過 Java 的朋友一定倍感親切。

參考資料方面我個人推薦官方手冊的 PHP5 的 Classes and Objects 一節,另外還有 Standard PHP Library (SPL) Functions

一定的開發經驗

Zend Framework 已經幫我們把很多東西都包裝好了,因此我們很多時候都可以不用重造輪子。但是它只是個工具,而且也不是萬能的,因此有些東西還是需要靠你自己來;所以對於 Web 開發你就必須要有一定程度的瞭解,至少你得曾經獨力完成一個以上的專案。

入門教學

假設以上你都有了一定的基礎,那麼你還需要臨門的一腳。一篇好的入門文章絕對是你學習 Zend Framework 必要的利器之一,因為它能快速帶你進入 Zend Framework 的世界裡。

這裡就不能不提 Rob Allen 寫的文章了,這篇「 Getting Started with Zend Framework 」是學習 Zend Framework 的你一定要載回來仔細研究的好文!不過官方也提供了一篇 Quick Start ,你可以將它和 Rob Allen 的文章交互參考看看 (我個人覺得 Rob 的比較完整) 。

參考手冊與源代碼

如果你已經入門了,那可別鬆懈。因為 Zend Framework 裡面有許多好用的工具,只是看你懂不懂得去運用而已;所以官方的參考手冊API 說明是一定要去看的。而源代碼更是重要,因為當官方手冊的範例或 API 說明明顯不足時,你可以從源代碼裡挖出一些不為人知的秘技,更可以學到許多高深的技巧!

當然以上的基礎不是絕對的,這僅僅是我個人的一些建議而已。希望這篇簡單的說明能幫助大家順利學習 Zend Framework ,也期盼大家能一起分享更多的學習心得。


Posted by jaceju at 樂多Roodo!16:40回應(10)

2008年03月17日

Zend Framework 1.5 正式推出

Zend Framework 1.5 正式推出了!

首先官方網站大改版!變得更漂亮了!而且也提供了官方版的快速入門,讓剛接觸的朋友也能很容易瞭解 ZF 1.5 的能耐。

主要更新特色如下:

  • Forms - 提供類似 PEAR 的 HTML_QuickForm 。

  • Layout - 提供類似 ASP.Net 的 Master Page 模式。

  • LDAP/OpenID authentication - 讓應用程式可以整合 LDAP 及 OpenID 的身份驗證。

  • Lucene search engine - 加強了搜尋語法。

  • Extended Ajax support - 支援動態的 Ajax 內容切換。

  • UTF-8 character sets in Adobe PDF creation - PDF 也能支援 UTF-8 字集。

  • Google GData web services - 加強了 GData 的操作。

  • Table Select - 能夠使用 Select 來操作 Table 了。

然而之前說 1.5 可能會將 Command Line Build 放進來,但是很可惜的是時程沒趕上。不過上面這些 ZF 1.5 的新特色,很值得有用 ZF 開發的朋友參考看看喔。

其他詳細消息請參考: Zend Technologies Releases Zend Framework 1.5

註:剛剛看到 Rob 更新了他的教學: Updated Tutorial for Zend Framework 1.5


Posted by jaceju at 樂多Roodo!22:44回應(1)

2008年02月29日

[PHP-ZF] 目錄結構的安排

Zend Framework 目錄結構的安排 (Directory Layout) 也是一個很讓初學者困擾的問題,而且不論是官方或是社群都推薦了很多方法,更讓剛接觸 Zend Framework 的朋友難以適從。

本文將探討 Zend Framework 目錄結構的特點以及幾個常見的目錄結構,讓各位開發者能夠對它們有所認知。

為什麼要有目錄結構

目錄結構主要目的如下:

  • 將程式及相關文件分門別類,以便於管理。

  • 便於設定相關存取權限。

  • 讓開發者很快能找到要修改的檔案。

因此選定一個好的目錄結構,將會為你的專案開發帶來高效率且易維護等好處。

專案目錄要放在哪裡

有些目錄結構需要有修改 Apache httpd.conf 的權限,有些則不需要;也因此專案目錄的存放位置也變得很重要,因為這會牽扯到安全性的問題。

一般來說,需要修改 httpd.conf 權限的目錄結構類型,我們可以放在檔案系統的任何地方,並確定它能讓 Apache 正確存取與執行。而不需要修改 httpd.conf 的類型,通常是為了因應專案的可攜性或是開發者缺少系統管理權;這時專案目錄就要放在 Apache 預設提供可以存取的位置,例如 /home/username/htdocs 。

常見的目錄結構

以下我們來看看幾個常見的目錄結構。

類型一:官方手冊的版本

官方手冊的「 7.1. Zend_Controller Quick Start 」一節裡提供了一個參考用的 Directory Layout ,我稍微做了一些修改,如下:

application/
    controllers/
        IndexController.php
        ErrorController.php
    models/
    views/
        scripts/
            index/
                index.phtml
            error/
                error.phtml
        helpers/
        filters/
library/
    Zend/
    (other libraries)/
html/
    scripts/
    images/
    styles/
    .htaccess (導向 index.php)
    index.php

說明

這是一個需要修改 httpd.conf 的目錄結構,也僅適用於單一功能的應用程式 (也就是單一模組) 。在安裝時我們必須修改 httpd.conf ,將專案的 Document Root 指向 html 這個目錄。 html 目錄除了做為 Document Root 外,其目地就是存放可公開的檔案,像是圖片、 JavaScript 或樣式表等等。而 application 目錄即為預設的模組,存放著 MVC 等三種類型的檔案 (即 controllers 、 models 及 views 等三個目錄) 。至於 library 目錄就比較隨意,我們只要確定它的執行權限,並且讓 PHP 能夠找到裡面所需要的類別即可。

在預設模組中, controllers 目錄裡的 IndexController.php 與 ErrorController.php 都是一定要的,另外 views/scripts/index/index.phtml 和 views/scripts/error/error.phtml 也是如此;而原因可以參考 Front Controller 的基本應用技一文,這裡不再詳述。

這裡要特別說明 application/views 這個目錄,其中還區分成 scripts 、 helpers 及 filters 三個目錄。 scripts 主要放的就是 templates ,底下可以用小寫的 controller name 做為資料夾名稱以做為區別;而 helpers 和 filters 其實在實際應用上比較少用,所以通常我會把它們從結構中拿掉。

註:為了節省篇幅,在呈現目錄結構時,預設模組的相關必要檔案我會略掉,但實際應用時請記得加回去。

另外 .htaccess (導向 index.php) 的實際內容如下:

RewriteEngine on
RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php
php_flag magic_quotes_gpc off
php_flag register_globals off 

在 .htaccess 的第一行中,由於 Zend Framework 支援自訂網址的功能,所以我們啟用了 RewriteEngine ,也就是設定為 on 。接著第二行我們加入一個 RewriteRule ,讓 index.php 處理所有非公開資源的 request 。

註:不過 Zend Framework 也支援沒有 mod_rewrite 的環境,我們只要在 Front Controller 中將 baseUrl 設定好就沒問題了,這點有機會再探討。

剩下兩行是 PHP 的設定,這邊就不再加以說明。

這種目錄結構最大的特色是將 index.php 放在 html 目錄下, 這樣的好處在於能提高對 application 目錄的安全掌控度,因為 application 目錄並不會直接曝露給外部的瀏覽者。但是這樣的結構也有一個很大的缺點,那就是可攜性很差!換句話說如果在測試環境所使用的網址是 http://localhost/myproject/ ,而正式上線的環境卻是 http://www.myproject.com/ ,那麼就要修改 httpd.conf 讓 html 目錄對應到正確的網址,這樣才有辦法讓程式正確執行。

類型二: Rob Allen 的版本

在 Rob 的 Getting Started with the Zend Framework 一文中,他採用了和官方不同的目錄結構:

application/
    .htaccess (deny from all)
    controllers/
    models/
    views/
        filters/
        helpers/
        scripts/
library/
    Zend/
    (other libraries)/
    .htaccess (deny from all)
public/
    .htaccess (RewriteEngine off)
    images/
    scripts/
    styles/
.htaccess (導向 index.php)
index.php

說明

這種目錄結構也是屬於單一功能 (模組) 的類型,它的特色是不必在 httpd.conf 中指定 Document Root 。這種目錄結構將 application 、 library 、 public (即第一種版本的 html) 以及 index.php 放在同一層的專案根目錄下,再利用 .htaccess 去控制每個目錄結構的存取權。

.htaccess (導向 index.php) 內容和官方版相同,請參考上面的說明。比較特別的是 application/.htacess 的內容為 deny from all ,這是要避免用戶直接從網址瀏覽 application 內的檔案;而 public/.htaccess 的內容則是 RewriteEngine off ,亦即關閉原本 .htaccess (導向 index.php) 對此目錄的影響,讓用戶可以直接瀏覽 public 內的檔案 (就是圖片、 JavaScript 及樣式表等等) 。

index.php 則是從 public 目錄中往外移至專案根目錄,這樣就可以讓用戶直接存取 (因為一般我們會在 Apache 設定 index.php 為預設執行頁) 。

這種目錄結構剛好和官方版本相反,它非常俱有可攜性,也是一般開發 Zend Framework 專案常用的目錄結構型態。不過它也有很大的缺點,那就是當 mod_rewrite 被關閉或是 .htaccess 不被解析時,這種目錄會將 application 及 library 兩個目錄曝露出來,在安全性上就大打折扣;不過通常這樣的 httpd.conf 設定比較少見,也因此一般開發 Zend Framework 專案時,大多都會採用這種目錄結構。

類型三: Gavin Vess 的 Conventional Modular 版本

前面兩種版本都是單一功能 (模組) ,不過如果專案需要前後台或多種功能時,通常就會使用模組機制。Zend Framework 支援了模組機制,因此在目錄結構上也需要做一些改變。

有一篇 Gavin Vess 所寫的 Choosing Your Application's Directory Layout 也討論到了有關 Zend Framework 在模組上的目錄結構安排。文章中有個 Conventional Modular 版本,我也做稍做了修改,如下:

application/
    config/ (非必要)
    default/
        config/ (非必要)
        controllers/
        models/
        views/
    (module 1)/
        controllers/
        models/
        views/
    (module 2)/
        controllers/
        models/
        views/
htdocs/
    images/
    scripts/
    styles/
    .htaccess (導向 index.php)
    index.php
library/
    Zend/
    (other libraries)/
tmp/
    sessions/
    cache/
    view_compiles/

說明

很明顯地,這個版本是從官方版本演化而來的;它的主要特色是將原來 application 中的 MVC 三個目錄拿掉了,取而代之的是 default 、 module1 、 module2 等 MVC 模組。

前面兩個版本都是以 application 為預設模組,因此我們不必在程式裡特別設定。但是在多模組的狀況下,我們需要指定 application 下的某個模組為預設模組;這部份的程式碼可以參考我在 「Front Controller 的基本應用技」一文中所提到的「進階的 Front Controller 用法」。

其他的目錄也都能望名生義,這裡不再解說。

類型四:我的版本

最後介紹我自己所採用的版本。基本上它可以算是模組化的 Rob 版本,只是我為了專案開發需求,又再加入一些特別的目錄結構。

app/
    .htaccess (deny from all)
    base/
    cron/
    etc/
    lib/
    tmp/
    mod/
        default/
            caches/
            controllers/
            models/
            views/
        (module 1)/
            caches/
            controllers/
            models/
            views/
        (module 2)/
            caches/
            controllers/
            models/
            views/
pub/
    .htaccess (RewriteEngine off)
    img/
    js/
    css/
.htaccess (導向 index.php)
index.php

說明

首先我將 application 及 public 的名稱分別縮短為 app 及 pub ,其它目錄名稱也改為較短的名稱。而所有的 .htaccess 則和 Rob 版的相同,因此也會有同樣的限制。

另外我把放置 Zend Framwork 及其他 library 所在的 lib 目錄移入 app 目錄中以方便管理, Gavin Vess 的 config 我也改成了 etc ,這點算是仿照 Gavin Vess 的 Classical (Unix/Linux-like) 版本。

其他幾個特點如下:

  1. 加入了 base 目錄,這是用來存放專案共用程式的地方。

  2. 加入了 cron 目錄,這是用來存放排程程式的地方。

  3. 加入了 tmp 目錄,視需要使用。

  4. 每個模組裡加入了 caches 資料夾,用來存放各模組所產生的快取資料。

註:以上的特色是在累積了多個 Zend Framework 專案經驗後所得出的結論,但或許各位所開發的專案屬性會和我的不同,因此這些特色僅供參考。

結論

Zend Framework 目錄結構的安排其實有很大的學問,在規劃得當的情況下,它能為我們帶來事半功倍的開發效率。本文簡單地為大家分析了幾種常見的目錄結構,也說明了它們的一些優缺點。各位朋友不妨多參考其他版本的目錄結構,並在瞭解專案的屬性後,選擇一個適合專案的目錄結構。

希望這篇文章能為各位在學習 Zend Framework 時帶來一些幫助,同時也希望對 Framework 有深入研究的朋友也能不吝指正我的謬誤。


Posted by jaceju at 樂多Roodo!0:30回應(3)

2008年02月27日

[PHP-ZF] Front Controller 的基本應用技

剛學 Zend Framework 的朋友通常會遇到兩個麻煩:目錄結構的安排,還有就是怎麼在 index.php (bootstrap) 中使用 Front Controller ?

目錄結構的安排下次有機會再談,這裡我來說說 Front Controller 的用法。

註:在此之前,請大家先看過 Rob Allen 寫的 Getting Started with the Zend Framework 一文 (PDF 版本) ;本文將會以該篇教學為基礎,一步一步來說明。

什麼是 Front Controller

整體而言 Front Controller 比較像是一個中介的角色,以下是它常見的幾個功能:

  1. 處理應用程式的初始化設定並執行之。

  2. 管理應用程式執行路徑。

  3. 管理輸入 (Request) ,使得使用者的動作得以分派 ( Dispatch) 到 Action Controller 中。

  4. 管理輸出 (Response) ,使得應用程式能將結果傳送出來。

  5. 可以接受 Plugins 來擴充 Front Controller 的行為。

Front Controller 裡還有更深入的參數設定機制,不過本文暫且先略過不談。

基本的 Front Controller 用法

我們先來看看 Rob 在 Getting Started with the Zend Framework 裡是怎麼用 Front Controller 的:

<?php
error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Europe/London');
set_include_path('.' . PATH_SEPARATOR . './library'
. PATH_SEPARATOR . './application/models/'
. PATH_SEPARATOR . get_include_path());

include "Zend/Loader.php";
Zend_Loader::loadClass('Zend_Controller_Front');

// setup controller
$frontController = Zend_Controller_Front::getInstance();
$frontController->throwExceptions(true);
$frontController->setControllerDirectory('./application/controllers');

// run!
$frontController->dispatch();

撇開前面 PHP 的設定部份,我們可以看到 Front Controller 主要分成設定與執行兩個部份。

在設定的部份裡,首先我們可以看到 Front Controller 為 Singleton ,換句話說 Front Controller 在整個應用程式執行的過程中只會有一個實體存在。接著我們再呼叫 Front Controller 的 throwExceptions() 方法,它能為我們設定是否要將 Exception 直接丟出或是放到 Response 物件中。最後我們再設定應用程式的 Action Controller 所在路徑,讓 Front Controller 知道到哪裡能找到要執行的 action 。

當設定好 Front Controller 後,就可以呼叫它的 dispatch() 方法來執行整個應用程式了。

以上就是最簡單的 Front Controller 使用方法,接下來我會介紹目前我所使用的方法。

進階的 Front Controller 用法

先來看看程式碼:

<?php
error_reporting(E_ALL|E_STRICT);
date_default_timezone_set('Asia/Taipei');

// set include_path
define('ROOT_PATH', str_replace('\\', '/', dirname(__FILE__)));
$includePath = array(
    ROOT_PATH . '/lib',
    get_include_path(),
);
set_include_path(join(PATH_SEPARATOR, $includePath));

// autoload
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();

// setup controller
$frontController = Zend_Controller_Front::getInstance();
$frontController->setControllerDirectory(array(
                      'default' => './app/mod/default/controllers',
                      'admin'   => './app/mod/admin/controllers'))
                ->throwExceptions(false)
                ->setDefaultModule('default')
                ->returnResponse(true);

// run!
$response = $frontController->dispatch();
if ($response->isException()) {
    $response->renderExceptions(true);
}
$response->sendResponse();

基本上我在 PHP 一般設定的部份和 Rob 的差不多 (因為我也是從他的教學入門的) ,只是我在載入類別的地方做了一些修改而已 (例如自動載入) 。

設定部份

接下來我們來看 Front Controller 設定的部份:

// setup controller
$frontController = Zend_Controller_Front::getInstance();
$frontController->setControllerDirectory(array(
                      'default' => './app/mod/default/controllers',
                      'admin'   => './app/mod/admin/controllers'))
                ->setDefaultModule('default')
                ->throwExceptions(false)
                ->returnResponse(true);

在 Rob 的教學中因為只需要單一的功能 (也就是專輯管理) ,因此他並沒有使用 Front Controller 的模組設定。而在我所開發的專案中通常會有前後台之分,因此我就應用了 Front Controller 裡的模組功能。使用 Front Controller 模組功能的方法很簡單,就是在 setControllerDirectory() 方法中,給它一個陣列參數;這個陣列參數的 key 即為模組名稱, value 則為模組中 Action Controller 的路徑。

然後我們要設定預設執行的模組,這裡使用的是 setDefaultModule() 方法。預設模組的好處是我們在網址上不必特別指定模組名稱,在上面的例子中即為 default 。如果不是預設的模組,那麼就要在網址中明確指定模組名稱;以 admin 這個模組來說,網址就要輸入: http://localhost/admin 。特別要注意的是網址上的模組名稱參考的是前段說明中的 key ,而非 value 裡的目錄名稱。

接下來的 throwExceptions() 方法是個 setter 和 getter 的合體,當我們給它參數值時 (true 或 false) 它就是 setter ,反之不提供任何參數時就會是 getter 。前面提到 throwExceptions() 在設定 true 值時, Front Controller 會直接丟出 Exception ,這時程式就會直接中斷 (或是用 try...catch 來接也行) ;而 throwExceptions() 設定 false 值時, Front Controller 則會將 Exception 放到 Response 物件裡,在稍後處理 Reponse 物件時才會看到 Exception 的內容。

註: throwExceptions() 方法在 setter 時會回傳 Front Controller 物件本身,而成為 getter 時則會回傳 boolean 值;這邊要特別小心,我也曾經在這裡中招。

最後的 returnResponse() 方法也是個 setter 與 getter 的合體,它主要是設定 Front Controller 的 dispatch() 方法要不要回傳一個 Response 物件。這裡我的做法是永遠回傳 Response 物件 (也就是設定 true 值) ,這樣後面執行的程式碼才不會發生錯誤。

執行部份

// run!
$response = $frontController->dispatch();
if ($response->isException()) {
    $response->renderExceptions(true);
}
$response->sendResponse();

在執行的部份,由於我們前面已經用 returnResponse() 方法來設定要回傳 Response 物件,因此 Front Controller 在完成 dispatch() 方法後就會將輸出的結果包裝在 Response 物件中回傳給 index.php 。

接著我們從 Response 物件的 isException() 方法來判斷輸出的結果是不是一個 Exception ,是的話就透過 Response 物件的 renderExceptions() 方法設定是否要顯示 Exception 。如果在這裡 renderExceptions() 方法的參數被設定為 false 時,那麼 Front Controller 就會呼叫預設模組底下 ErrorController 中的 errorAction() 方法,這樣就不會讓前端的瀏覽者看到程式錯誤,而是我們自訂的錯誤頁。

註:這個功能在上線環境中特別重要,你可以在這裡用 Response 物件的 getException() 方法取得 Exception 的內容然後寫入 Log 中,而最後只顯示自訂的 Error 頁面。

最後我們再呼叫 Response 物件的 sendResponse() 方法來將輸出的結果傳送給瀏覽器或 Console ,不論它是不是個 Exception 。

結論

以往我們在寫程式時,最頭痛的就是一開始的流程要怎麼寫?程式出現錯誤時要如何呈現?上線環境和開發環境要如何區別?而 Front Controller 正好為我們解決了許多這樣的麻煩;不過相對的它的使用方式也容易讓一般開發者感到迷惑,很多剛接觸的朋友就會在這裡受到很多挫折 (我剛好也是其中之一) 。

其實 Zend Framework 的 Front Controller 是非常靈活且強大的,這裡僅僅只介紹了它的基本應用方式。後面如果有空的話,我會再介紹其他相關的功能。

範例下載

照慣例,範例的下載位址在這裡


Posted by jaceju at 樂多Roodo!13:26回應(4)

2008年01月15日

[PHP-ZF] 初探 Zend_Db_Table Relationships (三)

前面我已經知道如何設定 Zend_Db_Table 的關連,這次來記一下串聯刪除 (cascading delete) 。

cascading delete 主要的目的是這樣的:假設 a 資料表和 b 資料表間有關連 (a.id = b.a_id) ,而當 a 資料表的某紀錄被刪除後,會使得 b 資料表中的相關紀錄失去作用而需要一併刪除。多數主流的資料庫系統都會內建類似的功能,例如 Oracle 、 MSSQL 及 PostgreSQL 等。但像是 MySQL 4.x 以前 的 MyISAM 格式並沒有提供 foreign key 和 trigger ,所以也沒辦法內建 Cascading Delete 時,這時只好從程式裡來著手。

不過有一點要小心,就算資料庫系統有支援 cascading delete ,也必須在資料表定義 foreign key 時設定 cascading delete 的相關指令 (通常是 ON DELETE CASCADE) ;不然資料表還是不會知道要去刪掉對應的紀錄。

註:以上資訊有錯誤的話,煩請大家不吝指正。

延續上一次的程式碼,這次我要為它們加入 cascading delete 的功能。

範例下載

不提供了,請下載上次的範例照以下說明修改吧。

說明

刪除文章時同時刪除文章與標籤之間的關連

首先我們在 Articles 這個類別中,加入一個 $_dependentTables 屬性:

<?php

class Articles extends Zend_Db_Table_Abstract
{
    // ... 略 ...

    protected $_dependentTables = array('ArticlesTags');
}

這個 $_dependentTables 必須給它一個陣列,這個陣列要包含你要串聯刪除的資料表類別名稱 (此例即為 ArticlesTags 這個類別) 。

接下來打開 ArticlesTags 這個類別,在原本的 $_referenceMap['Article'] 關連中,再加入一個 onDelete 索引:

<?php

class ArticlesTags extends Zend_Db_Table_Abstract
{
    protected $_referenceMap    = array(

        // ... 略 ...

        'Article' => array(
            'columns'           => array('article_id'),
            'refTableClass'     => 'Articles',
            'refColumns'        => 'id',
            'onDelete' => self::CASCADE,
        ),
    );
}

onDelete 的值固定就是用 self::CASCADE (其他值就不會動了) 。

註:別忘記原來 $_referenceMap['Article'] 所指定的意思,它是指 articles_tags 這個資料表的 articles_id (columns) 這個欄位要對應到 Articles (refTableClass) 這個資料表類別的 id 欄位 (refColumns) 。

這樣設定好後,就完成串聯刪除的動作了,很簡單吧?不過有一點非常重要:那就是一定要用 Zend_Db_Table_Row 的 delete 方法來刪除紀錄,才能啟動串聯刪除!

舉個例子:假設現在有兩篇文章,標題分別是 jQuery 和 Prototype ;現在我為它們都下了 javascript 和 ajax 兩個標籤。現在資料表紀錄如下:

articles 資料表
id category_id title
3 2 jQuery
4 2 Prototype
tags 資料表
id name
3 javascript
4 ajax
articles_tags 資料表
article_id category_id
3 3
3 4
4 3
4 4

註:請依照上面的例子,先在 articles 、 tags 及 artices_tags 三個資料表分別加入相關的紀錄。

接下來在 IndexController 中,加入一個 deleteAction 方法:

<?php

class IndexController extends Zend_Controller_Action 
{
    // ... 略 ...

    function deleteAction()
    {
        // 不顯示畫面
        $this->getHelper('ViewRenderer')->setNoRender();
        $articleTable = new Articles();
        // 有找到才刪
        if ($articleRow = $articleTable->find(3)->current()) {
            $articleRow->delete();
        }
    }
}

現在瀏覽一下 http://localhost/example3/index/delete (實際位置請按照各位的環境自行輸入) ,畫面應該不會出現任何資訊。現在到資料庫觀察一下,應該會發現 articles 資料表的第 3 筆紀錄已經被刪除;再看看 articles_tags ,在 article_id 為 3 的紀錄也跟著一併被刪除了。這樣就完成串聯刪除啦!

再整理一次重點:

  1. 在要刪除紀錄的資料表類別裡定義 $_dependentTables 。

  2. 在要串聯刪除紀錄的資料表類別裡定義 $_referenceMap[關連的名稱] 的 onDelete 屬性。

  3. 要使用串聯刪除,一定要用 Zend_Db_Row 的 delete 方法。


Posted by jaceju at 樂多Roodo!13:06回應(3)

2007年12月1日

[PHP-ZF] Zend Framework 1.0.3 版釋出

前陣子 Update SVN 時就發現了,昨天則正式公告。

這次釋出的 1.0.3 並沒有太大的功能更動,而是修正了一些在 1.0.2 發現的問題。詳細的更新情形請參考 ChangeLog


Posted by jaceju at 樂多Roodo!10:31

2007年09月26日

[PHP-ZF] Zend Framework 1.0.2 版釋出

這次 Zend Framework 1.0.2 主要是修正許多 1.0.1 所發現的 Bug ,詳細內容可以參考 Chagelog

Zend_Gdata 修得最多,看來外國人還滿喜歡把 Google 的服務整合到程式裡面。


Posted by jaceju at 樂多Roodo!16:08回應(0)

2007年09月21日

[PHP-ZF] 初探 Zend_Db_Table Relationships (二)

前一篇文章提到了簡單的 Zend_Db_Table 的一對多關連,這次來談一對一 (多對一) 和多對多。

不過我對一對一的定義比較模糊,詳細的資訊請參考良葛格的 Hibernate 學習筆記

資料表範例

基本上還是前一篇文章的範例,這次加上標籤 (Tag) ;每篇文章 (Article) 可以標上多個標籤,而每個標籤則是可以對應到多篇文章。新增的資料表結構如下:

CREATE TABLE IF NOT EXISTS `tags` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `name` varchar(200) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

INSERT INTO `tags` (`id`, `name`) VALUES
(1, 'php'),
(2, 'zend framework');

CREATE TABLE IF NOT EXISTS `articles_tags` (
  `article_id` int(10) unsigned NOT NULL default '0',
  `tag_id` int(10) unsigned NOT NULL default '0',
  PRIMARY KEY  (`article_id`,`tag_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES
(1, 1),
(1, 2),
(2, 1),
(2, 2);

其中我們多了一個中間表 articles_tags ,然後在邏輯上 articles.id 會關連到 articles_tags.article_id ,而 tags.id 則對應到 articles_tags.tag_id 。在 articles_tags 中, article_id 和 tag_id 為組合主鍵。

範例下載

下載位置

註:一樣沒包 Zend Framework ,請自行下載安裝。

說明

一對一 (多對一)

首先我們先來看看如何如何從文章中抓取對應的分類,從一篇文章上只會找到一個分類,也就是一對一關連。

註:其實嚴格來說文章對分類不是一對一的關係 (應該是多對一) ,只是在 Zend_Db_Table 裡,它們的作法是相似的。

然而一對多和多對一只是兩個相關的對應關係,因此我們並不用修改原來的 Articles 和 Categories 兩個 Classes 。在 Zend_Db_Table_Row 中,提供了一個 findParentRow 方法,讓我們能從 Articles 反推回去找到 Categories ;也就是在 IndexController::index 中,我們只要這樣做:

$phpArticle = $articles->find(2)->current();
$this->view->category = $phpArticle->findParentRow('Categories');

這裡會抓取 articles.id 等於 2 的文章,然後透過 findParentRow 來向 Categories 取得對應的分類。另外 Zend_Db_Table_Row 也提供了一個魔術方法,只要用 findParent<ParentClass> 就可以達到同樣的效果,例如:

$this->view->category = $phpArticle->findParentCategories();

然後我們就可以在 index.phtml 顯示相關的分類了:

<?php
var_dump($this->category->toArray());
?>

多對多

文章和標籤是以多對多的關係存在的,所以我們要先加入兩個 Classes ,一個是 Tags ,另一個是 ArticlesTags ;它們分別對應 tags 和 articles_tags 兩個資料表:

class Tags extends Zend_Db_Table_Abstract
{
    protected $_name = 'tags';
}

class ArticlesTags extends Zend_Db_Table_Abstract
{
    protected $_name = 'articles_tags';

    protected $_referenceMap    = array(
        'Tag' => array(
            'columns'           => array('tag_id'),
            'refTableClass'     => 'Tags',
            'refColumns'        => 'id',
        ),
        'Article' => array(
            'columns'           => array('article_id'),
            'refTableClass'     => 'Articles',
            'refColumns'        => 'id',
        ),
    );
}

註:這裡暫時不考慮 $_dependentTables 屬性,後面有用到再加。

在 ArticlesTags 這個 Class 的 $_referenceMap 屬性裡,我定義了兩個 Rule ,一個為 Tag 、另一個為 Article ;它們分別關連到 Tags 和 Articles 這兩個 Classes 。

在 Zend_Db_Table_Row 裡提供了一個 findManyToManyRowset 的方法,可以協助我們處理多對多的關係。我們沿續第一個例子,假設現在我們想知道該篇文章所對應的標籤有哪些,可以這樣寫:

$this->view->articleTags = $phpArticle->findManyToManyRowset('Tags', 'ArticlesTags', 'Article');

在 findManyToManyRowset 方法裡一般會用到三個參數,第一個參數表示要關連的資料表 Class ,第二個參數則是中間表所對應的 Class ,而第三個為 Rule 的名稱。而要特別注意的是, Rule 所對應的 refTableClass 要和 findManyToManyRowset 方法所在的物件其對應的資料表一樣。可以對照一下上面 $phpArticle 物件所對應的資料表和最後一個參數值 Article 所對應的資料表,這樣就很容易理解了。

最後一樣在 index.phtml 裡顯示結果:

<?php
var_dump($this->articleTags->toArray());
?>

當然反過來要從標籤去取得文章也就很簡單了,只要抓出目前的標籤,然後再利用 findManyToManyRowset 方法來取得關連的文章就可以了 (提示: Rule 要用 Tag) 。


Posted by jaceju at 樂多Roodo!0:09回應(0)

2007年09月20日

[PHP-ZF] 初探 Zend_Db_Table Relationships (一)

早上四點半就醒了...睡不著,想說研究一下 Zend Framework DB 一對多的關連。先前一直搞不定其中的關係,沒想到簡單玩了一下,竟然就搞定了 (可見手冊我根本就沒看懂...Orz) 。

這裡記一下,以免之後又忘記了。

資料表範例

這裡我用文章 (Article) 及文章分類 (Category) 來做為例子,它們的 DB Schema 如下:

CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `category_id` int(10) unsigned NOT NULL,
  `title` varchar(200) NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `table1_id` (`category_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

INSERT INTO `articles` (`id`, `category_id`, `title`) VALUES
(1, 3, 'Zend_Db_Table'),
(2, 3, 'Zend_Controller_Action'),
(3, 2, 'jQuery'),
(4, 2, 'Prototype');

CREATE TABLE IF NOT EXISTS `categories` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `parent_id` int(10) unsigned NULL,
  `name` varchar(200) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

INSERT INTO `categories` (`id`, `parent_id`, `name`) VALUES
(1, NULL, 'PHP'),
(2, NULL, 'JavaScript'),
(3, 1, 'Zend Framework');

其中 articles.category_id 對應到 categories.id ,而 categories.parent_id 也對應到自己的 categories.id 。

程式碼架構

這裡主要我是拿 RobGetting Started with the Zend Framework 裡面範例來修改,詳細架構就先看看後面附的下載檔。

不過也不一定需要用這樣的架構才能跑 ZF ,這是因為我偷懶不想寫太多不相關的程式,所以直接拿別人的來改。事實上我們可以把 Zend_Db 獨立出來使用,只是這樣要寫的東西就會比較複雜一點點,跟我要研究的東西無關。

範例下載

下載位置

註:裡面沒包 Zend Framework ,請自行到官方網站下載,然後把 library 目錄解開放到範例資料夾下即可。

說明

首先我們需要兩個繼承 Zend_Db_Table_Abstract 的 Class ,它們分別是 Articles 和 Categories :

class Articles extends Zend_Db_Table_Abstract
{
    protected $_name = 'articles';
}

class Categories extends Zend_Db_Table_Abstract
{
    protected $_name = 'categories';
}

因為大部份工作已經被 Zend_Db_Table_Abstract 做掉了,所以 Articles 和 Categories 只要很簡單的指定 $_name 屬性即可;這樣一來, Articles 和 Categories 兩個 Classes 就能對應到 articles 和 categories 兩個資料表。

不過這樣還沒有辦法讓這兩個資料表在程式碼裡產生關連,我們必須在 Articles 和 Categories 這兩個 Classes 額外定義一些資訊,讓它們能互相認識。首先我們在 Articles Class 裡加上 $_referenceMap 屬性:

class Articles extends Zend_Db_Table_Abstract
{
    protected $_name = 'articles';

    protected $_referenceMap    = array(
        'Category' => array( // 給這個關連一個名字
            'columns'           => 'category_id', // 對應到 articles.category_id
            'refTableClass'     => 'Categories',  // 對應到 Categories Class
            'refColumns'        => 'id',          // 對應到 categories.id
        ),
    );
}

$_referenceMap 屬性是用來宣告要透過哪個欄位來和另一個資料表做關連,就上面的例子來看,我們可以很清楚的看到是 articles.category_id 關連到 categories.id 。然而 $_referenceMap 屬性可以同時定義多組關連,但為了簡化以便於理解,這裡我只用了一組。

在這裡的 $_referenceMap 屬性裡主要有三個項目:

  • columns :表示目前資料表關連到另一個資料表的欄位,一般就是 Foreign Key (FK) 。

  • refTableClass :關連資料表所對應的 Class ,這裡要注意大小寫。

  • refColumns :關連資料表所對應的欄位,一般就是 Primary Key (PK) 。

註: $_referenceMap 其實還有其他可設定的項目,這裡暫不介紹。

接著 Categories 這個 Class 也必須定義一個 $_dependentTables 屬性,宣告與 categories 有關連的資料表名稱;這是為了保證我們在透過 Categories Class 刪除資料時,能一併刪掉關連資料表裡的資料 (這裡我還在研究,尚無實例) 。

class Categories extends Zend_Db_Table_Abstract
{
    protected $_name = 'categories';

    protected $_dependentTables = array('Articles');  // 對應到關連的 Table Class
}

註:事實上 $_dependentTables 屬性在目前這個例子裡可以不加,因為我們還用不到關連更新或刪除的功能。

現在我們可以在 IndexController::indexAction 裡測試看看:

class IndexController extends Zend_Controller_Action 
{
    function indexAction()
    {
        $categories = new Categories();

        $zendframework = $categories->find(3)->current();
        $this->view->articles = $zendframework->findDependentRowset('Articles');
    }
}

其中 findDependentRowset 是 Zend_Db_Table_Abstract 提供的方法,它可以幫我們找出關連資料表的所有資料。我們在 index.pthml 裡可以將取得的結果表列出來:

<?php
var_dump($this->articles->toArray());
?>

是不是很簡單呢?

那麼第二個問題來了,如果我們要讓 categories._parent_id 和 categories.id 產生關連呢?

其實仿照上面的做法,我們只需要在 Categories Class 裡動手腳即可:

class Categories extends Zend_Db_Table_Abstract
{
    protected $_name = 'categories';

    protected $_referenceMap    = array(
        'ParentCategory' => array(
            'columns'           => 'parent_id',   // 對應到 categories.parent_id
            'refTableClass'     => 'Categories',  // 對應到 Categories Class
            'refColumns'        => 'id',          // 對應到 categories.id
        ),
    );

    protected $_dependentTables = array('Articles', 'Categories');   // 對應到關連的 Table Class
}

也就是說,我們在 Categories Class 加入一個 $_referenceMap 屬性關連到自己,然後在 $_dependentTables 加入自己。

接著在 IndexController::indexAction 中測試一下:

class IndexController extends Zend_Controller_Action 
{
    function indexAction()
    {
        $categories = new Categories();

        $php = $categories->find(1)->current();
        $this->view->subcategories = $php->findDependentRowset('Categories');

        $zendframework = $categories->find(3)->current();
        $this->view->articles = $zendframework->findDependentRowset('Articles');
    }
}

在 index.phtml 裡顯示結果:

<?php
var_dump($this->subcategories->toArray());
?>

以上就是簡單的 Zend_Db_Table_Actract 一對多關連取得資料集的方式,謝謝收看,下次見。 (補眠去...什麼?已經天亮要上班了喔?)


Posted by jaceju at 樂多Roodo!6:46回應(0)
 [1]  [2]  [3]  [最終頁]