Magento模块开发教程

阅读: 评论:0

2024年1月28日发(作者:)

Magento模块开发教程

Magento模块开发教程第一章Magento强大的配置系统Magento的配置系统就像是Magento的心脏,支撑着Magento的运行。这套配置系统掌管着几乎所有“module/model/class/template/etc”。它把整个Magento系统抽象出来,用一个配置文件来描述。这里的“配置文件”并不是一个物理上存在的文件,而是Magento根据当前的系统状态动态生成的一段XML。大多数的PHP开发者并不习惯于这样抽象层,因为它增加的编程的复杂性。但是这样的抽象提供了无与伦比的灵活性,允许你覆盖几乎任何系统的默认行为。首先,让我们写一个简单的插件来看看这个所谓的“配置文件”长什么样。虽然我已经提供的现成的代码,但是还是建议你自己建立这个插件,把整个流程走一遍有助于你的理解。设置插件的目录结构我们将要创建一个Magento的模块【注:Magento的插件不叫plug-in,叫module,翻译成模块】。Magento的模块由php和xml文件组成,目的是扩展或者覆盖系统的行为,比如为订单增加数据模型,更改一个类的方法,或者增加一个全新的功能。【注:Magento自带的那些功能也都是基于模块的,比如用户注册,商品展示,结账流程等等。Magento给我的感觉就是一切皆模块,和Eclipse的插件体系结构有点像】大多数Magento的系统模块的结构和我们将要构建的插件的结构是一样的。Magento的系统模块在以下目录app/code/core/Mage每一个子目录都是一个单独的模块。这些模块是由Magento官方开发的。我们安装完Magento以后,所使用的功能就是来自这些模块。我们自己创建的模块应该放在如下目录app/code/local/Packagename“Packagename”应该是一个唯一的字符串,用来标识你的代码。通常人们使用公司名字作为Packagename,比如app/code/local/Microsoft由于我在做我自己的Magento项目,我将使用我自己的域名“Alanstormdotcom”。然后,我们要创建以下目录结构app/code/local/Alanstormdotcom/Configviewer/Block

app/code/local/Alanstormdotcom/Configviewer/controllersapp/code/local/Alanstormdotcom/Configviewer/etcapp/code/local/Alanstormdotcom/Configviewer/Helperapp/code/local/Alanstormdotcom/Configviewer/Modelapp/code/local/Alanstormdotcom/Configviewer/sql你的插件并不一定需要包含以上所有的目录,但是为了以后开发方便,我们还是在一开始就把目录创建好。接下来我们要创建两个文件,一个是,放在etc目录下面app/code/local/Alanstormdotcom/Configviewer/etc/文件内容如下0.1.0第二个文件需要在如下位置创建app/etc/modules/Alanstormdotcom_第二个文件应该遵循如下命名规则“Packagename_”,文件内容如下truelocal我们先不管这些文件是干什么的,以后会解释。建立好这两个文件以后,你的模块的骨架就已经完成了。Magento已经知道你的模块存在,但是现在你的模块不会做任何事情。我们来确认一下Magento确实装载了你的模块1.2.3.4.清空Magento缓存在后台管理界面,进入System->Configuration->Advanced展开“DisableModulesOutput”确认“Alanstormdotcom_Configviewer”显示出来了如果你看到“Alanstormdotcom_Configviewer”,那么恭喜你,你已经成功创建了你第一个Magento模块!创建模块逻辑我们之前创建的模块不会做任何事情,下面我们来为这个模块加入逻辑

1.检查“showConfig”查询字符串是否存在2.如果“showConfig”存在,那么检查“showConfigFormat”查询字符串是否存在3.如果“showConfigFormat”存在,那么输出指定格式的配置信息,否则输出默认格式的配置信息4.终止执行流程首先更改我们的文件...singletonAlanstormdotcom_Configviewer_Model_ObservercheckForConfigRequest然后创建如下文件Alanstormdotcom/Configviewer/Model/输入以下内容<?phpclassAlanstormdotcom_Configviewer_Model_Observer{constFLAG_SHOW_CONFIG='showConfig';constFLAG_SHOW_CONFIG_FORMAT='showConfigFormat';private$request;publicfunctioncheckForConfigRequest($observer){$this->request=$observer->getEvent()->getData('front')->getRequest();if($this->request->{self::FLAG_SHOW_CONFIG}==='true'){$this->setHeader();$this->outputConfig();}}privatefunctionsetHeader(){$format=isset($this->request->{self::FLAG_SHOW_CONFIG_FORMAT})?$this->request->{self::FLAG_SHOW_CONFIG_FORMAT}:

'xml';switch($format){case'text':header("Content-Type:text/plain");break;default:header("Content-Type:text/xml");}}privatefunctionoutputConfig(){die(Mage::app()->getConfig()->getNode()->asXML());}}?>好了,代码编辑结束。清空你的Magento缓存,输入如下URL/?showConfig=true【注:根据文中的配置,不难看出任何指向Magento的URL加了“?showConfig=true”以后,都会输出同样的内容,正常的执行流程会被终止。】配置文件分析打开上述URL,你应该看到一个巨大的XML文件。这个文件描述了当前Magento系统的状态。它列出了所有的模块,数据模型,类,事件,监听器等等。举个例子,如果你搜索如下字符串Configviewer_Model_Observer你会发现刚刚你创建的那个类被列出来了。Magento会解析每个模块的,并把它们包含在这个全局配置中。这个配置文件有啥用?到目前为止,我们所作的事情似乎没什么意义,但是这个配置文件却是理解Magento的关键因素。你创建的每一个模块都会被加到这个配置文件中,任何时候,你需要调用一个系统功能的时候,Magento都会通过这个配置文件来查询相应的模块和功能。举个简单的例子,如果你懂MVC的话,你应该和“helperclass”之类概念的打过交道$helper_salesrule=newMage_SalesRule_Helper();Magento抽象了PHP的类声明方式。在Magento系统中,上面的代码等同于$helper_salesrule=Mage::helper('salesrule');Magento将通过以下逻辑来处理这行代码1.在配置文件中查找标签2.在里面查找标签3.在里面查找标签

4.实例化从#3找到的类(Mage_SalesRule_Helper)Magento总是通过配置文件来获得类名,这个逻辑看起来有些复杂,但这样做的优点也很明显,我们可以不需要更改Magento的代码就能更改Magento的核心功能。【注:在这个例子中,我们可以通过修改配置文件用我们自己的SalesRule_Helper类来替换原来那个】这种高度抽象的编程方式在php中并不常见,但是它可以让你清晰的扩展或者替换系统的某一部分。第二章Magento请求分发与控制器Model-View-Controller(MVC),模型-视图-控制器,源于Smalltalk编程语言和XeroxParc。现在有很多系统是基于MVC架构的,不同的系统MVC的实现也略有不同,但都体现了MVC的精髓,分离数据,业务逻辑和显示逻辑。最常见的PHPMVC框架是这样的

请求被一个PHP文件拦截,通常称为前端控制器(FrontController)2.这个PHP文件分析这个URL,获得一个执行控制器(ActionController)的名字和一个执行方法(ActionMethod)的名字,这个过程通常称为路由(Routing)3.实例化#2获得的执行控制器4.调用执行控制器的执行方法5.执行方法中处理业务逻辑,比如获取数据6.执行控制器负责把数据传递给显示逻辑7.显示逻辑生成HTML

这个架构相对于传统的“每个php都是一个页面”来讲已经是一个巨大的飞跃,但还是有人抱怨【注:CodeIgniter就是这样一个MVC框架】••前端控制器仍然以全局的方式运行基于配置的惯例导致了系统不够模块化URLRouting不够灵活o控制器往往和视图绑定o更改默认设置往往导致大量的重构oMagento创造了一个更抽象的MVC来解决上述问题。

请求被一个PHP拦截2.这个PHP文件实例化一个Magento对象o对象实例化前端控制器4.前端控制器实例化全局配置中指定的路由对象,可以是多个5.路由对象会逐个与请求URL匹配6.如果发现匹配,那么可以获得一个执行控制器和一个执行方法的名字7.实例化#6获得的执行控制器,并调用相应的执行方法8.执行方法中处理业务逻辑,模型数据9.控制器实例化布局对象(Layout)10.布局对象根据请求的参数,系统配置创建一个块对象(Block)列表,并实例化11.布局对象会调用块对象的output方法生成HTML。这是一个递归的过程,因为块对象可以嵌套块对象12.每一个块对象都和一个模板文件(TemplateFile)对应。块对象包含了显示逻辑,模板文件包含了HTML和PHP输出代码13.块对象直接从模型那里获得数据,换句话说,在Magento的MVC架构中,控制器并不直接把数据传给视图这里很复杂,我们以后会详细解释每一个部分。我们先关注“前端控制器->路由对象->执行控制器”部分。创建magento模块HelloWorld示例我们讲了太多理论,现在让我们来实践一下,通过实践来加深理解。下面是我们将要做的事情1.创建一个HelloWorld模块2.为这个模块配置路由3.为这个模块创建执行控制器创建HelloWorld模块首先,我们要创建一个模块的目录结构,这个我们以前已经做过了,就不再熬述app/code/local/Alanstormdotcom/Helloworld/Blockapp/code/local/Alanstormdotcom/Helloworld/controllersapp/code/local/Alanstormdotcom/Helloworld/etc

app/code/local/Alanstormdotcom/Helloworld/Helperapp/code/local/Alanstormdotcom/Helloworld/Modelapp/code/local/Alanstormdotcom/Helloworld/sql下面是的内容PATH:app/code/local/Alanstormdotcom/Helloworld/etc/0.1.0然后我们要创建一个系统配置文件来激活这个模块PATH:app/etc/modules/Alanstormdotcom_truelocal最后,让我们检查一下模块是不是已经被激活1.2.3.4.清空Magento缓存在管理后台,进入System->Configuration->Advanced展开“DisableModulesOutput”确认Alanstormdotcom_Helloworld显示出来了配置路由下面,我们要配置一个路由。路由是用来把一个URL请求转换成一个执行控制器和方法。和传统的PHPMVC不同的是,你需要在Magento的全局配置中显式的定义你的路由。我们继续上面的例子,在中,添加如下代码...standardAlanstormdotcom_Helloworldhelloworld

...在这里,我们有很多新名词要解释。什么是标签指向一个Magento区(Area),比如“frontend”就是指网站的前台,“admin”是指网站的后台,“install”是指Magento的安装程序。【注:这个有点像磁盘分区,区和区之间是相互独立的,但是都归操作系统能够管理,在这里归Magento管理。默认的Magento安装没有“install”这个区,frontend区接管了,全局配置中的以下代码可以解释这一点...standardMage_Installinstall...】什么是?PhilKarlton有一句很著名的话“在计算机领域只有两件事是困难的:缓存和命名”。Magento引入了很多新概念,无疑存在很多命名问题,这里就是一个例子。标签有时候包含的是路由对象的定义,有时候包含的是路径的定义。路由对象是进行路由操作的实体,而路径仅仅是路由对象的一个参数。【注:如果你仔细看过那个全局配置xml的话,你会发现有两处地方出现,一处是“->”,另外一处是“->”。你再仔细看看会发现两处包含的内容不一样。第一处包含的是路由对象的定义,第二处包含的是路径的定义。】什么是?这个标签的内容应该是一个模块的全名,Packagename_Modulename,在这里是“Alanstormdotcom_Helloworld”。Magento用这个名字来定位你的模块文件。

什么是?当一个router解析一个URL的时候,它是按照如下规则进行的/frontName/actionControllerName/actionMethod/所以,当我们在标签里定义了“helloworld”以后,Magento会把如下的URL请求交给我们的模块“Alanstormdotcom_Helloworld”来处理/helloworld/*有些人容易把和前端控制器(FrontController)混淆起来。它们是两个不同的概念,只跟路由相关。【注:根据我们前面讲过的Magento的MVC流程,前端控制器是用来实例化所有路由的,而这里的“frontName”只是路由过程中的一个参数】什么是?这个标签的名字应该是模块名字的小写版本。我们的模块名字是“Helloworld”,所以这里我们用“helloworld”。你应该也已经注意到我们定义的“frontName”也是和我们的模块相匹配的。这是一个不成文的规定,但不是强制要求。事实上,一个模块可以定义多个,也就是可以有多个“frontName”。为路由创建执行控制器还记得Magento的MVC流程吗?路由会把控制权交给执行控制器。上面我们定义了路由,现在我们来定义我们的执行控制器。首先创建文件app/code/local/Alanstormdotcom/Helloworld/controllers/模块的控制器应该放在模块的子目录“controllers”(小写c)里面。这是规定,Magento会在这个目录寻找模块的控制器文件。我们的第一个控制器包含以下内容classAlanstormdotcom_Helloworld_IndexControllerextendsMage_Core_Controller_Front_Action{publicfunctionindexAction(){echo'HelloWorld!';}}清空Magento缓存,请求如下URL/helloworld/index/index如果你看到一个空白页面上面写着“HelloWorld”,那么恭喜你,你已经成功创建了你的第一个Magento控制器!

如何命名执行控制器?还记得的标签吗?Alanstormdotcom_Helloworld执行控制的名字的构成如下1.2.3.4.以标签的内容开始(Alanstormdotcom_Helloworld)紧接一个下划线(Alanstormdotcom_Helloworld_)加上我们给控制器取的名字“Index”(Alanstormdotcom_Helloworld_Index)最后加上关键词“Controller”(Alanstormdotcom_Helloworld_IndexController)我们自己定义的属于frontend区的执行控制器都应该继承Mage_Core_Controller_Front_Action。URL里面的index/index是什么意思?正如前文所述,Magento默认的路由的规则如下/frontName/actionControllerName/actionMethod/所以在我们请求的URL/helloworld/index/index其中“helloworld”是“frontName”,第一个“index”是执行控制器(ActionController)的名字,第二个“index”是执行方法的名字。对比我们写的执行控制器代码,我们不难发现执行方法的定义是执行方法名字加上“Action”关键字publicfunctionindexAction(){...}Magento根据命名规则找到执行控制器文件并实例化,然后再根据命名规则调用指定的执行方法。如果URL没有给出执行控制器名字或者执行方法,Magento会用默认的“index”来替代,所以下面三个URL是等价的/helloworld/index/index/helloworld/index//helloworld/我们再来看一个例子。如果URL如下/checkout/cart/addMagento的执行步骤如下1.查询全局配置,找到frontName“checkout”对应的模块,Mage_Checkout2.找到执行控制器“Mage_Checkout_CartController”3.调用执行控制器的“addAction”方法

进一步理解执行控制器下面我们来为我们的执行控制器添加一个执行方法。添加如下代码到licfunctiongoodbyeAction(){echo'GoodbyeWorld!';}请求URL/helloworld/index/goodbye这次你应该看到“GoodbyeWorld!”。因为我们继承了“Mage_Core_Controller_Front_Action”,我们可以使用一些父类已经定义好的方法和变量。比如父类会把URL后面跟的参数转换成key/value的数组。添加如下代码到我们的执行控制器publicfunctionparamsAction(){echo'

';foreach($this->getRequest()->getParams()as$key=>$value){echo'
Param:'.$key.'
';echo'
Value:'.$value.'
';}echo'
';}请求如下URL/helloworld/index/params?foo=bar&baz=eof你应该看到如下输出Param:fooValue:barParam:bazValue:eof最后,让我们再写一个执行控制器,用来处理以下URL/helloworld/messages/goodbye这里的执行控制器名字是“messages”,所以我们要创建如下文件app/code/local/Alanstormdotcom/Helloworld/controllers/执行控制器的类名应该是Alanstormdotcom_Helloworld_MessagesController添加执行方法publicfunctiongoodbyeAction(){echo'AnotherGoodbye';}

好了,Magento的MVC架构大概就是这样了。它比传统的PHPMVC要复杂一点,但是Magento的这个高度灵活的MVC架构能让你创造出几乎所有你能想到的URL结构。第三章布局、块和模板我们接着研究Magento。根据我们第二章讲的MagentoMVC的架构,我们接下来应该讲模型(Model),但是我们跳过模型先来看布局和块。和一些流行的PHPMVC架构不同的是,Magento的执行控制器不直接将数据传给试图,相反的视图将直接引用模型,从模型取数据。这样的设计就导致了视图被拆分成两部分,块(Block)和模板(Template)。块是PHP对象,而模板是原始PHP文件,混合了XHTML和PHP代码(也就是把PHP作为模板语言来使用了)。每一个块都和一个唯一的模板文件绑定。在模板文件phtml中,“$this”就是指该模板文件对应的快对象。让我们来看一个例子File:app/design/frontend/base/default/template/catalog/product/你将看到如下代码<?php$_productCollection=$this->getLoadedProductCollection()?><?phpif(!$_productCollection->count()):?><?phpecho$this->__('Therearenoproductsmatchingtheselection.')?>

<?phpelse:?>这里“getLoadedProductCollection”方法可以在这个模板的块对象“Mage_Catalog_Block_Product_List”中找到File:app/code/core/Mage/Catalog/Block/Product/...publicfunctiongetLoadedProductCollection(){return$this->_getProductCollection();}...块的“_getProductCollection”方法会实例化模型,并读取数据然后返回给模板。嵌套块Magento把视图分离成块和模板的真正强大之处在于“getChildHtml”方法。这个方法可以让你实现在块中嵌套块的功能。顶层的块调用第二层的块,然后是第三层……这就是Magento如何输出HTML的。让我们来看一下单列的顶层模板File:app/design/frontend/base/default/template/page/

<?phpecho$this->getChildHtml('head')?>getBodyClass()?'class="'.$this->getBodyClass().'"':''?>><?phpecho$this->getChildHtml('after_body_start')?><?phpecho$this->getChildHtml('global_notices')?><?phpecho$this->getChildHtml('header')?><?phpecho$this->getChildHtml('breadcrumbs')?><?phpecho$this->getChildHtml('global_messages')?><?phpecho$this->getChildHtml('content')?>

<?phpecho$this->getChildHtml('footer')?><?phpecho$this->getChildHtml('before_body_end')?>
<?phpecho$this->getAbsoluteFooter()?>我们可以看到这个模板里面很多地调用了“$this->getChildHtml(…)”。每次调用都会引入另外一个块的HTML内容,直到最底层的块。布局对象看到这里,你可能有这样的疑问Magento怎么知道在一个页面上要用那些块?Magento怎么知道哪一个块是顶层块?“$this->getChildHtml(…)”里面的参数是什么意思?块的名字吗?Magento引入了布局对象(LayoutObject)来解决上面的那些问题。布局对象(或者说布局文件)就是一个XML文件,定义了一个页面包含了哪些块,并且定义了哪个块是顶层块。

在第二章的时候我们在执行方法(ActionMethod)里面直接输出了HTML内容。现在我们要为我们的HelloWorld模块创建一个简单的HTML模板。首先我们要创建如下文件app/design/frontend/default/default/layout/包含以下内容再创建如下文件app/code/local/Alanstormdotcom/Helloworld/simple_包含以下内容Untitledbody{background-color:#f00;}最后,我们要在执行控制器里面调用布局文件,开始输出HTML。修改执行方法如下publicfunctionindexAction(){//removeourpreviousecho//echo'HelloIndex!';$this->loadLayout();$this->renderLayout();}清空Magento缓存,访问URL“/helloworld/index/index”。你应该看到一个纯红色背景的页面。这个页面的源代码应该和我们创建的文件“simple_”一模一样。

究竟是怎么回事呢?也许你看到这里一头雾水,没关系,我们来慢慢解释。首先你得安装一个LayoutViewer模块,这和我们第一章讲的ConfigViewer模块很相似,都是查看Magento的内部信息。安装完这个模块之后【译者注:你需要参照第一章的内容,为这个模块创建“app/etc/modules/Alanstormdotcom_”】,打开如下URL/helloworld/index/index?showLayout=page你看到的是你正在请求的页面的布局文件。它是由组成的。当你在执行方法中调用“loadLayout”时,Magento会做如下处理生成这个布局文件为每一个标签实例化一个块对象。块对象的类名是通过标签的name属性来查找的。这些块对象被存储在布局对象的_blocks数组中如果标签包含了output属性,那么这个块的名字和output属性的值会被添加到布局对象的_output数组中然后,当你在执行方法中调用“renderLayout”方法是,Magento会遍历_output数组中所有的块名字,从_blocks数组中获得该名字的块,并调用块对象中使用output属性的值作为名字的函数。这个函数往往是“toHtml”。这个output属性也告诉Magento这里就是输出HTML的起点,也就是顶层块。【注:直接阅读Layout类的代码应该比较容易理解这里的逻辑File:app/code/core/Mage/Core/Model/licfunctiongetOutput(){$out='';if(!empty($this->_output)){foreach($this->_outputas$callback){$out.=$this->getBlock($callback[0])->$callback[1]();}}return$out;}从这里我们也可以看出,一个页面的布局文件时可以拥有多个顶层块。】下面我们要讲解块对象是如何被实例化的,这个布局文件时如何被生成的,最后我们将动手做一个例子来实践这一章讲的内容。实例化块对象在布局文件中,标签有一个“type”属性,这个属性其实是一个URI

Mage_Page_Block这里我们拿到了一个基本类名“Mage_Page_Block”,然后添加URI的第二部分“html”到基本类名后面,我们就得到最终的块对象的类名“Mage_Page_Block_Html”。块的类名在Magento中被称为“分组类名”(GroupedClassNames),这些类都用相似的方法被实例化。我们将在以后的章节中详细介绍这个概念。的区别我们上面提到都会实例化块对象,那么它们究竟有什么区别呢?在布局文件中是用来表示替换一个已经存在的块,举个例子Magento首先创建了一个名叫“root”的块。然后,它有发现了一个引用(reference)的名字也叫“root”,Magento会把原来那个“root”块替换成标签里面的那个快。再来看看我们之前创建那个

在这里,块“root”被我们用替换了,指向了一个不同的模板文件。布局文件是如何生成的现在我们对布局文件已经有所了解了,但是这个布局文件是那里来的呢?要回答这个问题,我们得引入Magento中的另外两个概念,操作(Handle)和包布局(PackageLayout)。操作Magento会为每一个页面请求生成几个不同的操作。我们的LayoutView模块可以显示这些处理器/helloworld/index/index?showLayout=handles你应该看到类似如下列表的列表(和你的配置有关)defaultSTORE_bare_usTHEME_frontend_default_defaulthelloworld_index_indexcustomer_logged_out它们每一个都是一个操作的名字。我们可以在Magento系统的不同的地方配置操作。在这里我们需要关注两个操作“default”和“helloworld_index_index”。“default”处理器是Magento的默认处理器,参与每一个请求的处理。“helloworld_index_index”处理器的名字是frontname“helloworld”加上执行控制器的名字“index”再加上执行方法的名字“index”。这说明执行控制器的每一个执行方法都有一个相应的操作。我们说过“index”是Magento默认的执行控制器和执行方法的名字,所以以下请求的操作名字也是“helloworld_index_index”。/helloworld/?showLayout=handles包布局包布局和我们以前讲过的全局配置有些相似。它是一个巨大的XML文档包含了Magento所有的布局配置。我们可以通过以LayoutView模块来查看包布局,请求一下URL

/helloworld/index/index?showLayout=package你可能要等一会儿才能看到输出,因为文件很大。如果你的浏览器在渲染XML的时候卡死了,建议你换成text格式的/helloworld/index/index?showLayout=package&showLayoutFormat=text假设你选择的是XML格式输出,那么你应该看到一个巨大的XML文件,这就是包布局。这个文件时Magento动态生成的,合并当前主题(theme)下面所有的布局文件。如果你用的是默认安装的话,这些布局文件在以下目录app/design/frontend/base/default/layout/其实在全局配置中,有一个节点下面定义了所有将被装载的布局文件...当这些文件被装载以后,Magento还会装载最后一个布局文件,,也就是我们之前新建的那个文件。我们可以通过这个文件来定制Magento的布局。结合操作和包布局在包布局文件中,我们可以看到一些熟悉的标签等等,但是他们都包含在一下这些标签中这些就是操作标签。对于每个特定的请求来说,针对这个请求的布局文件是由包布局中所有和这个请求相关的操作标签组成的。比如我们上面的例子,和请求相关的操作标签如下所以,针对请求/helloworld/index/index布局文件就是包布局中上面这些标签的内容组合。在包布局文件中,还有一个标

值得我们注意。我们可以通过这个标签引入另外一个操作标签。比如这段代码的意思是,如果一个请求包含了“customer_acount_index”操作,那么这个请求的布局文件也应该包含“customer_account”操作标签下面的。更新我们的例子好了,理论讲完了,让我们来修改我们的例子,把这一章的内容实践一下。我们重新来看我们用一个引用(reference)覆盖了名为“root”的块。然后定义了一个新的块,指向了一个不同的模板文件。我们把这个引用放在操作标签下面,那就说明这个Layout将对所有的请求有效。如果你访问Magento自带的一些页面,你会发现它们要门是空白,要么就是和我们“helloworld”例子的红色背景,但这并不是我们想要的效果。我们来修改一下,让我们的模板仅对“helloworld”的请求有效。我们把操作标签换成了“helloworld_index_index”。清空Magento缓存,重新访问Magento的各个页面,你应该发现都恢复了正常,但是针对"helloworld"模块的请求页面还是我们自定义的那个。

目前我们只实现了一个“index”执行函数,现在我们来实现“goodbye”执行函数。修改我们的执行控制器代码如下publicfunctiongoodbyeAction(){$this->loadLayout();$this->renderLayout();}但是你访问一下页面的时候你还是会看到Magento的默认布局/helloworld/index/goodbye那是因为我们没有为这个请求定义布局。我们需要在中添加“helloworld_index_goodbye”标签。由于“index”请求和“goodbye”请求我们要套用的布局是一样的,所以我们将用标签来重用已有的配置清空Magento缓存,请求以下URL/helloworld/index/index/helloworld/index/goodbye你将会得到两个完全相同的页面。开始输出和getChildHtml方法在Magento默认的配置下,HTML输出是从名为“root”的块开始(其实是因为这个块拥有output属性【译者注:任何一个拥有output属性的块都是顶层块,在拥有多个顶层块的情况下Magento将按照块定义的先后顺序输出HTML】)。我们覆盖了“root”块的模板template="../../../../../code/local/Alanstormdotcom/Helloworld/simple_"模板文件的查找路径是当前主题(theme)的根目录,Magento默认设置时这里app/design/frontend/base/default为页面加入内容到目前为止,我们的页面都比较无聊,啥也没有。我们来为页面加点有意义的内容。修改如下

name="customer_form_register"template="customer/form/"/>我们在“root”块里面嵌套了一个块“customer_form_register”。这个块是Magento本来就有的,包含了一张用户注册表单。我们把这个块嵌套进来,那么我们在模板文件里面就能用这个块的内容。使用方法如下,修改simple_<?phpecho$this->getChildHtml('customer_form_register');?>这里“getChildHtml”的参数就是要引入的块的名字,使用起来相当方便。清空Magento缓存,刷新helloworld页面,你应该在红色背景上看到用户注册表单。Magento还有一个块,叫做“”,让我们把它也加进来。修改simple_

Links

<?phpecho$this->getChildHtml('');?><?phpecho$this->getChildHtml('customer_form_register');?>刷新页面,你会发现

Links

显示出来了,但是“”什么都没有显示。那是因为我们并没有把这个块引入到,所以Magento找不到这个块。“getChildHtml”的参数一定要是当前页面的布局文件中声明过的块。这样的话Magento就可以只实例化需要用到的块,节省了资源,我们也可以根据需要为块设置不同的模板文件。我们修改文件如下清空Magento缓存,刷新页面,你会看到一排链接显示出来了。【译者注:如果你细心一点的话你会发现“”块没有template属性,那是因为这个块的类中一定定义了默认的模板protectedfunction_construct()

{$this->setTemplate('page/template/');}】总结这一章我们讲解了布局的基础知识。你可能会觉得这个很复杂,但是你也不必过分担心,因为平常使用Magento是不会用到这些知识的,Magento提供的默认布局应该可以满足大部分需求。对于想要深入研究Magento的开发者来说,理解Magento的布局是至关重要的。布局,块和模板构成了MagentoMVC架构中的View,这也是Magento的特色之一。第四章模型和ORM基础对于任何一个MVC架构,模型(Model)层的实现都是占据了很大一部分。对于Magento来说,模型占据了一个更加重要的位置,因为它常常包含了一部分商业逻辑代码(可以说它对,也可以说它错)。这些代码在其他的MVC框架中往往出现在控制器或者帮助函数中。传统的PHP-MVC架构中的模型本来MVC的定义就不是很清晰,不同的人有不同的看法,而对于模型的定义争议就更多了。在MVC模式被广泛采用之前,PHP程序员往往通过SQL语句直接操作数据库。也有些程序员通过一个SQL抽象层来操作数据库(比如AdoDB)。程序员往往关注SQL语句本身,而不是和数据相关的对象。虽然直接操作SQL的方式一直被病诟,但是很多PHP框架还是以SQL为中心的。模型层提供了一系列对象,抽象/封装了数据操作,但是程序员最终还是需为模型层对象写SQL语句操作数据库。还有一些框架回避了SQL,使用了对象关系映射(ObjectRelationalMapping,ORM)来解决这个问题。使用这个方法的话,程序员不用关注SQL,而只需要和对象打交道。我们可以操作一个对象的属性,当“Save”方法被调用的时候,对象的属性会作为数据自动的被写入数据库。有些ORM框架会根据数据表的信息自动推测对象的属性,也有框架要求用户显示的生命对象属性和表的关系。比较有名的ORM框架有ActiveRecord等等。【注:ActiveRecord源自RubyonRails,不过现在PHP也有了】

关于ORM的概念,我就解释到这里。但是和许多计算机领域的其他概念一样,ORM的定义也越来越模糊了。我不想在这片文章中讨论关于ORM的争议,所以我说的ORM就是那个最基本的ORM概念。Magento的模型Magento理所当然的也追随潮流应用了ORM。虽然Magento自带的Zend框架提供了SQL抽象层,但是在大多数情况下我们将通过Magento自带的模型和我们自己的模型来进行数据访问。他和视图层(View)一样,Magento的模型层也不是简单的ORM,而是一个高度灵活,高度抽象甚至有点令人费解。解剖Magento的模型大部分的Magento模型分为两类。第一类是基本的ActiveRecord类型,一张表一个对象的模型。第二类是EntityAttributeValue(EAV)模型。【译者注:EAV翻译成“实体属性值”有点词不达意,还是就叫EAV的好】Magento自己定义了一个数据类型叫做模型集合(ModelCollection)。顾名思义,模型集合就是一个对象里面包含了很多模型对象。Magento的创造者Varien团队实现了PHP类库的标准接口,“IteratorAggregate”,“Countable”。这样模型集合就能调用这些方法,这也是模型集合和数组的区别。Magento的模型并不直接访问数据库。每一个模型都有一个资源模型(ResourceModel),每一个资源模型拥有两个适配器(Adapter),一个读,一个写。这样的话逻辑模型和数据库访问就分开了,所以从理论上讲更改底层数据库只需要重写适配器就可以了,所有上层代码都不需要更改。创建一个基本模型【译者注:从这一章开始我用我自己的例子替换了Alan的例子】继续我们HelloWorld的例子。在HelloWorld模块中创建如下classZhlmmc_Helloworld_BlogControllerextendsMage_Core_Controller_Front_Action{publicfunctionindexAction(){echo'HelloBlog';}}访问以下URL/Magento/helloworld/blog你应该看到“HelloBlog”输出。

创建数据表我们可以通过Magento自带的方法创建或者修改数据库,但是为了不引入过多新内容,我们暂且手工创建一张表。在你的数据库中执行以下语句CREATETABLE`blog_posts`(`blogpost_id`int(11)NOTNULLauto_increment,`title`text,`post`text,`date`datetimedefaultNULL,`timestamp`timestampNOTNULLdefaultCURRENT_TIMESTAMP,PRIMARYKEY(`blogpost_id`));INSERTINTO`blog_posts`VALUES(1,'MyNewTitle','Thisisablogpost','2009-07-0100:00:00','2009-07-0223:12:30');这里我们创建了一张名为“blog_posts”的表,并填充了一条数据。创建和启用模型创建模型要设置一个模型一共有以下四个步骤1.2.3.4.启用模型启用资源模型在资源模型中添加实体(Entity)。对于简单的模型来说,实体就是数据表的名字为资源模型设置读、写适配器在进行这些步骤之前,我们先来看假设这些步骤已经做完了,我们怎么用一个模型。在Magento中,我们用以下的方式来实例化一个模型$model=Mage::getModel('helloworld/blogpost');和我们以前讲过的“Mage::getHelper()”的原理类似,这里Magento也是通过全局配置去查找模型的类名。模型的类名和我们以前讲过的块类名一样,都是分组类名。这里参数的前半部分“helloworld”是组名(GroupName),后半部分“blogpost”是半类名(ClassName)【注:我将“ClassName”翻译成半类名是为了和类名区分开来】。具体步骤如下1.从全局配置“/global/models/GROUP_NAME/class”获得基本类名“Zhlmmc_Helloworld_Model”2.检查全局配置“/global/models/GROUP_NAME/rewrite/CLASS_NAME”是否设置,如果有那么这个节点的值将被作为类名实例化3.否则,最终的类名将是基本类名加上半类名,也就是“Zhlmmc_Helloworld_Model_Blogpost”

启用模型修改模块的Zhlmmc_Helloworld_Modelhelloworld_mysql4标签就是组名,也应该和模块名一致。标签的内容是基本类名,所有Helloworld模块的模型都用这个基本类名,命名方式如下Packagename_Modulename_Model标签指明了这个模块的模型要用哪个资源模型。这个标签的内容是组名加上“mysql4”我们将在后面详细介绍资源模型。现在让我们来实例化一个模型看看,修改indexAction方法publicfunctionindexAction(){$blogpost=Mage::getModel('helloworld/blogpost');echoget_class($blogpost);}清空Magento缓存,刷新页面,你应该看到一个类似这样的异常(请先打开Magento的开发模式)include()[e]:failedtoopenstream:Nosuchfileordirectory原因很简单,就是Magento尝试去实例化“Zhlmmc_Helloworld_Model_Blogpost”,但是它在Helloworld模块的文件夹里面找不到这个类。所以我们现在来创建这个类File:app/code/local/Zhlmmc/Helloworld/Model/ssZhlmmc_Helloworld_Model_BlogpostextendsMage_Core_Model_Abstract{protectedfunction_construct(){$this->_init('helloworld/blogpost');

}}刷新页面,你应该看到页面上显示“Zhlmmc_Helloworld_Model_Blogpost”。所有的模型都必须继承“Mage_Core_Model_Abstract”类。这个抽象类强制你实现一个方法“_construct”(注意:这个不是PHP的构造行数“__construct”)。这个方法应该调用父类已经定义好的“_init”方法,参数是资源模型的URI,也就是我们要告诉模型使用哪个资源模型。我们将在解释资源模型的时候再解释这个URI。启用资源模型并添加实体启用资源模型并添加实体好了,我们设置好了模型,下面我们要为模型设置资源模型。资源模型才是真正和数据库对话的组件。在模型的配置中,有一段这样的代码helloworld_mysql4的值将被用来实例化资源模型。我们不需要显式的调用资源模型,但是当一个模型需要访问数据库的时候,Magento会自动实例化一个资源模型来使用。Mage::getResourceModel('helloworld/blogpost');这里“helloworld/blogpost”就是我们给模型的“_init”传入的参数。“helloworld”是组名,“blogpost”是模型的半类名。“Mage::getResourceModel”方法将以“helloworld/blogpost”为URI在全局配置中找到标签的值,在这里是“helloworld_mysql4”。然后Magento会用URI“helloworld_mysql4/blogpost”去实例化资源模型类。实例化的过程和我们前面讲的模型的实例化是一样的,所以我们也需要在中添加资源模型的声明Zhlmmc_Helloworld_Model_Resource_Mysql4这里我们可以看到,资源模型的声明也是放在下面的。有点搞,但是也不必深究了,Magento就这么定义的。标签的值是所有资源模型类的基本类名,命名方式如下Packagename_Modulename_Model_Resource_Mysql4好了,我们已经配置了资源模型,我们来试试装载一些数据。修改indexAction如下publicfunctionindexAction(){

$params=$this->getRequest()->getParams();$blogpost=Mage::getModel('helloworld/blogpost');echo("LoadingtheblogpostwithanIDof".$params['id']."
");$blogpost->load($params['id']);$data=$blogpost->getData();var_dump($data);}清空Magento缓存,访问下面的页面/Magento/helloworld/blog/index/id/1你应该看到一个类似下面这样的异常include()[e]:failedtoopenstream:Nosuchfileordirectory我想你看到这里也明白了,我们要为模型添加一个资源类,添加如下文件File:app/code/local/Zhlmmc/Helloworld/Model/Resource/Mysql4/ssZhlmmc_Helloworld_Model_Resource_Mysql4_BlogpostextendsMage_Core_Model_Mysql4_Abstract{protectedfunction_construct(){$this->_init('helloworld/blogpost','blogpost_id');}}这里“_init”方法的第一个参数这个资源模型将要使用的数据表的URI,第二个参数是数据表中的列名。这个列的内容必须唯一,往往是数据表的主键。为资源模型添加实体刷新页面,你是不是得到下面的异常?Can'tretrieveentityconfig:helloworld/blogpost那是因为我们的资源文件现在还是一个空壳,并没有和数据库联系起来。现在我们来把资源模型和我们的表联系起来,修改如下Zhlmmc_Helloworld_Model_Resource_Mysql4

blog_posts

我们前面设置了资源模型使用的数据表的URI是“helloworld/blogpost”,那么Magento会把“helloworld”作为组名,“blogpost”作为实体名,也就是。在Magento的简单模型中(也就是继承Mage_Core_Model_Mysql4_Abstract的模型),一个实体对应一张数据表。我们的数据表是“blog_posts”,所以这里

标签的内容就是“blog_posts”。清空Magento缓存,再次刷新页面,你应该看到以下内容LoadingtheblogpostwithanIDof1array(5){["blogpost_id"]=>string(1)"1"["title"]=>string(12)"MyNewTitle"["post"]=>string(19)"Thisisablogpost"["date"]=>string(19)"2009-07-0100:00:00"["timestamp"]=>string(19)"2009-07-0223:12:30"}设置读写适配器在上面的例子中,我们已经可以从数据库中取数据了,但是我们却没有为资源模型设置读写适配器,怎么回事呢?原因很简单,那就是因为Magento会为没有适配器的资源模型启用默认适配器。我们也可以显式的配置默认的适配器default_writedefault_read标签下面有两个部分,一个读,一个写。标签名字中的“hellworld”是我们定义的组名【译者注:在资源模型的“_init”函数中传入的数据表的URI“helloworld/blogpost”的前半部分就是适配器名字的前半部分】。从这里我们也可以看出来一个资源组对应一对适配器。清空Magento缓存,刷新浏览器,你应该看到和刚才相同的页面。【译者注:如果你去全局配置中找“core_read”你会发现“default_read”,然后是“default_setup”

mysql4SETNAMESutf8pdo_mysqllocalhostrootadminzend-magento1这才是最终和数据库连接的详细信息。如果你再往下深究,你会发现全局配置有这么一段Mage_Core_Model_Resource_Type_Db_Pdo_Mysql所以,“Mage_Core_Model_Resource_Type_Db_Pdo_Mysql”才是最终连接数据库的类。如果我们更换数据库的话,我们要重写一个相似的类来连接别的数据库。基本模型操作所有的模型最终都继承自类“Varien_Object”。这个类属于Magento的系统类库,不属于Magento的核心模块。你可以在以下位置找到这个类lib/Varien/ento模型的数据保存在“_data”属性中,这个属性是“protected”修饰的。父类“Varian_Object”定义了一些函数用来取出这些数据。我们上面的例子用了“getData”,这个方法返回一个数组,数组的元素是“key/value”对。【译者注:其实就是数据表中一行的数据,“key”就是列名,“value”就是值】我们可以传入一个参数获取某个具体的“key”的值。$model->getData();$model->getData('title');还有一个方法是“getOrigData”,这个方法会返回模型第一次被赋予的值。【译者注:因为模型在初始化以后,值可以被修改,这个方法就是拿到那个最原始的值】$model->getOrigData();$model->getOrigData('title');“Varien_Object”也实现了一些PHP的特殊函数,比如神奇的“__call”。你可以对任何一个属性调用“get,set,unset,has”方法$model->getBlogpostId();$model->setBlogpostId(25);

$model->unsetBlogpostId();if($model->hasBlogpostId()){...}这里的方法名中的属性名字符合“camelcase”命名规则【注:简单的说就是Java的命名规则,每个单词的第一个字母大写,第一个字母可以大写也可以小写】。为了有效的利用这些方便的方法,我们在定义数据表列名的时候要用小写,并用下划线作为分隔符,比如“blogpost_id”。在最近的Magento版本中,这个规则已经被弱化,为了实现PHP的“ArrayAccess”接口$id=$model->['blogpost_id'];$model->['blogpost_id']=25;//也就是说,你会在Magento中同时看到这两种技巧的使用。Magento中的CRUD操作Magento模型通过“load,save,delete”三个方法来支持基本的Create,Read,Update和Delete操作。我们在上面已经使用过“load”方法了。这个方法的参数就是要装在的数据记录的“id”。$blogpost->load(1);“save”方法可以用来创建新数据或者修改已有数据。我们在中添加如下方法publicfunctioncreateNewPostAction(){$blogpost=Mage::getModel('helloworld/blogpost');$blogpost->setTitle('CodePost!');$blogpost->setPost('Thispostwascreatedfromcode!');$blogpost->save();echo'postcreated';}访问以下URL/Magento/helloworld/blog/createNewPost现在你数据表中应该有两条数据了。下面来修改一条数据publicfunctioneditFirstPostAction(){$blogpost=Mage::getModel('helloworld/blogpost');$blogpost->load(1);$blogpost->setTitle("TheFirstpost!");$blogpost->save();echo'postedited';}最后,我们来删除一条数据publicfunctiondeleteFirstPostAction(){$blogpost=Mage::getModel('helloworld/blogpost');$blogpost->load(1);$blogpost->delete();echo'postremoved';

}模型集合上面的例子我们只是演示了对单个数据操作,现在我们来看看如何同时操作多条记录。我们上面已经讲过,每个Magento的模型都有一个独特的模型集合。这些模型集合实现了PHP的“IteratorAggregate”和“Countable”接口,也就是他们可以作为“count”函数的参数,并且可以在“foreach”语句中使用。现在让我们来看看如何使用模型集合,在Blog控制器中添加如下方法publicfunctionshowAllBlogPostsAction(){$posts=Mage::getModel('helloworld/blogpost')->getCollection();foreach($postsas$blog_post){echo'

'.$blog_post->getTitle().'

';echonl2br($blog_post->getPost());}}访问如下URL/Magento/helloworld/blog/showAllBlogPosts你应该看到以下异常include()[e]:failedtoopenstream:Nosuchfileordirectory我想你不会被这个异常吓到,已经熟门熟路了。我们需要添加一个PHP类,定义Blogpost的模型集合。每个模型都有一个“protected”属性“_resourceCollectionName”【译者注:从父类“Mage_Core_Model_Abstract”继承来的】。这个属性的值是这个模型对应的模型集合的URI。protected'_resourceCollectionName'=>string'helloworld/blogpost_collection'在默认情况下,这个值是模型的URI加上“_collection”。Magento把模型集合也看做是一种资源(Resrouce),所以运用资源模型的命名规则,模型集合的全名是Zhlmmc_Helloworld_Model_Resource_Mysql4_Blogpost_Collection然后我们要创建如下文件File:app/code/local/Zhlmmc/Helloworld/Model/Resource/Mysql4/Blogpost/ssZhlmmc_Helloworld_Model_Resource_Mysql4_Blogpost_CollectionextendsMage_Core_Model_Mysql4_Collection_Abstract{protectedfunction_construct(){$this->_init('helloworld/blogpost');}}

这里的参数是模型的UR,用来I来初始化模型集合。刷新页面,你应该看到数据库中的Blog都显示出来了。总结首先我要恭喜你,到这里你已经创建并配置了你的第一个Magento模型。在后面的章节中我们将讲解Magento的另外一种模型EntityAttributeValueModel。在这章开始的时候,我撒了一个小谎。其实在Magento中,并不是所有的模型都继承自“Mage_Core_Model_Abstract”。在Magento最初的版本中,这个抽象类并不存在。所以有很多模型是直接继承自“Varien_Object”。不过这些并不影响我们创建Magento模型,了解一下就可以了,方便阅读Magento的代码。第五章Magento资源配置对于任何一个更新频繁的项目来说,保持开发环境和生产环境的数据库同步是件很头疼的事情。Magento提供了一套系统,用版本化的资源迁移脚本来解决这个问题。上一章,我们为HelloworldBlogpost创建了一个模型。我们直接通过SQL语句“CREATETABLE”来创建数据表。在这一章,我们将为Helloworld模块创建一个资源配置(SetupResource)用于创建数据表。我们也会创建一个模块升级脚本,用来升级已经安装的模块。下面是我们要做的步骤1.2.3.4.5.在配置文件中添加资源配置创建资源类文件创建安装脚本创建升级脚本添加资源配置修改Helloworld模型的Zhlmmc_HelloworldZhlmmc_Helloworld_Model_Setup_Mysql4_Setupcore_setup

标签是用来唯一标识我们正在创建的资源配置。虽然不是强制要求,但是我们应该使用“modelname_setup”来命名资源配置。标签的内容是“Packagename_Modulename”。最后,标签的内容就是我们将要创建的资源配置类的类名。虽然对于基本的配置来说,没有必要创建一个单独的资源配置类,但是为了更好的理解资源配置是如何工作的,我们的例子还是创建一个单独的类。File:app/code/local/Zhlmmc/Helloworld/Model/Setup/Mysql4/ssZhlmmc_Helloworld_Model_Setup_Mysql4_SetupextendsMage_Core_Model_Resource_Setup{}创建安装脚本下面我们将要创建一个安装脚本。这个安装脚本包含了“CREATETABLE”等SQL语句。这个脚本将在模块初始化的被运行。首先我们来看一下模块的配置文件0.1.0这一部分是所有都必须包含的。它包含了模块的名称,还有版本。我们的安装脚本的名字将基于这个版本号,“0.1.0”。创建以下文件File:app/code/local/Zhlmmc/Helloworld/sql/helloworld_setup/o'RunningThisUpgrade:'.get_class($this)."n
n";die("Exitfornow");文件路径中的“helloworld_setup”应该和上文在中添加的一致。文件名中的“0.1.0”就是模块的版本号。清空Magento缓存,访问任何URL,你应该看到以下内容RunningThisUpgrade:Zhlmmc_Helloworld_Model_Setup_这说明我们的安装脚本已经被运行了。我们先不放SQL脚本在这里,先把创建一个资源配置的流程走完。移除“die()”语句,重新装载页面,你应该看到你的Upgrade语句在页面的顶部,再次刷新页面,页面应该正常显示了。资源版本Magento的资源配置系统允许你直接拷贝安装脚本和升级脚本到服务器上,Magento会根据当前模块的版本自动运行相应的脚本。这样你就只需要维护一份数据库迁移脚本。我们先来

看看“core_resource”数据表mysql>selectcode,versionfromcore_resource;+————————-+————+|code|version|+————————-+————+|adminnotification_setup|1.0.0||admin_setup|alipay_setup|api_setup|backup_setup|bundle_setup|canonicalurl_setup|catalogindex_setup|cataloginventory_setup|catalogrule_setup|catalogsearch_setup|catalog_setup|checkout_setup|chronopay_setup|cms_setup|compiler_setup|contacts_setup|core_setup|cron_setup|customer_setup|cybermut_setup|cybersource_setup|dataflow_setup|directory_setup|downloadable_setup|eav_setup|eway_setup|flo2cash_setup|giftmessage_setup|googleanalytics_setup|googlebase_setup|googlecheckout_setup|googleoptimizer_setup|helloworld_setup|ideal_setup|index_setup|log_setup|moneybookers_setup|newsletter_setup|0.7.2||0.9.0||0.8.1||0.7.0||0.1.11||0.1.0||0.7.10||0.7.5||0.7.8||0.7.7||1.4.0.0.21||0.9.5||0.1.0||0.7.13||0.1.0||0.8.0||0.8.26||0.7.1||1.4.0.0.6||0.1.0||0.7.0||0.7.4||0.8.10||0.1.16||0.7.15||0.1.0||0.1.1||0.7.2||0.1.0||0.1.1||0.7.3||0.1.2||0.1.0||0.1.0||1.4.0.2||0.7.7||1.2||0.8.2|

|oscommerce_setup|paybox_setup|paygate_setup|payment_setup|paypaluk_setup|paypal_setup|poll_setup|productalert_setup|protx_setup|rating_setup|reports_setup|review_setup|salesrule_setup|sales_setup|sendfriend_setup|shipping_setup|sitemap_setup|strikeiron_setup|tag_setup|tax_setup|usa_setup|weee_setup|widget_setup|wishlist_setup+————————-+————+63rowsinset(0.00sec)||||||||||||||||||||||||0.8.100.1.30.7.10.7.00.7.00.7.40.7.20.7.20.1.00.7.20.7.100.7.60.7.120.9.560.7.40.7.00.7.20.9.10.7.50.7.110.7.10.131.4.0.0.00.7.7||||||||||||||||||||||||这张表包含了系统中所有安装的模块和模块的版本。你可以看到我们的模块|helloworld_setup|0.1.0|Magento就是根据这个版本来判断是否需要运行升级脚本的。这里“helloworld_setup”版本“0.1.0”,而我们的安装脚本也是“0.1.0”,所以Magento不会再运行该脚本。如果你需要重新运行安装脚本(在开发的时候常用到),只要删除表中相应模块的数据就行了。让我们来试试看DELETEfromcore_resourcewherecode='helloworld_setup';这次我们将通过安装脚本来创建数据表,所以我们也要删除之前创建的数据表DROPTABLEblog_posts;添加以下代码到我们的安装脚本$installer=$this;$installer->startSetup();$installer->run("CREATETABLE`{$installer->getTable('helloworld/blogpost')}`(`blogpost_id`int(11)NOTNULLauto_increment,`title`text,`post`text,`date`datetimedefaultNULL,

`timestamp`timestampNOTNULLdefaultCURRENT_TIMESTAMP,PRIMARYKEY(`blogpost_id`))ENGINE=InnoDBDEFAULTCHARSET=utf8;INSERTINTO`{$installer->getTable('helloworld/blogpost')}`VALUES(1,'MyNewTitle','Thisisablogpost','2009-07-0100:00:00','2009-07-0223:12:30');");$installer->endSetup();清空Magento缓存,访问任何URL,你应该发现“blog_posts”表又被建立了,拥有一条数据。解剖配置脚本让我们来分析一下上面的代码。【译者注:作者在文中混用了“installscript”,“upgradescript”和“setupscript”。我在翻译的时候尽量分清。配置脚本包含了安装脚本和升级脚本。】先看第一行$installer=$this;这个“$this”是什么呢?每一个配置脚本都是属于某个资源配置类(SetupResourceclass),比如上面我们创建的“Zhlmmc_Helloworld_Model_Setup_Mysql4_Setup”。这些脚本都是在这个资源配置类的上下文环境中运行的。所以“$this”就是指资源配置类的实例。虽然不是强制的,但是大多数核心模块的资源配置类都用“$installer”来代替“$this”,就和我们这里做的一样。虽然我们说不是强制的,但是我们最好还是遵守这个约定,除非你有一个很好的理由。接下来看下面两个调用$installer->startSetup();//…$installer->endSetup();如果你打开“Mage_Core_Model_Resource_Setup”类(也就是我们创建的资源配置类的父类)的源码,你将会看到这两个方法做了一些SQL准备工作publicfunctionstartSetup(){$this->_conn->multi_query("SETSQL_MODE='';SET@OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS,FOREIGN_KEY_CHECKS=0;SET@OLD_SQL_MODE=@@SQL_MODE,SQL_MODE='NO_AUTO_VALUE_ON_ZERO';");return$this;}publicfunctionendSetup(){$this->_conn->multi_query("

SETSQL_MODE=IFNULL(@OLD_SQL_MODE,'');SETFOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS,0);");return$this;}真正的配置逻辑是在“run”方法中$installer->run(…);这个方法的参数就是你要运行的SQL。你可以包含任意数量的SQL,用分号隔开。你可能注意到了以下语句$installer->getTable('helloworld/blogpost')【译者注:你的第一反应是什么?是不是联想到里面下面这段代码?Zhlmmc_Helloworld_Model_Resource_Mysql4

blog_posts
这里“helloworld/blogpost”就是我们要创建的数据表的URI。Magento先用“helloworld”找到模块,得到资源模块“helloworld_mysql4”,然后在资源模块下面通过“blogpost”找到类名“blog_posts”。】很显然,你可以用常量“blog_posts”来代替调用“getTable”,但是调用这个方法可以保证即使用户更改了配置文件中的数据表名字,你的配置脚本依然能运行。“Mage_Core_Model_Resource_Setup”类有很多类似这样的帮助函数。学习这些函数最好的方法就是阅读Magento核心模块的资源配置类代码。模块升级脚本上面我们说过配置脚本包括安装脚本和升级脚本。讲完了安装脚本,我们来讲升级脚本。安装脚本是在第一次安装模块的时候使用的,而升级脚本顾名思义就是在升级模块的时候使用。Magento的资源配置系统使用了版本化的方式来升级模块。首先要说明的是,在安装模块的时候Magento会执行一次安装脚本,然后Magento再也不会为该模块执行任何安装脚本了。如果你要更新模块的数据表就要通过升级脚本来执行。除了命名方式以外,升级脚本和安装脚本并没有太大的不同。创建升级脚本如下File:app/code/local/Zhlmmc/Helloworld/sql/helloworld_setup/o'Testingourupgradescript

()andhaltingexecutiontoavoidupdatingthesystemversionnumber
';die();升级脚本和安装脚本是放在相同目录下面的,但是命名方式不同。首先是“upgrade”关键词。其次,你会发现这里我们有两个版本号,用“-”分开。第一个版本号“0.1.0”是指从哪个版本升级(起始版本),第二个版本号“0.2.0”是指要升级到哪个版本(目标版本)。清空Magento缓存,请求任何一个URL,你会发现没有任何配置脚本被运行。那是因为,第一,我们已经运行过安装脚本,第二,目前我们模块的版本是“0.1.0”,所以Magento不会运行我们要升级到“0.2.0”的升级脚本。要让Magento运行这个升级脚本,我们得修改配置文件中的版本号0.2.0清空Magento缓存,重新请求页面,你会看到升级脚本的输出。【译者注:作者在这里引入了第二个升级到“0.1.5”的升级脚本,我觉得并没有必要,我来直接总结一下Magento升级的步骤1.2.3.4.5.6.7.8.9.10.11.从数据表“core_resource”中获得当前模块的安装版本从配置文件中获得当前模块的版本如果两个版本一样,那么什么都不做如果#2的版本号小于#1的版本号,我也不知道Magento会干什么,理论上是不可能出现的情况如果#2的版本号大于#1的版本号,那么开始升级程序在配置脚本文件夹内(在上面的例子中是“helloworld_setup”)把所有升级脚本加入队列在队列内,按照升级脚本的起始版本排序,升序循环队列如果队列中当前脚本的起始版本不等于“core_resource”数据表中当前模块的版本号,那么跳过该脚本如果队列中当前脚本的起始版本等于“core_resource”数据表中当前模块的版本号并且目标版本小于等于#2的版本号,那么执行该脚本。循环队列结束,升级结束值得注意的是第10步,每次执行一个升级脚本,“core_resource”数据表中的版本号都会被更新。所以如果我们有两个升级文件“0.1.0-0.1.5”和“0.1.0-0.2.0”,只有一个升级文件会被执行。】下面我们来为升级脚本添加实质内容$installer=$this;$installer->startSetup();

$installer->run("ALTERTABLE`{$installer->getTable('helloworld/blogpost')}`CHANGEpostposttextnotnull;");$installer->endSetup();清空Magento缓存,刷新页面,你应该看到升级脚本的输出。如果没有,请你对照上面讲的Magento升级的步骤查找错误。总结这一章我们讲解了Magento是如何处理模块数据表的安装和升级的。Magento的资源配置系统使用版本化的升级脚本,这样就保证了不同版本的模块可以使用同一套的升级脚本,便于维护。我们后面的章节也会提到这种升级方式的优点,特别是对于使用EAV模型的模块来说,这个优点更为明显。第六章高级Magento模型我们讲过Magento有两种模型,简单模型和EAV(EntityAttributeValue)模型。上一章我们讲过所有的Magento模型都是继承自Mage_Core_Model_Abstract/Varien_Object。简单模型和EAV模型的区别在于资源模型(ModelResource)。虽然所有的资源模型都最终继承“Mage_Core_Model_Resrouce_Abstract”,但是简单模型是直接继承“Mage_Core_Model_Mysql4_Abstract”,而EAV模型是直接继承“Mage_Eav_Model_Entity_Abstract”。Magento这么做是由它的道理的。对于大部分开发人员或者用户来说,他们只需要知道一系列的方法能够操作模型,获得数据,数据到底是如何存储的并不是很重要。什么是EAV模型?EAV(Entity-Attribute-Value)模型,也作Object-Attribute-Value模型或者开放模型是一种数据模型。这种数据模型常常用在一个对象的属性数目不是一定的情况下。在数学上,这种模型称为松散矩阵。换一种方式理解,EAV模型就是数据表的一种泛化。在传统的数据库中,数据表的列的数量是一定的+——————+|products|+——————+

|product_id||name||price||+——————++————+—————-+——————+———+|product_id|name|price+————+—————-+——————+———+|1|WidgetA|11.34+————+—————-+——————+———+|2|DongleB|6.34+————+—————-+——————+———+在上面这张表中,每一个商品都有名称,价格等等。|etc…|etc…|etc…|||在EAV模型中,每一个模型都有不同的属性。这对于电子商务的应用来说是很合适的。比如说一个网店可以卖笔记本,拥有CPU速度,颜色,内存等属性,但是网店也可以卖衣服,有颜色属性,但是没有CPU速度。即使是卖衣服的网店,也有上衣和裤子之分,它们的属性也是不一样的。有很多开源的或者商业的数据库是默认使用EAV模型的。但是一般的网站托管平台不提供这些数据库。所以Varien开发了一套基于PHP和MySQL的EAV系统。换句话说,它们在传统的关系型数据库上面开发了一套EAV数据库系统。在使用的时候,EAV模型的属性是会分布在不同的MySQL数据表中。

上面的这张图是Magento中关于“catalog_product”的表。每一个产品都是“catalog_product_entity”中的一行。Magento系统中所有的属性(不仅仅是商品)都存放在“eav_attribute”表中,而属性的值都放在类似下面的表中“catalog_product_entity_attribute_varchar”,“catalog_product_entity_attribute_decimal”,“catalog_product_entity_attribute_etc”。【译者注:如果你仔细观察上面这幅数据表结构图,你会发现明显少了一张表,和“entity_type”有关。因为这里有“entity_type_id”出现,但却没有定义这个属性的表。这个表在Magneto中叫做“eav_entity_type”。由于EAV模型中所有的模型数据都混在一套数据表中了,实体类型(entity_type)就是用来把不同的模型区别开来的属性。假如我们要找出系统中所有的产品数据,那么Magento先通过“eav_entity_type”表获得产品模型的“entity_type_id”,然后再通过上面这幅图的关系来拿到所有的数据。】在EAV系统下面,当你需要添加一个属性的时候,只需要在“eav_attribute”表中添加一行就行了。而传统的关系型数据库则需要修改数据表调用“ALTERTABLE”语句,复杂而且有风险。EAV模型的缺点是你不能通过一个简单的SQL语句就获得一个模型的所有属性。你往往需要调用多个SQL或者一个SQL包干了多个join语句。

实战EAV模型我们已经介绍了EAV是怎么工作的了。下面我们要通过一个例子来说明要在Magento中创建一个EAV模型所需要的步骤。这部分内容大概是Magento中最令人头疼的部分,95%的Magento用户都不会和这些代码打交道,但是理解EAV模型的原理能够帮助你更好的理解Magento的代码和架构。因为EAV模型的内容太多了,所以我假设你已经熟悉了前几章的内容,包括MagentoMVC,组类名等等。在这一章我不会再重复这些内容。EAV形式的HelloWorld我们将为HelloWorld模块创建另外一个模型,使用EAV形式的资源模型。首先我们为模块创建一个新的模型叫做“Eavblogpost”。记住,简单模型和EAV模型的区别是资源模型,所以我们创建一个模型的基本步骤是一样的。Zhlmmc_Helloworld_Modelhelloworld-eav_mysql4我想我不说你也应该知道,我们要创建一个新的模型文件。由于PHP5.3和命名空间(namespaces)还没有被广泛采用,Magento中的类名仍然和文件的物理路径相关。这就导致了很多时候不知道一个URI所对应的类究竟该放在什么文件夹下面。我发现我们可以利用Magento的异常信息来直接得到一个类的路径。比如,这里我们先不创建模型类,先来修改BlogController来直接使用模型类,这样Magento就会报错说找不到模型类,并给出路径publicfunctioneavReadAction(){$eavModel=Mage::getModel('helloworld-eav/eavblogpost');echoget_class($eavModel)."
";}清空Magento缓存,访问以下URL/Magento/helloworld/blog/eavRead跟预计的一样,你应该得到以下异常Warning:include(Zhlmmc/Helloworld/Model/)

[e]:failedtoopenstream:Nosuchfileordirectory所以我们应该创建如下文件File:app/code/local/Zhlmmc/Helloworld/Model/ssZhlmmc_Helloworld_Model_EavblogpostextendsMage_Core_Model_Abstract{protectedfunction_construct(){$this->_init('helloworld-eav/blogpost');}}刷新页面,你应该看到下面的输出Zhlmmc_Helloworld_Model_Eavblogpost下面我们来创建资源模型。先定义资源模型Zhlmmc_Helloworld_Model_Resource_Eav_Mysql4

eavblog_posts
这里的标签名字和我们上面定义的模型的是一致的。的定义和上一章是一样的。下面的适配器的定义default_writedefault_read然后再次利用Magento的异常,先修改“eavReadAction”publicfunctioneavReadAction(){$eavModel=Mage::getModel('helloworld-eav/eavblogpost');

$params=$this->getRequest()->getParams();echo("LoadingtheblogpostwithanIDof".$params['id']."
");$eavModel->load($params['id']);$data=$eavModel->getData();var_dump($data);}清空Magento缓存,访问URL/Magento/helloworld/blog/eavRead/id/1你应该看到如下异常Warning:include(Zhlmmc/Helloworld/Model/Resource/Eav/Mysql4/)[e]:failedtoopenstream:Nosuchfileordirectory所以我们创建相应的资源模型类File:app/code/local/Zhlmmc/Helloworld/Model/Resource/Eav/Mysql4/ssZhlmmc_Helloworld_Model_Resource_Eav_Mysql4_BlogpostextendsMage_Eav_Model_Entity_Abstract{publicfunction_construct(){$resource=Mage::getSingleton('core/resource');$this->setType('helloworld_eavblogpost');$this->setConnection($resource->getConnection('helloworld-eav_read'),$resource->getConnection('helloworld-eav_write'));}}这个类和简单的资源模型就不一样。首先,我们这里继承的是“Mage_Eav_Model_Entity_Abstract”。其次,我们没有调用“_init”方法。在EAV模型中我们需要自己来完成资源模型初始化的过程,包括,告诉资源模型使用哪个适配器,以及实体类型(entity_type)。刷新URL,你应该看到如下异常Invalidentity_typespecified:helloworld_eavblogpost根据我们上文所讲的内容,那这个异常的原因很明显,那就是“eav_entity_type”表中,没有需要的“helloworld_eavblogpost”的数据。这里的“helloworld_eavblogpost”就是我们“setType”的参数。让我们来看一下这张表长什么样mysql>select*fromeav_entity_typeG

******************************************************entity_type_id:1entity_type_code:customerentity_model:customer/customerattribute_model:entity_table:customer/entityvalue_table_prefix:entity_id_field:is_data_sharing:1data_sharing_key:defaultdefault_attribute_set_id:1increment_model:eav/entity_increment_numericincrement_per_store:0increment_pad_length:8increment_pad_char:0additional_attribute_table:customer/eav_attributeentity_attribute_collection:customer/attribute_collection******************************************************entity_type_id:2entity_type_code:customer_addressentity_model:customer/customer_addressattribute_model:entity_table:customer/address_entityvalue_table_prefix:entity_id_field:is_data_sharing:1data_sharing_key:defaultdefault_attribute_set_id:2increment_model:increment_per_store:0increment_pad_length:8increment_pad_char:0additional_attribute_table:customer/eav_attributeentity_attribute_collection:customer/attribute_collection正如我们前面讲过的,这张表包含了所有系统中的实体类型。我们的参数“helloworld_eavblogpost”就是实体类型的值,对应数据表列“entity_type_code”。

创建资源配置系统和应用程序这一章讲的内容是Magento最重要的一个概念,也是很多人觉得头疼的概念。拿电脑来做比方。操作系统,比如MacOSX,Windows,Linux等等,是软件系统,而浏览器,比如FIrefox,Safari,IE等等是应用程序。Magento首先是一个系统,其次才是一个应用程序。你可以在Magento系统之上创建一个电子商务应用。令人感到困惑的是Magento的代码在很多地方是以很原始的方式暴露给应用程序的。EAV系统的配置和你网店的数据存放在统一数据库中就是一个例子。随着你越来越深入Magento,你需要把Magento当作老式的IBM650机器。也就是说,你必须对Magento有很深的了解才能对它运用自如。【注:这一段和上下文没什么关系】创建资源配置从理论上讲,你可以手动的在数据库中插入数据,让我们的EAV模型工作,但我还是不建议你这么做。所幸的是,Magento提供了一个特殊的资源配置类,包含了一些有用的方法能自动的创建一些数据,使得系统能工作。我们先添加资源配置Zhlmmc_HelloworldZhlmmc_Helloworld_Model_Entity_Setupcore_setup创建资源配置类文件File:app/code/local/Zhlmmc/Helloworld/Model/Entity/ssZhlmmc_Helloworld_Model_Entity_SetupextendsMage_Eav_Model_Entity_Setup{}

Magento模块开发教程

本文发布于:2024-01-28 10:04:20,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/17064074606642.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:模型   模块   脚本   执行   创建
留言与评论(共有 0 条评论)
   
验证码:
排行榜
  • 我要关灯
    我要开灯
  • 返回顶部