在深入了解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)这一匹配方法
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,会得一个如下的数组
$p = Array ( [0] => catalog [1] => category [2] => view )
下面函数将会检测请求中是否包含模块名称,如果没有,请会根据数组的第一个元素来决定模块名称。如果无法获得模块名称,函数会返回false,这部分代码如下:
// 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:
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模型的结构:
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