13 min read

(For more resources related to this topic, see here.)

Zend Framework – the base of Magento

As you probably know, Magento is the most powerful e-commerce platform in the market; what you might not know about Magento is that it is also an object-oriented (OO) PHP framework developed on top of Zend Framework.

Zend’s official site describes the framework as:

Zend Framework 2 is an open source framework for developing web applications and services using PHP 5.3+. Zend Framework 2 uses 100% object-oriented code and utilises most of the new features of PHP 5.3, namely namespaces, late static binding, lambda functions and closures.

The component structure of Zend Framework 2 is unique; each component is designed with few dependencies on other components. ZF2 follows the SOLID object oriented design principle. This loosely coupled architecture allows developers to use whichever components they want. We call this a “use-at-will” design.

But what is Zend Framework exactly? Zend Framework is an OO framework developed on PHP that implements the Model-View-Controller (MVC) paradigm. When Varien, now Magento Inc., started developing Magento it decided to do it on top of Zend because of the following components:

  • Zend_Cache

  • Zend_Acl

  • Zend_DB

  • Zend_Pdf

  • Zend_Currency

  • Zend_Date

  • Zend_Soap

  • Zend_Http

In total, Magento uses around 15 different Zend components. The Varien library directly extends several of the Zend components mentioned previously, for example Varien_Cache_Core extends from Zend_Cache_Core.

Using Zend Framework, Magento was built with the following principles in mind:

  • Maintainability: It occurs using code pools to keep the core code separate from local customizations and third-party modules

  • Upgradability: Magento modularity allows extensions and third-party modules to be updated independently from the rest of the system

  • Flexibility: Allows seamless customization and simplifies the development of new features

Although having used Zend Framework or even understanding it are not the requirements for developing with Magento, having at least a basic understanding of the Zend components, usage, and interaction can be invaluable information when we start digging deeper into the core of Magento.

You can learn more about Zend Framework at http://framework.zend.com/

Magento folder structure

Magento folder structure is slightly different from other MVC applications; let’s take a look at the directory tree, and each directory and its functions:

  • app: This folder is the core of Magento and is subdivided into three importing directories:

    • code: This contains all our application code divided into three code pools such as core, community, and local

    • design: This contains all the templates and layouts for our application

    • locale: This contains all the translation and e-mail template files used for the store

  • js: This contains all the JavaScript libraries that are used in Magento

  • media: This contains all the images and media files for our products and CMS pages as well as the product image cache

  • lib: This contains all the third-party libraries used in Magento such as Zend and PEAR, as well as the custom libraries developed by Magento, which reside under the Varien and Mage directories

  • skin: This contains all CSS code, images, and JavaScript files used by the corresponding theme

  • var: This contains our temporary data such as cache files, index lock files, sessions, import/export files, and in the case of the Enterprise edition the full page cache folders

Magento is a modular system. This means that the application, including the core, is divided into smaller modules. For this reason, the folder structure plays a key role in the organization of each module core; a typical Magento module folder structure would look something like the following figure:

Let’s review each folder in more detail:

  • Block: This folder contains blocks in Magento that form an additional layer of logic between the controllers and views

  • controllers: controllers folders are formed by actions that process web server requests

  • Controller: The classes in this folder are meant to be abstract classes and extended by the controller class under the the controllers folder

  • etc: Here we can find the module-specific configuration in the form of XML files such as config.xml and system.xml

  • Helper: This folder contains auxiliary classes that encapsulate a common-module functionality and make it available to a class of the same module and to other modules’ classes as well

  • Model: This folder contains models that support the controllers in the module for interacting with data

  • sql: This folder contains the installation and upgrade files for each specific module

As we will see later on in this article, Magento makes heavy use of factory names and factory methods. This is why the folder structure is so important.

Modular architecture

Rather than being a large application, Magento is built by smaller modules, each adding specific functionality to Magento.

One of the advantages of this approach is the ability to enable and disable specific module functionality with ease, as well as add new functionality by adding new modules.

Autoloader

Magento is a huge framework, composed of close to 30,000 files. Requiring every single file when the application starts would make it incredibly slow and heavy. For this reason, Magento makes use of an autoloader class to find the required files each time a factory method is called.

So, what exactly is an autoloader? PHP5 includes a function called __autoload().When instantiating a class, the __autoload() function is automatically called; inside this function, custom logic is defined to parse the class name and the required file.

Let’s take a closer look at the Magento bootstrap code located at app/Mage.php:


Mage::register('original_include_path', get_include_path());
if (defined('COMPILER_INCLUDE_PATH')) {
$appPath = COMPILER_INCLUDE_PATH;
set_include_path($appPath . PS .
Mage::registry('original_include_path'));
include_once "Mage_Core_functions.php";
include_once "Varien_Autoload.php";
} else {
/**
* Set include path
*/
$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'local';
$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'community';
$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'core';
$paths[] = BP . DS . 'lib';
$appPath = implode(PS, $paths);
set_include_path($appPath . PS .
Mage::registry('original_include_path'));
include_once "Mage/Core/functions.php";
include_once "Varien/Autoload.php";
}
Varien_Autoload::register();

The bootstrap file takes care of defining the include paths and initializing the Varien autoloader, which will in turn define its own autoload function as the default function to call. Let’s take a look under the hood and see what the Varien autoload function is doing:

/**
* Load class source code
*
* @param string $class
*/
public function autoload($class)
{
if ($this->_collectClasses) {
$this->_arrLoadedClasses[self::$_scope][] = $class;
}
if ($this->_isIncludePathDefined) {
$classFile = COMPILER_INCLUDE_PATH .
DIRECTORY_SEPARATOR . $class;
} else {
$classFile = str_replace(' ', DIRECTORY_SEPARATOR,
ucwords(str_replace('_', ' ', $class)));
}
$classFile.= '.php';
//echo $classFile;die();
return include $classFile;
}

The autoload class takes a single parameter called $class, which is an alias provided by the factory method. This alias is processed to generate a matching class name that is then included.

As we mentioned before, Magento’s directory structure is important due to the fact that Magento derives its class names from the directory structure. This convention is the core principle behind factory methods that we will be reviewing later on in this article.

Code pools

As we mentioned before, inside our app/code folder we have our application code divided into three different directories known as code pools. They are as follows:

  • core: This is where the Magento core modules that provide the base functionality reside. The golden rule among Magento developers is that you should never, by any circumstance, modify any files under the core code pool.

  • community: This is the location where third-party modules are placed. They are either provided by third parties or installed through Magento Connect.

  • local: This is where all the modules and code developed specifically for this instance of Magento reside.

The code pools identify where the module came from and on which order they should be loaded. If we take another look at the Mage.php bootstrap file, we can see the order on which code pools are loaded:

$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'local';
$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'community';
$paths[] = BP . DS . 'app' . DS . 'code' . DS . 'core';
$paths[] = BP . DS . 'lib';

This means that for each class request, Magento will look in local, then community, then core, and finally inside the lib folder.

This also leads to an interesting behavior that can easily be used for overriding core and community classes, by just copying the directory structure and matching the class name.

Needless to say that this is a terrible practice, but it is still useful to know about just in case you someday have to take care of a project that exploits this behavior.

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. It then goes through the following steps:

  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. Magento then instantiates the matching controller and takes the corresponding action.

Routers are especially important in this process. The 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_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. Magento URIs are comprised of three parts – /FrontName/ControllerName/ActionName.

This means that for our example URL, the breakdown would be as follows:

  • FrontName: catalog

  • ControllerName: category

  • ActionName: view

If I take a look at the Magento router class, I can see the 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 preceding code, we can see that the first thing the router tries to do is to parse the URI into an array. Based on our example URL, the corresponding array would be something like the following code snippet:

$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. And if a module name can’t be provided, then the function will return false. Let’s take a look at that 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->_validate
ControllerClassName($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, so let’s break it down even 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, and then it will try to do the same for the action name.

If we got this far in the loop, we should have a module name, a controller name, and an action name, which Magento will now use to try and get a matching controller class name by calling the following function:

$controllerClassName = $this->_validateControllerClassName($realModu
le, $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.

Since we now have a valid class name, we can proceed to instantiate our controller object; if you were paying attention up to this point, you have probably noticed that we haven’t done anything with our action yet, and that’s precisely the next step in our loop.

Our new instantiated controller comes with a very handy function called hasAction(); in essence, what this function does is to 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:

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

Before actually looking into the inner workings 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 is comprised of several properties, but only two of them are of particular interest to use – 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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here