什么是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)
你也可以图形来了解MVC架构 Magento_MVC.pdf.
在讲解之前,我们先来认识下名词:
- Magento模块(Magento Modules)
- Magento控制器(Magento Controllers)
- Magento模型(Magento Models)
- Magento助手(Magento Helpers)
- Magento布局(Magento Layouts)
- 事件监听(Observers)
Magento模块中的代码组织
Magento通过将代码放入独立的模块进行组织。在一个典型的PHP MVC应用中 Model-View-Controller (MVC) , 所有的控制器会被放在一个文件夹中,所有的模型会被放在另外一个文件夹里,等等。而在Magento中,文件是基于功能进行分组的,这种分组后的组合在一起的代码组织叫做模块。
magento的代码
举例来说,如果你想寻找Magento中关于付款的功能,你仅仅需要找到下面代码中的文件夹,就能获取所有的控制器,模型,助手,Blocks等。
app/code/core/Mage/Checkout
|
app
/
code
/
core
/
Mage
/
Checkout
|
在Magento的谷歌结帐功能中,你会发现控制器,模型,助手,块( Controllers, Models, Helpers, Blocks,)等文件夹
app/code/core/Mage/GoogleCheckout
|
app
/
code
/
core
/
Mage
/
GoogleCheckout
|
你的代码
如果你想自定义或扩展Magento,不要直接编辑的核心文件(core文件),你要把你自己的控制器,模型,助手,块等Magento的代码放到local文件夹,创建自己的模块
app/code/local/Package/Modulename
|
app
/
code
/
local
/
Package
/
Modulename
|
Package(通常也被称为命名空间)是用于识别您的公司或组织的唯一名称。这样做的目的是为了避免与其他用户的代码碰撞创建模块时,在全球范围内的Magento社区的每个成员会用自己的包名。
当你创建一个新的模块,你需要告诉Magento的吧。这是通过添加一个XML文件到文件夹:
此文件夹中有两种类型的文件,第一个启用单个模块,并命名为形式:Packagename_Modulename.xml
第二个是启用多个模块从包/命名空间的文件,并命名为形式:Packagename_All.xml。值得注意的是仅被core team用这个文件Mage_All.xml。我们不建议激活几个模块在一个文件中,因为这打破了你的模块的模块化。
基于配置的MVC
Magento是一个配置型(基于配置的)MVC(Configuration-based MVC)系统。另外一种MVC系统则是大部分PHP框架使用的,约定性(基于公约的)MVC(convertion-based MVC)。
在约定性MVC系统里,如果你想添加,比如说,一个新的控制器或者一个新的模型,你只需要创建file/class,系统会自动识别。
在配置型的系统,如Magento的,除了加入新的文件/类的代码库,你经常需要明确地告诉系统这个新类或类新组。在Magento的,每个模块都有一个名为config.xml文件。该文件包含了Magento的所有模块的相关配置。在运行时,所有这些文件都装入一个大的配置树。
例如,要使用模型中的自定义模块?您需要添加一些代码config.xml中告诉Magento的要使用模型,以及什么对你所有的型号的基类名应该是。
例如,要使用模型中的自定义模块?您需要添加一些代码到config.xml中告诉Magento要使用的模型,以及所有的型号的基类名应该是什么。
Packagename_Modulename_Model
|
<
models
>
<
packagename
>
<
class
>
Packagename_Modulename_Model
<
/
class
>
<
packagename
>
<
/
models
>
|
这同样适用于Helpers, Blocks, Routes for your Controllers, Event Handlers等等,几乎任何时候你想要扩展magento,你需要做出一些改变或添加到您的配置文件。
Magento控制器(Magento Controllers)
在任何PHP系统,主要的PHP切入点仍然是一个PHP文件。 Magento的没有什么不同,而该文件是index.php文件。
但是,你永远不要编辑index.php。在MVC体系,index.php文件将包含代码/调用,可以做以下行为动作:
1.检查URL网址
2.根据一些规则集,将这个URL分发到控制器类和动作方法(称为路由)
3.实例化控制器类并调用Action方法(称为调度)
这意味着在Magento的实际入口点(或任何基于MVC的系统)是一个控制器文件里面的方法。看下面的网址:
http://example.com/catalog/category/view/id/25
|
http
:
//example.com/catalog/category/view/id/25
|
服务器名称(域名)后的路径的各部分被分析如下。
Front Name – catalog
该URL的第一部分被称为Front Name。它用来指示Magento应该在哪个模块中寻找URL中的控制器。在这个例子中,catalog就是Front Name,对应于catalog模块。
app/code/core/Mage/Catalog
|
app
/
code
/
core
/
Mage
/
Catalog
|
Controller Name – category
URL的第二部分告诉Magento它应该使用哪个控制器文件。每个有控制器的模块都包含有一个名为“控制器”的特殊文件夹,这个文件包含了这个模块的所有控制器文件。在上面的例子中,URL部分类别被转换成控制器文件
app/code/core/Mage/Catalog/controllers/CategoryController.php
|
app
/
code
/
core
/
Mage
/
Catalog
/
controllers
/
CategoryController
.
php
|
其中控制器文件的定义格式为
class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action { }
|
class
Mage_Catalog_CategoryController
extends
Mage_Core_Controller_Front_Action
{
}
|
在Magento的购物车应用程序中,所有控制器都是Mage_Core_Controller_Front_Action的延伸(继承与该类)。
Action Name – view
URL的第三部分就是 action name. 在我们的例子中,它是 “view”. “view” 就是创建的Action的名字. 所以,在我们的例子中, “view” 就是 “viewAction”
class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action { public function viewAction() { //main entry point } }
|
class
Mage_Catalog_CategoryController
extends
Mage_Core_Controller_Front_Action
{
public
function
viewAction
(
)
{
//main entry point
}
}
|
人们熟悉的Zend框架将在这里认识这种命名约定。
Paramater/Value – id/25
任何位于action方法名之后的路径,都会被认为是key/value形式传递的GET变量。那么在我们的例子当中,’id/25′表示有一个值为25的$_GET[‘id’]变量。
正如前面提到的,如果你希望你的模块使用控制器,你需要配置它们。下面是在配置块中使用控制器的目录模块配置代码
Mage_Catalog catalog
|
<
frontend
>
<
routers
>
<
catalog
>
<
use
>
standard
<
/
use
>
<
args
>
<
module
>
Mage_Catalog
<
/
module
>
<
frontName
>
catalog
<
/
frontName
>
<
/
args
>
<
/
catalog
>
<
/
routers
>
<
/
frontend
>
|
不要太担心了具体的权利,但注意到catalog, 这是用来关联模块与URL地址中frontname的。Magento核心代码选择将一个模块的名字与frontname一致,但这不是强制规定的。
Multiple Routers
上述的路由是Magento的购物应用程序(通常称为前端)。如果Magento的没有找到一个有效的控制器/动作的URL,它再次尝试,为管理应用程序使用的是第二组路由这段时间的规则。如果Magento的没有找到一个有效的管理控制器/动作,它使用一个名为Mage_Cms_IndexController一种特殊的控制器。
CMS的控制器检查Magento的内容管理系统,看看是否有应加载任何内容。如果找到了一些,它加载它,否则用户将看到一个404页面。
例如,Magento的“index”页(首页)是一个使用CMS的控制,而这往往把新引入到一个死循环里。
Context-Based URI 模型加载
现在,我们将Action方作为切入点,我们将要开始实例化的类做点事情。Magento的提供了一种特殊的方式来实例化模型,利用Models, Helpers and Blocks,即使用Mage全局类提供的静态工厂方法. 例如
Mage::getModel('catalog/product'); Mage::helper('catalog/product');
|
Mage
::
getModel
(
'catalog/product'
)
;
Mage
::
helper
(
'catalog/product'
)
;
|
‘catalog/product’字符串被称为Grouped Class Name。通常叫做URI。Grouped Class Name的第一部分用来指示该类存在于哪个模块当中。第二部分用来决定哪个类将被调用。
所以,在这两个以上的实施例中,“catalog”解析为模块的app/code/core/Mage/Catalog。
这意味着我们的 class name将开始于Mage_Catalog。
然后根据调用的类型,将product类名加入到最后一部分。即
Mage::getModel('catalog/product'); Mage_Catalog_Model_Product Mage::helper('catalog/product'); Mage_Catalog_Helper_Product
|
Mage
::
getModel
(
'catalog/product'
)
;
Mage_Catalog_Model_Product
Mage
::
helper
(
'catalog/product'
)
;
Mage_Catalog_Helper_Product
|
这些规则是由每个模块的配置文件所约束的,当你在建立一个自定义的模块时,你将定义你自己的group class name,例如:
Mage::getModel('myspecialprefix/modelname');.
不是一定要用Groupd Class Name来实例化你的类名,但是,为了以后的学习,这样做有一定的优势
Magento模型(Magento Models)
像大多是框架一样,magento也提供了对应关系映射(ORM)系统,ORMs 让你之关注SQL的业务,并允许您通过PHP代码完全操纵的数据存储,而不是无尽的SQL语句。例如:
$model = Mage::getModel('catalog/product')->load(27); $price = $model->getPrice(); $price += 5; $model->setPrice($price)->setSku('SK83293432'); $model->save();
|
$
model
=
Mage
::
getModel
(
'catalog/product'
)
->
load
(
27
)
;
$
price
=
$
model
->
getPrice
(
)
;
$
price
+=
5
;
$
model
->
setPrice
(
$
price
)
->
setSku
(
'SK83293432'
)
;
$
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('SK83293432');
这是由于每个set方法都会返回一个模型的实例,你会在代码中看到很多都用这种模式
Magento的ORM还包含一种方法通过Collections接口查找多个对象,下面我们将获得所有费用5.00美元产品的集合
$products_collection = Mage::getModel('catalog/product') ->getCollection() ->addAttributeToSelect('*') ->addFieldToFilter('price','5.00');
|
$
products_collection
=
Mage
::
getModel
(
'catalog/product'
)
->
getCollection
(
)
->
addAttributeToSelect
(
'*'
)
->
addFieldToFilter
(
'price'
,
'5.00'
)
;
|
同样地,你会发现Magento实现了一个链接接口。集合使用PHP的标准库实现有阵类似属性的对象。集合(Collections)使用PHP的标准库来实现属性组的对象
foreach($products_collection as $product) { echo $product->getName(); }
|
foreach
(
$
products_collection
as
$
product
)
{
echo
$
product
->
getName
(
)
;
}
|
你可能想知道的“addAttributeToSelect”的方法是什么。 Magento的有两大类型的模型对象。一种是传统的“一个对象,一张表”的Active Record风格模型。当你实例化这些模型的时候,所有的属性将被自动选中。
第二类模型是一个实体属性值(EAV)模型。 EAV模型会按照一定的规律将数据分散存储在数据库不同的表中。这使Magento的系统的灵活性,提供了灵活的产品属性的系统,而无需每次添加一个属性时做架构更改。当创建EAV对象的集合,Magento查询列的次数是保守的,因此您可以使用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'); //gettext style translations if($helper->isModuleOutputEnabled()): //is output for this module on or off?
|
$
translated_output
=
$
helper
->
__
(
'Magento is Great'
)
;
//gettext style translations
if
(
$
helper
->
isModuleOutputEnabled
(
)
)
:
//is output for this module on or off?
|
Magento布局(Layouts)
到这儿,我们已经了解了Controllers, Models, and Helpers,在典型的PHP MVC系统当中,在操作模型之后,一般会
- 传递变量到视图中。
- 系统会自动读取默认的外层布局
- 接着将视图加载到外层布局中
但是,如果你只看一个典型的Magento控制器的动作,你看不到任何东西
/** * View product gallery action */ 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(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* View product gallery action
*/
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
(
)
;
}
|
相反,这个控制器由两个会话结束
$this->loadLayout(); $this->renderLayout();
|
$
this
->
loadLayout
(
)
;
$
this
->
renderLayout
(
)
;
|
所以,在Magento的MVC中的“V”型已经不同于可能已经与你习惯的V不一样了,在你需要明确地分发到布局。
Magento的布局本身也区别与你经常使用的MVC系统。Magento布局是一个包含嵌套或者树状的Block对象的对象。每一个Block对象输出一部分HTML,输出HTML的环节包含两个部分,PHP代码组成的Block以及.phtml模板文件。
Block对象是为了与Magento的系统来检索模型数据进行交互,而PHTML模板文件会产生所需的页面的HTML。
例如,页面头部Block文件app/code/core/Mage/Page/Block/Html/Head.php使用与其对应的page/html/head.phtml模板文件。
换种方式说的话,Blocks类就像迷你控制器,而.phtml文件就是视图文件。
默认情况下,当你调用
$this->loadLayout(); $this->renderLayout();
|
$
this
->
loadLayout
(
)
;
$
this
->
renderLayout
(
)
;
|
Magento将加载网站骨架结构布局。这些结构Blocks用来输出head,body以及设定单栏或多栏的布局。此外,还会有一些内容块的导航,默认的欢迎信息等。
“结构”和“内容”Blocks在布局系统中是随意设置的。一般不会在代码中刻意添加代码,从而区分一个Block是结构还是内容,但是Blocks要么属于“结构”,要么属于“内容”。
添加内容到这个布局,你需要告诉Magento系统如
"Hey, Magento, add these additional Blocks under the "content" Block of the skeleton"
|
"Hey, Magento, add these additional Blocks under the "
content
" Block of the skeleton"
|
或者
"Hey, Magento, add these additional Blocks under the "left column" Block of the skeleton"
|
"Hey, Magento, add these additional Blocks under the "
left
column
" Block of the skeleton"
|
这可以通过编程的控制器操作来完成
public function indexAction() { $this->loadLayout(); $block = $this->getLayout()->createBlock('adminhtml/system_account_edit') $this->getLayout()->getBlock('content')->append($block); $this->renderLayout(); }
|
public
function
indexAction
(
)
{
$
this
->
loadLayout
(
)
;
$
block
=
$
this
->
getLayout
(
)
->
createBlock
(
'adminhtml/system_account_edit'
)
$
this
->
getLayout
(
)
->
getBlock
(
'content'
)
->
append
(
$
block
)
;
$
this
->
renderLayout
(
)
;
}
|
但更常见的(至少在购物车前端应用程序),是使用XML布局体系。
在主题布局的XML文件里允许你在每个控制器的基础上,消除通常被控制器分发的块,或添加块到默认骨架的区域。例如,考虑这种布局的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和方法default中,插入catalog/navigation模块到“left”的结构模块,使用catalog/navigation/ left.phtml模板。
关于块的最后一个重要的事情。你会经常看到的代码模板,如下所示:
$this->getChildHtml('order_items')
这是Block输出套嵌Block的方式。但是,只有在XML布局文件中明确声明一个Block包含另一个子Block时,才能在模板文件中通过getChildHtml()方法调用子Block的模板文件。
例如,我们有像这样的
|
<
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');
Magento观察员(Observers)
像任何优秀的面向对象的系统,Magento通过实现观察者模式给用户作为钩子,作为一个页面请求(模型被保存,用户登录等)在某些动作发生,Magento的会发出一个事件信号。
在创建自己的模块的时候,可以“监听”这些事件。比如说,你想在特定用户登录商店的时候发一封邮件到管理员信箱里,可以通过“监听”customer_login事件(设置在config.xml中)。
singleton mymodule/observer iSpyWithMyLittleEye
|
<
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
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系统还提供了类的复写功能,你可以通过自己的代码覆盖核心代码里的Model, Helper 和 Block 类。类似Ruby或Python语言中的”Duck Typing” 和”Monkey Patching”。
下面是一个例子来帮助大家理解。一个产品的模型类是Mage_Catalog_Model_Product。
只要下面的代码被调用时,将创建一个Mage_Catalog_Model_Product对象
$product = Mage::getModel('catalog/product');
|
$
product
=
Mage
::
getModel
(
'catalog/product'
)
;
|
他是一个工厂模式。
Magento的类重写系统的作用是让你告诉系统
“Magento! 如果有请求对应catalog/product,不要实例化Mage_Catalog_Model_Product,让Packagename_Modulename_Model_Foobazproduct接手”
|
“
Magento
!
如果有请求对应
catalog
/
product,不要实例化
Mage_Catalog_Model
_Product,让
Packagename_Modulename_Model
_Foobazproduct接手”
|
然后,如果你愿意,你的Packagename_Modulename_Model_Foobazproduct类可以扩展原有产品类别
class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product { }
|
class
Packagename_Modulename_Model_Foobazproduct
extends
Mage_Catalog_Model_Product
{
}
|
这将允许您更改类的任何方法的行为,但保持现有方法的功能。
class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product { public function validate() { //add custom validation functionality here return $this; } }
|
class
Packagename_Modulename_Model_Foobazproduct
extends
Mage_Catalog_Model_Product
{
public
function
validate
(
)
{
//add custom validation functionality here
return
$
this
;
}
}
|
正如你所料,这最主要的(或重写)在config.xml文件中完成。
Packagename_Modulename_Model_Foobazproduct
|
<
models
>
<
!
--
does
the
override
for
catalog
/
product
--
>
<
catalog
>
<
rewrite
>
<
product
>
Packagename_Modulename_Model_Foobazproduct
<
/
product
>
<
/
rewrite
>
<
/
catalog
>
<
/
models
>
|
有一件事,重要的是要注意这里。在你的模块里的类都覆盖各个类中的其他模块。 ,但是覆盖整个模块,你都没有。这使您可以更改特定的行为方式,而不必担心什么模块的其余部分在做什么。
我们希望您喜欢的一些Magento的电子商务系统提供给开发商的这一特点旋风之旅。它可以是一个有点势不可挡,第一,特别是如果这是你与一个现代化的,面向对象的PHP系统初体验。如果你开始感到沮丧,深呼吸,提醒自己,这是新的,新的东西很难,但在一天结束的时候它的编码只是一种不同的方式。一旦你得到了学习曲线,你会发现自己不愿意回到其他不那么强大的系统。