在深入了解Magento的各组件之前,我们应当了解这里组件之间是如何交互的以及Magento是如何处理来自Web服务器的请求的。和其它PHP应用程序一样,Magento使用index.php作为入口文件,用于处理Mage.php引导类以及开启请求环节,主要有如下几步:
- Web服务器接到请求,magento通过调用引导文件Mage.php被实例化
- 初始化和实例化前端控制器,这控制器初始化过程中Magento搜索web路由并对它们进行实例化
- Magento在各路由器之前进行循环迭代并调用匹配方法,匹配方法用于处理URL以及生成对应的控制器和动作(action)
- Magento实例化匹配控制器并采取相应的动作
在这一过程中路由器(router)尤其重要,Router对象被前端控制器用来匹配请求的模块控制器URL(路由)和动作。Magento默认包含如下路由器:
Mage_Core_Controller_Varien_Router_Admin
Mage_Core_Controller_Varien_Router_Standard
Mage_Core_Controller_Varien_Router_Default
然后动作控制器会加载并渲染布局,进而加载相应的块文件、模型文件和模板文件。
下面我们来分析一下Magento是如何处理目录页的请求的,这里我们以http://localhost/catalog/category/view/id/10为例。Magento的URI由3部分组成-/FrontName/ControllerName/ActionName,也就是说我们刚刚所举的url可以分成如下部分:
FrontName: catalog
ControllerName: category
ActionName: view
如果查看Magento的路由器类,会看Mage_Core_Controller_
Varien_Router_Standard(app\code\core\Mage\Core\Controller\Varien\Router)这一匹配方法
1 2 3 4 5 6 7 8 9 10 11 | public function match(Zend_Controller_Request_Http $request ) { … $path = trim( $request ->getPathInfo(), '/' ); if ( $path ) { $p = explode ( '/' , $path ); } else { $p = explode ( '/' , $this ->_getDefaultPath()); } … } |
从上面的代码可以看出router首先将URI解析为一个数组,根据我们的示例URL,会得一个如下的数组
1 2 3 4 5 6 | $p = Array ( [0] => catalog [1] => category [2] => view ) |
下面函数将会检测请求中是否包含模块名称,如果没有,请会根据数组的第一个元素来决定模块名称。如果无法获得模块名称,函数会返回false,这部分代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // get module name if ( $request ->getModuleName()) { $module = $request ->getModuleName(); } else { if (! empty ( $p [0])) { $module = $p [0]; } else { $module = $this ->getFront()->getDefault( 'module' ); $request ->setAlias(Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS, '' ); } } if (! $module ) { if (Mage::app()->getStore()->isAdmin()) { $module = 'admin' ; } else { return false; } } |
下一步,匹配函数使用如下代码在现有的模块中进行循环来匹配controller和action:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | foreach ( $modules as $realModule ) { $request ->setRouteName( $this ->getRouteByFrontName( $module )); // get controller name if ( $request ->getControllerName()) { $controller = $request ->getControllerName(); } else { if (! empty ( $p [1])) { $controller = $p [1]; } else { $controller = $front ->getDefault( 'controller' ); $request ->setAlias( Mage_Core_Model_Url_Rewrite::REWRITE_REQUEST_PATH_ALIAS, ltrim( $request ->getOriginalPathInfo(), '/' ) ); } } // get action name if ( empty ( $action )) { if ( $request ->getActionName()) { $action = $request ->getActionName(); } else { $action = ! empty ( $p [2]) ? $p [2] : $front ->getDefault( 'action' ); } } //checking if this place should be secure $this ->_checkShouldBeSecure( $request , '/' . $module . '/' . $controller . '/' . $action ); $controllerClassName = $this ->_validateControllerClassName( $realModule , $controller ); if (! $controllerClassName ) { continue ; } // instantiate controller class $controllerInstance = Mage::getControllerInstance( $controllerClassName , $request , $front ->getResponse()); if (! $controllerInstance ->hasAction( $action )) { continue ; } $found = true; break ; } |
让我们来对这一长串代码进行分解,第一个循环查看请求中是否带有controller名称以及action名,Magento通过调用如下方法来匹配controller名:
$controllerClassName = $this->_validateControllerClassName($realModule, $controller);
该方法不仅会生成一个匹配类名而且会验证类别是否存在,我们例子应该会返回Mage_Catalog_CategoryController。
既然现在我们有一个有效的类名,就可以开始实例化我们的controller对象,您可能注意到至此我们还没有用action做任何事,这正是下面循环中要做的。新实例化的控制器带一个非常好用的方法hasAction(),其本质上是PHP中的is_callable()函数,用于检测我们当前的控制器是否拥有一个与action名相匹配的公共函数,本例中应为viewAction()。
能够使用这种精密的匹配进程以及foreach循环源自多个模块使用相同的FrontName:
http://localhost/catalog/category/view/id/10不是一个对用户友好的URL,幸好Magento拥有自己的rewrite系统,让我们可以使用http://localhost/books.html这样的链接。
我们再进一步的查看URL重写系统来看看Magento如何从URL别名中获取controller名和action名。在Varien/Front.php的控制器调度方法,Magento会调用:
Mage::getModel(‘core/url_rewrite’)->rewrite();
在查看rewrite方法的内部机制之前,我们来看看core/url_rewrite模型的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Array ( [ "url_rewrite_id" ] => "10" [ "store_id" ] => "1" [ "category_id" ] => "10" [ "product_id" ] => NULL [ "id_path" ] => "category/10" [ "request_path" ] => "books.html" [ "target_path" ] => "catalog/category/view/id/10" [ "is_system" ] => "1" [ "options" ] => NULL [ "description" ] => NULL ) |
可以看到重写模块是由多个属性组成,其中request_path和target_path两个具有特别的用途,简单的说rewrite模块会通过匹配target_path的值来修改请求对象的路径信息。
下一节让我们来看看Magento版本的MVC