Book Image

Magento PHP Developer????s Guide, 2nd Edition

By : Allan MacGregor
Book Image

Magento PHP Developer????s Guide, 2nd Edition

By: Allan MacGregor

Overview of this book

Table of Contents (16 chapters)
Magento PHP Developer's Guide Second Edition
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Routing and request flow


Before going into more detail about the different components that form a part of Magento, it is important that we understand how these components interact together and how Magento processes requests coming from the web server.

As with any other PHP application, we have a single file as an entry point for every request. In the case of Magento, this file is index.php, which is in charge of loading the Mage.php Bootstrap class and starting the request cycle.

  1. The web server receives the request and Magento is instantiated by calling the Bootstrap file Mage.php.

  2. The frontend controller is instantiated and initialized. During this controller initialization, Magento searches for the web routes and instantiates them.

  3. Magento then iterates through each of the routers and calls the match. The match method is responsible for processing the URL and generating the corresponding controller and action.

  4. Instantiates the matching controller and corresponding action.

Routers are especially important in this process. Router objects are used by the frontend controller to match a requested URL (route) to a module controller and action. By default, Magento comes with the following routers:

  • Mage_Core_Controller_Varien_Router_Admin

  • Mage_Core_Controller_Varien_Router_Standard

  • Mage_Core_Controller_Varien_Router_Cms

  • Mage_Core_Controller_Varien_Router_Default

The action controller will then load and render the layout, which in turn will load the corresponding blocks, models, and templates.

Let's analyze how Magento will handle a request to a category page. We will use http://localhost/catalog/category/view/id/10 as an example. The Magento URI comprises three parts, namely FrontName/ControllerName/ActionName.

Hence, for our example URL, the breakdown is as follows:

  • FrontName: This is a catalog

  • ControllerName: This is a category

  • ActionName: This is a view

Let's take a look at the Magento router class Mage_Core_Controller_Varien_Router_Standard match function:

public function match(Zend_Controller_Request_Http $request)
{
  …
   $path = trim($request->getPathInfo(), '/');
            if ($path) {
                $p = explode('/', $path);
            } else {
                $p = explode('/', $this->_getDefaultPath());
            }
  …
}

From the previous code, we can see that the first thing the router tries to do is parse the URI into an array. Based on our example URL, the corresponding array will be similar to the following code:

$p = Array
(
    [0] => catalog
    [1] => category
    [2] => view
)

The next part of the function will first try to check if the request has the module name specified. If not, then it tries to determine the module name, based on the first element of our array. If a module name can't be provided, then the function will return false. Let's take a look at this part of the code:

      // 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;
            }
        }

Next, the match function will iterate through each of the available modules and try to match the controller and action using the following code:

…
        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;
        }
...

Now that looks like an awful lot of code! Let's break it down further. The first part of the loop will check if the request has a controller name. If it is not set, it will check our parameter array's ($p) second value and try to determine the controller name. Then, it will try to do the same for the action name.

If we get this far in the loop, we should have a module name, a controller name, and an action name. Magento will now use these to try and get matched with the Controller class name by calling the following code:

$controllerClassName = $this->_validateControllerClassName($realModule, $controller);

This function will not only generate a matching class name, but it will also validate its existence. In our example case, this function should return Mage_Catalog_CategoryController.

As we now have a valid class name, we can proceed to instantiate our controller object. You may probably notice that so far we haven't done anything with our action yet, and that's precisely the next step on our loop.

Now, our instantiated controller comes with a very handy function called hasAction(). In essence, all this function does is call a PHP function called is_callable(), which will check if our current controller has a public function matching the action name. In our case this will be viewAction().

The reason behind this elaborate matching process and the use of a foreach loop is that it is possible for several modules to use the same frontName:

Now, http://localhost/catalog/category/view/id/10 is not a very user-friendly URL. Fortunately, Magento has its own URL rewrite system that allows us to use http://localhost/books.html.

Let's dig a little deeper into the URL rewrite system and see how Magento gets the controller and action names from our URL alias. Inside our Varien/Front.php controller dispatch function, Magento will call the following action:

Mage::getModel('core/url_rewrite')->rewrite();

Before actually looking into the inner working of the rewrite function, let's take a look at the structure of the core/url_rewrite model:

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
)

As we can see, the rewrite module comprises several properties, but only two of them are of particular interest to us, namely request_path and target_path. Simply put, the job of the rewrite module is to modify the request object path information with the matching values of target_path.