Modules->模塊
Controller->控制器
Model->模型
Magento是這個星球上最強大的購物車網店平台。當然,你應該已經對此毫無疑問了。不過,你可能還不知道,Magento同樣是一個面向對象的PHP框架。你可以配合Magento購物車程序強大的功能,開發動態WEB應用程序。
這是Magento中文開發手冊的開篇,我們會在整個手冊中介紹絕大部分Magento的開發框架特性。不要想在這片文章中立刻掌握所有的特性。這僅僅是個開始,但是足夠讓你在同行中鶴立雞群了。
在這片文章中,你將了解到:
- Magento模塊(Magento Modules)代碼組織形式
- 配置型MVC架構
- Magento控制器(Magento Controllers)
- 基於URI的模型實例化(Context-based URI Model Loading)
- Magento模型(Magento Models)
- Magento助手(Magento Helpers)
- Magento布局(Magento Layouts)
- 事件監聽(Observers)
- Magento類重寫(Class Overrides)
- 總結
開始之前,你可以試着看下Magento MVC模式的一個圖形化直觀體現。Magento_MVC.pdf
Magento模塊中的代碼組織形式
Magento通過將代碼放入獨立的模塊進行組織。在一個典型的PHP MVC應用中,所有的控制器會被放在一個文件夾中,所有的模型會被放在另外一個文件夾里,等等。而在Magento中,文件是基於功能進行分組的,這種分組后的代碼塊叫做模塊。
Magento的代碼:
舉例來說,如果你想尋找Magento中關於付款的功能,你僅僅需要找到下面代碼中的文件夾,就能獲取所有的控制器,模型,助手,Blocks等。
app/code/core/Mage/Checkout
如果你想尋找Magento中關於Google Checkout的功能,也僅僅需要找到如下文件夾,即可獲取所有你想要的信息。
app/code/core/Mage/GoogleCheckout
你的代碼:
如果你想擴展Magento,千萬不要想當然的去修改core文件夾中的文件,也不要將你自己的控制器,模型,助手或者Blocks放在Core文件夾中。所有對於Magento的擴展,都將在local文件夾中進行。
app/code/local/<Package>/<Modulename>
Package(也可稱為命名空間,當然這不是PHP手冊中提到的命名空間)是唯一的命名,通過Package來標識你的公司,組織或個人。通過Package,世界范圍內的Magento社區在創建模塊擴展時,能夠使用他們自己的Package名稱,以避免與其他開發者有命名沖突。
創建一個新的模塊時,你需要告訴Magento新模塊的相關信息。可以通過添加一個XML文件在下面的目錄中。
app/etc/modules
在這個目錄中有兩類xml文件,第一種用來開啟獨立的模塊,以下列方式命名:
Packagename_Modulename.xml
第二種文件用來從一個Package中開啟多個模塊,以下列方式命名:
Packagename_All.xml
配置型MVC系統
Magento是一個配置型MVC(Configuration-based MVC)系統。另外一種MVC系統則是大部分PHP框架使用的,約定性MVC(convertion-based MVC)。
在約定型MVC系統中,如果你添加一個控制器,或者一個模型,只需要根據約定的內容,創建這個文件以及類即可,系統會自動識別它。
而在配置型MVC系統中,比如Magento,除了需要添加相應的文件及類之外,還需要明確的告訴系統該類的存在。在Magento中,每個模塊都有一個config.xml文件。這個文件中包含了一個模塊相關的配置信息。在運行時,所有模塊的配置文件,都會被加載到一個巨大的配置文件樹中(后面的文章會介紹如何查看這個配置樹)。
比如,想在模塊中使用模型。你需要添加類似下面的代碼,來告訴Magento你會在這個模塊中使用這個模型。
<
models
>
<
packagename
>
<
class
>Packagename_Modulename_Model</
class
>
</
packagename
>
<
models
>
當然,這種配置不僅限於模型,對於控制器,助手,Blocks,路由,事件句柄等都需要在該模塊的config.xml中進行相關的配置。
Magento控制器(Magento Controllers)
在任何PHP系統當中,核心文件肯定是PHP文件。Magento也不例外,index.php是Magento的核心文件。
不過,永遠不要編輯index.php中的任何代碼。在MVC系統中,index.php的左右大概有以下幾項:
- 檢測URL地址。
- 根據路由規則,將訪問的URL地址分發到控制器類中的方法。
- 初始化控制器,並調用相應的動作方法。這一步驟叫做分發。Dispatching。
這意味着Magento(或任何MVC系統)每一個有效的entry point都是控制器文件中的一個方法。一起來看下面這個URL:
http://example.com/catalog/category/view/id/25
上述域名后URL地址可以被分拆為以下幾個部分。
Front Name – catalog
該URL的第一部分被稱為Front Name。它用來指示Magento應該在哪個模塊中尋找URL中的控制器。在這個例子中,catalog就是Front Name,對應於catalog模塊。
Controller Name – Category
第二部分指示Magento應該匹配的控制器。每個擁有控制器的模塊都包含一個‘controllers’的文件夾,用來存放該模塊下的所有控制器。上述URL地址,匹配了下面這個控制器文件。
app/code/core/Mage/Catalog/controllers/CategoryController.php
其中的類定義格式大概為:
class
Mage_Catalog_CategoryController
extends
Mage_Core_Controller_Front_Action
{
}
在Magento中,所有的控制器都繼承自Mage_Core_controller_Front_Action類。
Action Name – view
第三部分是一個action方法的名稱。在此URL中,view便是一個action方法的名字。
class
Mage_Catalog_CategoryController
extends
Mage_Core_Controller_Front_Action {
public
function
viewAction() {
}
}
Paramater/Value – id/25
任何位於action方法名之后的路徑,都會被認為是key/value形式傳遞的GET變量。那么在我們的例子當中,’id/25′表示有一個值為25的$_GET['id']變量。
如前所述,如果你想讓自定義模塊使用控制器,你必須對它進行配置。下面是在模塊中開啟控制器的代碼。
<
frontend
>
<
routers
>
<
catalog
>
<
use
>standard</
use
>
<
args
>
<
module
>Mage_Catalog</
module
>
<
frontName
>catalog</
frontName
>
</
args
>
</
catalog
>
</
routers
>
</
frontend
>
現在不清楚上述內容都是什么意思還沒關系,但是注意<frontName>catalog</frontName>。這是用來關聯模塊與URL地址中frontname的。Magento核心代碼選擇將一個模塊的名字與frontname一致,但這不是強制規定的。
Multiple Routers
上面提到的路由規則主要是針對Magento購物車程序(即你所能看到的前端)。如果Magento在URL中無法匹配到正確的控制器/動作,它會嘗試使用針對Admin程序(后台管理端)的另一套路由規則。如果依舊無法正確匹配,它會使用一個特殊的控制器Mage_Cms_IndexController。
CMS控制器會檢查Magento內容管理系統中是否有內容需要輸出,如果有內容輸出,則讀取該內容,如果找不到,則輸出404頁面。
例如,Magento默認的首頁就是在使用CMS控制器。
Context-Based URI 模型讀取
目前為止,我們已經建立了一個控制器以及一個方法,到實例化一個類做點什么的時候了。Magento提供了一種特殊的方式去實例化模型,助手以及Blocks,即使用Mage全局類提供的靜態工廠方法。例如,
Mage::getModel(
'catalog/product'
);
Mage::helper(
'catalog/product'
);
‘catalog/product’字符串被稱為Grouped Class Name。通常叫做URI。Grouped Class Name的第一部分用來指示該類存在於哪個模塊當中。第二部分用來決定哪個類將被調用。
那么,上述例子中,‘catalog’對應於app/code/core/Mage/Catalog模塊,也就意味着我們的類名將以Mage_Catalog開頭,然后根據調用的類型,將product類名加入到最后一部分。即,
Mage::getModel(
'catalog/product'
);
Mage_Catalog_Model_Product;
Mage::helper(
'catalog/product'
);
Mage_Catalog_Helper_Product;
Magento 模型
和現在的多數框架一樣,Magento也提供ORM支持。ORM讓你能夠專注於數據,而非無盡的SQL語句。例如,
$model
= Mage::getModel(
'catalog/product'
)->load(27);
$price
=
$model
->getPrice();
$price
+= 5;
$model
->setPrice(
$price
)->setSku(
'SK1231414'
);
$model
->save();
在上面這個例子中,我們調用了“getPrice”和“setPrice”方法。然而,在Mage_Catalog_Model_Product類中並沒有此方法。那為什么上面這個例子能夠使用這些方法呢?因為Magento的ORM系統中使用了PHP的_get和_set魔術方法。
調用$product->getPrice()會獲取模型屬性price,而調用$product->setPrice()會設置price屬性。當然,所有的這些都假設模型類沒有getPrice和setPrice方法。如果它們存在於模型類中,PHP魔術方法會被忽略。如果你有興趣知道這是如何實現的,可以參考Varien_Object類,所有的模型類都繼承自該類。
如果你想獲取模型當中所有的數據,可以直接調用$product->getData()方法,它會返回包含所有字段的一個數組。
你可能已經注意到上例中的方法存在使用->符號鏈接的形式:
$model->setPrice($price)->setSku(‘SK12312542′);
能夠使用這種方式調用方法,最主要的原因是所有的set方法都會返回一個模型的實例。 你會經常在Magento的核心代碼中看到此類調用方法的形式。
Magento的ORM系統中還包含一種通過Collections接口查詢多個對象的方式。下例會讀取系統中所有5美元的產品。
$product_collection
= Mage::getModel(
'catalog/product'
)
->getCollection()
->addAttributeToSelect(
'*'
)
->addFieldToFilter(
'price'
,
'5.00'
);
這里我們又一次看到了鏈接調用方法的形式。Collections use the PHP Standard Library to implement Objects that have array like properties.(這句超出理解范圍)。
foreach
(
$products_collection
as
$product
)
{
echo
$product
->getName();
}
在上面的一個例子當中,你可能注意到了addAttributeToSelect方法。這里單獨提到此方法,是因為它代表了Magento模型中的一個類別。Magento擁有兩種形式的模型對象。一種是傳統的“一個對象,一張表”的Active Record模型。當你實例化這些模型的時候,所有的屬性都會被自動選取。
Magento中第二種模型叫做Entity Attribute Value(EAV)模型。這種模型會按照一定的規律將數據分散存儲在數據庫不同的表中。EAV模型的高級特性,讓Magento不用在增加一種產品屬性的時候改變數據庫模型(一般的購物車系統在增加新的屬性時,有兩種方式,一種是增加數據庫字段,一種是使用預留的空字段。),從而保證了Magento系統的高度擴展性。當創建一個EAV模型的collection時,Magento會conservative in它會查詢的字段數,所有你可以使用addAttributeToSelect來制定你想獲取的列,或者使用addAttributeToSelect(*)來獲取所有列。
Magento Helpers 助手
Magento的助手類包含一系列實用的方法,通過這些方法可以對對象及變量做日常性的操作。例如,
$helper
= Mage::helper(
'catalog'
);
是否注意到這里舍棄了Grouped Class Name的第二部分?每個模塊都有一個默認的data助手類。下面的語句與上面的作用是相同的,即默認使用模塊下的data助手類。
$helper
= Mage::helper(
'catalog/data'
);
大部分的助手類繼承自Mage_Core_Helper_Abstract,默認提供了很多使用的方法。
$translated_output
=
$helper
->__(
'Magento is Great'
);
if
(
$helper
->isModuleOutputEnabled()) {
}
Magento Layout 布局
目前為止,我們已經介紹了控制器,模型以及助手。在典型的PHP MVC系統當中,在操作模型之后,一般會
- 傳遞變量到視圖中。
- 系統會自動讀取默認的外層布局
- 接着將視圖讀取到外層布局中
不過,如果你仔細觀察Magento 控制器動作方法,你不會看到這些步驟,
public
function
galleryAction() {
if
(!
$this
->_initProduct()) {
if
(isset(
$_GET
[
'store'
]) && !
$this
->getResponse()->isRedirect()) {
$this
->redirect(
''
);
}
elseif
(!
$this
->getResponse()->isRedirect()) {
$this
->_forward(
'noRoute'
);
}
return
;
}
$this
->loadLayout();
$this
->renderLayout();
}
不同於典型PHP MVC形式的是,控制器動作方法,以兩個輸出布局的方法結束。所以說,Magento MVC系統中的V視圖部分可能與你經常使用的大相徑庭。因為,你必須在控制器中明確的輸出布局。
並且,Magento的布局本身也區別與你經常使用的MVC系統。Magento布局是一個包含嵌套或者樹狀的Block對象的對象。每一個Block對象輸出一部分HTML,輸出HTML的環節包含兩個部分,PHP代碼組成的Block以及.phtml模板文件。
Blocks對象負責與Magento系統交互並從模型中獲取數據,而phtml模板文件則為頁面生成必須的HTML代碼。
例如,頁面頭部Block文件app/code/core/Mage/Page/Block/Html/Head.php使用與其對應的page/html/head.phtml模板文件。
換種方式說的話,Blocks類就像迷你控制器,而.phtml文件就是視圖文件。
默認的,當你調用,
$this
->loadLayout();
$this
->renderLayout();
Magento will load up a Layout with a skeleton site structure(此段能夠理解,但想不到最佳翻譯,大概意思是Magento會讀取網站的布局框架)。這些結構Blocks用來輸出head,body以及設定單欄或多欄的布局。另外,還有一些內容Blocks負責實際輸出像導航,產品分類等。
“結構”和“內容”Blocks在布局系統中是隨意設置的。一般不會在代碼中刻意添加代碼,從而區分一個Block是結構還是內容,但是Blocks要么屬於“結構”,要么屬於“內容”。
為了添加一個內容Blocks到布局中,你需要告訴Magento系統
“Magento,快把這幾個Blocks添加到內容Block 里”
或者
“Magento,把這邊幾個Blocks放到“左邊欄”結構Block里”
這些可以通過控制器中的代碼進行控制,
public
function
indexAction() {
$block
=
$this
->getLayout()->createBlock(
'adminhtml/system_account_edit'
);
$this
->getLayout()->getBlock(
'content'
)->append(
$block
);
}
但是更常用的方式(至少在前台購物車應用中)是使用基於XML文件的布局系統。
在一款風格中,基於XML文件的布局 允許你刪除正常輸出的Blocks或者添加默認的skeleton區域(即Structure Blocks)。例如下面這個XML布局文件,
<
catalog_category_default
>
<
reference
name
=
"left"
>
<
block
type
=
"catalog/navigation"
name
=
"catalog.leftnav"
after
=
"currency"
template
=
"catalog/navigation/left.phtml"
/>
</
reference
>
</
catalog_category_default
>
上面這段代碼的作用是,在catalog模塊的category控制器的默認動作方法中,將catalog/navigation Block插入到左邊欄結構Block中,並使用catalog/navigation/left.phtml模板文件。
關於Blocks還有一個比較重要的特性。在模板文件中,你會看到很多類似下面的代碼,
1
$this
->getChildHtml(
'order_items'
)
這是Block輸出套嵌Block的方式。但是,只有在XML布局文件中明確聲明一個Block包含另一個子Block時,才能在模板文件中通過getChildHtml()方法調用子Block的模板文件。
例如,在XML布局文件中,
<
catalog_category_default
>
<
reference
name
=
"left"
>
<
block
type
=
"catalog/navigation"
name
=
"catalog.leftnav"
after
=
"currency"
template
=
"catalog/navigation/left.phtml"
>
<
block
type
=
"core/template"
name
=
"foobar"
template
=
"foo/baz/bar.phtml"
/>
</
block
>
</
reference
>
</
catalog_category_default
>
那么從catalog/navigation Block中,我們才可以調用$this->getChildHtml(‘foobar’);
Observers 觀察者
和許多優秀的面向對象系統一樣,Magento通過實現觀察者模式給用戶作為鈎子。對於在頁面請求時(模型存儲,用戶登錄等)調用的特定動作方法,Magento會生成一個事件信號。
當創建新的模塊時,你可以“監聽”這些事件。比如說,你想在特定用戶登錄商店的時候發一封郵件到管理員信箱里,可以通過“監聽”customer_login事件做到。
<
events
>
<
customer_login
>
<
observers
>
<
unique_name
>
<
type
>singleton</
type
>
<
class
>mymodule/observer</
class
>
<
method
>iSpyWithMyLittleEye</
method
>
</
unique_name
>
</
observers
>
</
customer_login
>
</
events
>
接下來是當用戶登錄時應該運行的代碼:
class
Packagename_Mymodule_Model_Observer
{
public
function
iSpyWithMyLittleEye(
$observer
)
{
$data
=
$observer
->getData();
//code to check observer data for out user,
//and take some action goes here
}
}
Class Overrides 類的復寫
最后,Magento系統還提供了類的復寫功能,你可以通過自己的代碼覆蓋核心代碼里的模型,助手,和Blocks類。
下面舉些例子幫助你更容易理解這個功能。產品的模型類是Mage_Catalog_Model_Product.無論何時,下面的代碼被調用時,就會生成一個Mage_Catalog_Model_Product對象。
1
$product
= Mage::getModel(
'catalog/product'
);
這是工廠模式。Magento的類復寫系統運作模式大概是這樣,
“Magento! 如果有請求對應catalog/product,不要實例化Mage_Catalog_Model_Product,讓Packagename_Modulename_Model_Foobazproduct接手”
在模型中,類的覆蓋方式及命名規則如下,
class
Packagename_Modulename_Model_Foobazproduct
extends
Mage_Catalog_Model_Product
{
}
通過這種方式,你可以改變父類中方法的行為,並且完全繼承父類的所有功能。
class
Packagename_Modulename_Model_Foobazproduct
extends
Mage_Catalog_Model_Product
{
public
function
validate() {
//添加一些自定義的驗證功能
return
$this
;
}
和之前所說的一樣,復寫同樣需要在config.xml配置文件中進行配置。
<
models
>
<!-- tells the system this module has models -->
<
modulename
>
<
class
>Packagename_Modulename_Model</
class
>
</
modulename
>
<!-- does the override for catalog/product-->
<
catalog
>
<
rewrite
>
<
product
>Packagename_Modulename_Model_Foobazproduct</
product
>
</
rewrite
>
</
catalog
>
</
models
>