Magento Basics, Request Flow, Standards and Best Practices
Nowadays, companies are migrating their businesses online more and more. They’ve chosen to direct their attention to online communication in order to stay closer to their potential buyers, to make use of social websites to inform people about their products/services and, finally, to find a workaround that provides people with a buying process directly from home, work office or any place with an Internet connection.
One of the solutions for e-commerce that gained trust from lots of companies is Magento. It supports small businesses, but also scales to larger sizes.
Alongside the expansion of e-commerce came across the need for experts to maintain and customize functionalities for their specific shop infrastructure.
In this article we will discuss the initial step that needs to be accomplished by a developer in order to get familiar with Magento’s structure and be ready to start adding custom functionality.
Magento’s Basics
A free version of Magento Community Edition can be downloaded from here, the official Magento website.
Assuming we have already configured a virtual host in a local environment and extracted Magento in the desired folder, before firing the installer, file permissions needs to be set correctly. Here are the steps:
-
set all directories and subdirectories from the application to 775
-
set all files to 644
-
set directory app/etc/ and all its files and subdirectories to 777
-
set directory var/ and all its files and subdirectories to 777
-
set directory media and all its files and subdirectories to 777
In a linux system you can achieve all this by typing the following commands in your Magento folder:
find . -type d -exec chmod 775 {} \;
find . -type f – exec chmod 644 {} \;
chmod 777 -R app/etc/
chmod 777 -R var/
chmod 777 -R media/`
After the installer is done working, file permissions from app/etc
should be changed back – directories to 775 and files to 644, for security reasons.
Code pools
Modules are located in app/code/
in the existing code pools: core, community (deprecated) and local.
Each module has a corresponding configuration in an .xml
file in app/etc/modules/
and has defined a syntax like <codePool>local</codePool>
from which Mage_Core_Model_Config
→ getModuleDir()
will point to the module’s path.
Module structure
Block
The files in this folder inherit or load data and transfer it to the templates from your theme (.phtml files).
controllers
Controllers represent the business logic, containing specific actions that are matched for a given request (dispatch(), preDispatch(), postDispatch() methods) and delegate commands to the other parts of the system.
Helper
In the Helper directory you can create class files with utility methods, parsing methods and other generally helpful methods for your store which are commonly used by the whole system. Methods declared in helpers can be called from any template file or block, model or controller. Magento is returning a singleton instance for helper classes.
Controller
In this directory routes can be defined, used to match module-controller-action based on the request, template layers for controller classes, etc. For an example, see the “Paypal” module.
Model
Usually contains classes that correspond to database tables. Models can be used as regular models that use two resource classes (one for reading and one for writing) to communicate with the database, resource models used to prepare query data for database communication, service models used as standalone classes that define a process flow, helper models used as standalone classes with individual methods for computing more complex algorithms than regular Helpers, and so on.
etc
Contains module behavior configurations. Each module must have at least a config.xml
file and declare paths for models, blocks, helpers, routers, etc.
sql
Each module can come along with sql installers to prepare database entities. Installers have a group name defined in each module/etc/config.xml
and each sql installer has a version for order iteration purposes.
data
Almost the same functional statement as sql installers, but with a different scope: will not create/update table schemes and attributes, instead it will update existing records or populate database tables with default records.
doc
A folder dedicated to documentation and module explanation.
Templates and Layout
The layout in Magento is kept in app/design/
and has a well defined structure for default and custom theming. Loading hierarchy within its priority and file existence starts with the custom theme. If not found, it will search the .phtml
file in default theme and if still not found, the last search path will be the base theme.
Magento establishes the theming structure in high-level areas such as adminhtml (system administration templates), frontend (the visible template for an end-user), installer (templates for the helper system that will automatically configure the shop).
Each theme has a folder ‘layout’ with .xml files (usually each module has its own referred layout configuration file) that defines a block of content for a controller action.
The custom theme may also contain a folder called ‘locales’ that will keep a file ‘translate.csv’ where all the custom translations are kept or even override existing ones already defined in standard Magento.
Skin and Javascript
The CSS files, images, and custom JS are located in the directory skin/
and implement the same theming structure and loading priority as for Templates and Layout (skin/area_name/package_name
– a custom namespace can be defined or base
or default/theme_name
).
Class naming conventions
Magento applies the basic, albeit outdated Zend class naming convention and uses Varien_Autoload::register()
to autoload classes by replacing the ‘_’ in the name with a directory separator to find the inclusion path to the file. This is very primitive, but due to backwards compatibility could not be solved sooner – Magento 2 will use ZF2, modern PHP and namespaces.
Request Flow
Let’s see how a Magento application handles a request starting from the main index.php
file.
Application Initialization
1) If you use Nginx, you should follow the configuration guide. If you’re on Apache, these are the rewrite rules for .htaccess
from the main application directory that shouldn’t be missing.
Rewrite rules for skin, media, JS directories to allow loading files and, if not found, to return 404. Everything else is rewritten to index.php:
<IfModule mod_rewrite.c>
############################################
## enable rewrites
Options +FollowSymLinks
RewriteEngine on
############################################
## always send 404 on missing files in these folders
RewriteCond %{REQUEST_URI} !^/(media|skin|js)/
############################################
## never rewrite for existing files, directories and links
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
############################################
## rewrite everything else to index.php
RewriteRule .* index.php [L]
</IfModule>
2) These are the steps the application follows when initialized:
-
run file index.php where
app/Mage.php
is included -
call
Mage::run($mageRunCode, $mageRunType)
-
in ‘run’, we load Varien Events Collection, the class
Mage_Core_Model_Config
and the classMage_Core_Model_App
-
Mage_Core_Model_App::run()
is triggered -
load configuration files from
app/etc/modules
-
load configuration xml files inside modules
app/code/{code pool}/{module name}/etc/config.xml
-
initialize current store
-
initialize request object
-
run sql and data installers
-
update/create versions of modules in core_resource
-
Front Controller dispatch –
Mage_Core_Controller_Varien_Front::dispatch();
-
collect routers from config by Area and Route Code
-
use routes to match Controller
-
if a controller and action will be matched, the controller file will be included, instantiated and action method will be called
-
action method will instantiate and call methods on models, depending on the request
-
the Varien Action Controller will call a Layout Object
-
this Layout Object will create a list of Block objects that are valid for this request, based on some request variables and system properties (also known as “handles”)
-
template files (.phtml) are assigned in the layout xml file for each block.
-
_prepareLayout()
method is called where, based on action layout xml structure, specific blocks are instantiated within associated .phtml files -
_toHtml()
method is triggered to generate html -
output html
Front controller
1) the properties and role of the Front Controller:
-
directory path:
app/code/core/Mage/Core/Controller/Varien/Front.php
-
base role: receive all requests from the browser and return HTML code
-
Front controller uses routes of the system to match a controller class and its action method
-
default Magento routers are: Standard (class
Mage_Core_Controller_Varien_Router_Standard
), Admin (classMage_Core_Controller_Varien_Router_Admin
), Default (classMage_Core_Controller_Varien_Router_Default
)
Here’s is an example of a defined route in the Customer module’s /etc/config.xml
:
<routers>
<customer>
<use>standard</use>
<args>
<module>Mage_Customer</module>
<frontName>Customer</frontName>
</args>
</customer>
</routers>
2) events that Front Controller triggers:
-
controller_front_init_before
-
controller_front_init_routers
-
controller_front_send_response_before
-
controller_front_send_response_after
URL rewrites
URL structure/processing in Magento.
A link in Magento has the below structure:
https://user:password@host:443/{base_path}/{base_script}/{storeview_path}/{module_front_name}/{controller_name}/{action_name}/{param1}/{value1}?{
query_param=query_value#fragment}
-
user:password@host:443/{base_path}/{base_script}
: the path to the Script file which runs Magento. Usually, it is anindex.php
file or a path that uses access rewrite rules toindex.php
-
{storeview_path}
: store view code will be used if the website has a multi-store-view model (e.g. multi-language support that is marked in the url) -
{module_front_name}/{controller_name}/{action_name}
: the path to match a module controller and action method -
{param1}/{value1}
: name and value of a parameter sent by url -
?query_param=query_value#fragment
: parameters query sent via the standard GET request
The above link will traverse through the following files, each contributing to match a schema for url and call the correct module-controller-action to output content:
-
app/Mage.php (Mage::app()->run())
-
app/code/core/Mage/Core/Model/App.php
-
Init and Dispatch controller
$this->getFrontController()->dispatch();
-
app/code/core/Mage/Core/Controller/Varien/Front.php
-
choose the defined router from
config.xml
to match module-controller-action$router->match($this->getRequest())
app/code/core/Mage/Core/Controller/Varien/Router/Admin.php
app/code/core/Mage/Core/Controller/Varien/Router/Standard.php
app/code/core/Mage/Core/Controller/Varien/Router/Default.php
-
app/code/core/Mage/Core/Controller/Varien/Action.php
-
call Action method (example:
indexAction()
)
URL rewrite process
Url is rewritten in the following cases:
-
core url rewrite: having priority over routers, will try to translate a custom request path to standard
module/controller/action
path based on the defined rows in database (core_url_rewrite
table) -
module name rewrite: a front name will be defined in a module’s
config.xml
to rewrite the actual path and name (example:Mage_Customer
will be matched by url through using its defined front name ‘customer’) -
custom routers will rewrite the url (mostly used for SEO purposes): for the default Magento an example will be the CMS module that will match a url-identifier like
homepage.html
to module ‘Mage_Cms’, controller ‘Page’, action ‘View’ and a parameter id that will match the cms entity row in the database.
Standards and Best Practices
Sample configuration xml
This is a sample of module config.xml (path: {base_app_folder}/app/local/{module_name}/etc/config.xml
) that includes the main configuration:
<?xml version="1.0"?>
<config>
<modules>
<{{localnamespaceC}}_{{modulenameC}}>
<version>0.1.0</version>
</{{localnamespaceC}}_{{modulenameC}}>
</modules>
<global>
<models>
<{{localnamespaceL}}_{{modulenameL}}>
<class>{{localnamespaceC}}_{{modulenameC}}_Model</class>
</{{localnamespaceL}}_{{modulenameL}}>
</models>
<resources>
<{{localnamespaceL}}_{{modulenameL}}_setup>
<setup>
<module>{{localnamespaceC}}_{{modulenameC}}</module>
</setup>
<connection>
<use>core_setup</use>
</connection>
</{{localnamespaceL}}_{{modulenameL}}_setup>
</resources>
<blocks>
<{{localnamespaceL}}_{{modulenameL}}>
<class>{{localnamespaceC}}_{{modulenameC}}_Block</class>
</{{localnamespaceL}}_{{modulenameL}}>
</blocks>
<helpers>
<{{localnamespaceL}}_{{modulenameL}}>
<class>{{localnamespaceC}}_{{modulenameC}}_Helper</class>
</{{localnamespaceL}}_{{modulenameL}}>
</helpers>
</global>
<frontend>
<routers>
<{{localnamespaceL}}_{{modulenameL}}>
<use>standard</use>
<args>
<module>{{localnamespaceC}}_{{modulenameC}}</module>
<frontName>{{modulenameL}}</frontName>
</args>
</{{localnamespaceL}}_{{modulenameL}}>
</routers>
<layout>
<updates>
<{{localnamespaceL}}_{{modulenameL}} module="{{localnamespaceC}}_{{modulenameC}}">
<file>{{modulenameL}}.xml</file>
</{{localnamespaceL}}_{{modulenameL}}>
</updates>
</layout>
</frontend>
</config>
Legend:
-
localnamespaceC and localnamespaceL is the name of your chosen namespace directory in app/local/{namespace} (C is for uppercase first letter of the word, and L for lower case)
-
modulenameC and modulenameL is the name of your module in app/local/{namespace}/{modulename}
Magento’s factory methods
Magento makes use of factory methods to instantiate models, helpers and blocks. The methods are:
-
Mage::getModel('{module}/{path to file in model directory}')
– returns an instance of a class in the Model directory -
Mage::helper('{module}/{path to file in helper directory}')
– returns a singleton instance of a class in the Helper directory -
Mage::getSingleton('{module}/path to file in model directory')
– returns a singleton instance of a class in the Model directory -
Mage::getResourceModel('{module}/{path to file in model/resource directory}')
– returns an instance of a class in the Model/Resource directory. -
Mage::getBlockSingleton('{module}/{path to file in block directory}')
– returns a singleton instance of a class in the Block directory after the layout for a controller action was initialized.
Rewriting the core
Magento’s architecture offers several ways to rewrite core files. Let’s see what they are and list some pros and cons:
a) rewrite a file by creating a similar directory path and file name in the ‘local’ code pool
-
you need to copy entire class with all its properties even if one line of code needs to be changed (downside)
-
if the file is also overridden in a custom module, the tracking for applied change previously may be lost due to the fact that it doesn’t respect the standard developers are used to.
b) rewrite the file in a Custom Module (local/MyNamespace/MyCustomModule/
) and create a subdirectory with the core module name and add the original subdirectory tree (e.g. local/MyNamespace/MyCustomModule/Model/Core/Rewrite.php).
- to override a file in a custom module name and not using Magento’s module name for this purpose will also confuse developers and the tracking of those changes may be lost by adding another rewrite in a different place. However, by adding module configuration (in
app/etc/modules/MyCustomModule.xml
) to mark dependency to theMage_Core
module you can give developers a heads up that a file rewrite was made.
c) rewrite a file in a Custom Module with Magento’s module name and preserve path (e.g. local/MyNamespace/Core/Model/Rewrite.php
)
-
this rewrite method is the most clear as developers can keep track of changes due to the naming convention.
-
will encourage other developers working on the same side to extend already changed functionality and not override it
-
a small disadvantage for this rewrite method is that the number of modules in a chosen namespace will grow along with each file that is overriden from different Magento modules.
Dependency between modules
a) when extending a Magento core module, dependency between modules needs to be configured
e.g. app/etc/modules/MyNamespace_Customer.xml
:
<MyNamespace_Customer>
<active>true</active>
<codePool>local</codePool>
<priority>1</priority>
<depends>
<Mage_Customer/>
</depends>
</MyNamespace_Customer>
b) when creating an sql installer or data installer in the Custom Module that updates the Magento core module entity, a dependency needs to be created
e.g. installer in MyCustomModule:
$installer = $this;
$installer->startSetup();
$sqlQuote = 'ALTER TABLE ' . $this->getTable('sales_flat_quote') .
' ADD `is_urgent` TINYINT UNSIGNED NOT NULL DEFAULT 0';
$installer->run($sqlOrder);
$installer->run($sqlQuote);
$installer->endSetup();
e.g. dependency config (sales quote entity is altered, dependency with ‘Sales’ module needs to be added):
<MyNamespace_MyCustomModule>
<active>true</active>
<codePool>local</codePool>
<priority>1</priority>
<depends>
<Mage_Sales/>
</depends>
</MyNamespace_MyCustomModule>
c) Extending/Rewriting a file (model, helper, block, controller) from the Magento core module requires dependency between modules, otherwise the last changed version will be arbitrary and you will lose control over the rewrite.
Module dependencies are important to prioritize order of sql statements, and on a fresh install they will avoid conflicts, crashes or wrong final data.
Conclusion
In this article, I’ve covered some of Magento’s fundamentals that will help developers understand the basic architecture and have a starting point in building their custom functionalities. If you’d like to see specific upgrades to Magento covered, or have use cases you’re curious about, let us know. Stay tuned for some Magento 2 tutorials and explanations as well!