Magento for Developers: Part 1 - Introduction to Magento

What is Magento? It's the most powerful online eCommerce platform in the universe and is changing the face of eCommerce forever. grin

Of course, you already know that. What you may not realize is Magento's also an object-oriented PHP Framework that can be used to develop modern, dynamic web applications that tap into Magento's powerful eCommerce features.

This is the first in a series of articles in which we're going to go on a whirlwind tour of Magento's programming framework features. Don't worry if you don't follow everything immediately. As you study the system more everything in this article will start to make sense, and you'll soon be the envy of your colleagues stuck working with more primitive PHP systems.

In this article...

Or for the more visually oriented Magento_MVC.pdf.


Code Organized in Modules

Magento organizes its code into individual Modules. In a typical PHP Model-View-Controller (MVC) application, all the Controllers will be in one folder, all the Models in another, etc. In Magento, files are grouped together based on functionality, which are called modules in Magento.

Magento's Code

For example, you'll find Controllers, Models, Helpers, Blocks, etc. related to Magento's checkout functionality in

app/code/core/Mage/Checkout

You'll find Controllers, Models, Helpers, Blocks, etc. related to Magento's Google Checkout functionality in

app/code/core/Mage/GoogleCheckout
Your Code

When you want to customize or extend Magento, rather than editing core files directly, or even placing you new Controllers, Models, Helpers, Blocks, etc. next to Magento code, you'll create your own Modules in

app/code/local/Package/Modulename

Package (also often referred to as a Namespace) is a unique name that identifies your company or organization. The intent is that each member of the world-wide Magento community will use their own Package name when creating modules in order to avoid colliding with another user's code.

When you create a new Module, you need to tell Magento about it. This is done by adding an XML file to the folder:

app/etc/modules

There are two kinds of files in this folder, the first enables an individual Module, and is named in the form: Packagename_Modulename.xml

The second is a file that will enable multiple Modules from a Package/Namespace, and is named in the form: Packagename_All.xml. Note it is only used by the core team with the file Mage_All.xml. It is not recommended to activate several modules in a single file, as this breaks the modularity of your modules.

Configuration-Based MVC

Magento is a configuration-based MVC system. The alternative to this would a convention-based MVC system.

In a convention-based MVC system, if you wanted to add, say, a new Controller or maybe a new Model, you'd just create the file/class, and the system would pick it up automatically.

In a configuration-based system, like Magento, in addition to adding the new file/class to the codebase, you often need to explicitly tell the system about the new class, or new group of classes. In Magento, each Module has a file named config.xml. This file contains all the relevant configuration for a Magento Module. At runtime, all these files are loaded into one large configuration tree.

For example, want to use Models in your custom Module? You'll need to add some code to config.xml that tells Magento you want to use Models, as well as what the base class name for all your Models should be.

<models>
     <
packagename>
          <class>
Packagename_Modulename_Model</class>
    <
packagename>
</
models>

The same goes for Helpers, Blocks, Routes for your Controllers, Event Handlers, and more. Almost anytime you want to tap into the power of the Magento system, you'll need to make some change or addition to your config file.

Controllers

In any PHP system, the main PHP entry point remains a PHP file. Magento is no different, and that file is index.php.

However, you never CODE in index.php. In an MVC system, index.php will contains code/calls to code that does the following:

  1. Examines the URL

  2. Based on some set of rules, turns this URL into a Controller class and an Action method (called Routing)

  3. Instantiates the Controller class and calls the Action method (called dispatching)

This means the practical entry point in Magento (or any MVC-based system) is a method in a Controller file. Consider the following URL:

http://example.com/catalog/category/view/id/25

Each portion of the path after the server name is parsed as follows.

Front Name - catalog

The first portion of the URL is called the front name. This, more or less, tells magento which Module it can find a Controller in. In the above example, the front name is catalog, which corresponds to the Module located at:

app/code/core/Mage/Catalog

Controller Name - category

The second portion of the URL tells Magento which Controller it should use. Each Module with Controllers has a special folder named 'controllers' which contains all the Controllers for a module. In the above example, the URL portion category is translated into the Controller file

app/code/core/Mage/Catalog/controllers/CategoryController.php

Which looks like

class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
{
}

All Controllers in the Magento cart application extend from Mage_Core_Controller_Front_Action.

Action Name - view

Third in our URL is the action name. In our example, this is "view". The word "view" is used to create the Action Method. So, in our example, "view" would be turned into "viewAction"

class Mage_Catalog_CategoryController extends Mage_Core_Controller_Front_Action
{
    
public function viewAction()
    
{
        
//main entry point
    
}
}

People familiar with the Zend Framework will recognize the naming convention here.

Paramater/Value - id/25

Any path portions after the action name will be considered key/value GET request variables. So, in our example, the "id/25" means there will get a GET variable named "id", with a value of "25".

As previously mentioned, if you want your Module to use Controllers, you'll need to configure them. Below is the configuration chunk that enables Controllers for the Catalog Module

<frontend>
    <
routers>
        <
catalog>
            <use>
standard</use>
            <
args>
                <
module>Mage_Catalog</module>
                <
frontName>catalog</frontName>
            </
args>
        </
catalog>
    </
routers>
</
frontend>

Don't worry too much about the specifics right now, but notice the <frontName>catalog</frontName>

This is what links a Module with a URL frontname. Most Magento core Modules choose a frontname that is the same as their Module name, but this is not required.

Multiple Routers

The routing described above is for the Magento cart application (often called the frontend). If Magento doesn't find a valid Controller/Action for a URL, it tries again, this time using a second set of Routing rules for the Admin application. If Magento doesn't find a valid Admin Controller/Action, it uses a special Controller named Mage_Cms_IndexController.

The CMS Controller checks Magento's content Management system to see if there's any content that should be loaded. If it finds some, it loads it, otherwise the user will be presented with a 404 page.

For example, the main magento "index" page is one that uses the CMS Controller, which can often throw newcomers for a loop.

Context-Based URI Model Loading

Now that we're in our Action method entry point, we'll want to start instantiating classes that do things. Magento offers a special way to instantiate Models, Helpers and Blocks using static factory methods on the global Mage class. For example:

Mage::getModel('catalog/product');
Mage::helper('catalog/product');

The string 'catalog/product' is called a Grouped Class Name. It's also often called a URI. The first portion of any Grouped Class Name (in this case, catalog), is used to lookup which Module the class resides in. The second portion ('product' above) is used to determine which class should be loaded.

So, in both of the examples above, 'catalog' resolves to the Module app/code/core/Mage/Catalog.

Meaning our class name will start with Mage_Catalog.

Then, product is added to get the final class name

Mage::getModel('catalog/product');
Mage_Catalog_Model_Product

Mage
::helper('catalog/product');
Mage_Catalog_Helper_Product

These rules are bound by what's been setup in each Module's config file. When you create your own custom Module, you'll have your own grouped classnames (also calles classgroups) to work with Mage::getModel('myspecialprefix/modelname');.

You don't have to use Groupd Class Names to instantiate your classes. However, as we'll learn later, there are certain advantages to doing so.

Magento Models

Magento, like most frameworks these days, offers an Object Relational Mapping (ORM) system. ORMs get you out of the business of writing SQL and allow you to manipulate a datastore purely through PHP code. For example:

$model Mage::getModel('catalog/product')->load(27);
$price $model->getPrice();
$price += 5;
$model->setPrice($price)->setSku('SK83293432');
$model->save();

In the above example we're calling the methods "getPrice" and "setPrice" on our Product. However, the Mage_Catalog_Model_Product class has no methods with these names. That's because Magento's ORM uses PHP's magic __call method to implement getters and setters.

Calling the method $product->getPrice(); will "get" the Model attribute "price".

Calling $product->setPrice(); will "set" the Model attribute "price". All of this assumes the Model class doesn't already have methods named getPrice or setPrice. If it does, the magic methods will be bypassed. If you're interested in the implementation of this, checkout the Varien_Object class, which all Models inherit from.

If you wanted to get all the available data on a Model, call $product->getData(); to get an array of all the attributes.

You'll also notice it's possible to chain together several calls to the set method:
$model->setPrice($price)->setSku('SK83293432');

That's because each set method returns and instance of the Model. This is a pattern you'll see used in much of the Magento codebase.

Magento's ORM also contains a way to query for multiple Objects via a Collections interface. The following would get us a collection of all products that cost $5.00

$products_collection Mage::getModel('catalog/product')
->
getCollection()
->
addAttributeToSelect('*')
->
addFieldToFilter('price','5.00');

Again, you'll notice Magento's implemented a chaining interface here. Collections use the PHP Standard Library to implement Objects that have array like properties.

foreach($products_collection as $product)
{
    
echo $product->getName();
}

You may be wondering what the "addAttributeToSelect" method is for. Magento has two broad types of Model objects. One is a traditional "One Object, One Table" Active Record style Model. When you instantiate these Models, all attributes are automatically selected.

The second type of Model is an Entity Attribute Value (EAV) Model. EAV Models spread data across several different tables in the database. This gives the Magento system the flexibility to offer its flexible product attribute system without having to do a schema change each time you add an attribute. When creating a collection of EAV objects, Magento is conservative in the number of columns it will query for, so you can use addAttributeToSelect to get the columns you want, or addAttributeToSelect('*') to get all columns.

Helpers

Magento's Helper classes contain utility methods that will allow you to perform common tasks on objects and variables. For example:
$helper = Mage::helper('catalog');

You'll notice we've left off the second part of the grouped class name. Each Module has a default Data Helper class. The following is equivalent to the above:
$helper = Mage::helper('catalog/data');

Most Helpers inherit form Mage_Core_Helper_Abstract, which gives you several useful methods by default.

$translated_output =  $helper->__('Magento is Great'); //gettext style translations
if($helper->isModuleOutputEnabled()): //is output for this module on or off?

Layouts

So, we've seen Controllers, Models, and Helpers. In a typical PHP MVC system, after we've manipulated our Models we would

  1. Set some variables for our view
  2. The system would load a default "outer" HTML layout>
  3. The system would then load our view inside that outer layout

However, if you look at a typical Magento Controller action, you don't see any of this:

/**
 * View product gallery action
 */
public function galleryAction()
{
    
if (!$this->_initProduct()) {
        
if (isset($_GET['store']) && !$this->getResponse()->isRedirect()) {
            $this
->_redirect('');
        
elseif (!$this->getResponse()->isRedirect()) {
            $this
->_forward('noRoute');
        
}
        
return;
    
}
    $this
->loadLayout();
    
$this->renderLayout();
}

Instead, the Controller action ends with two calls

$this->loadLayout();
$this->renderLayout();

So, the "V" in Magento's MVC already differs from what you're probably used to, in that you need to explicitly kick off rendering the layout.

The layout itself also differs. A Magento Layout is an object that contains a nested/tree collection of "Block" objects. Each Block object will render a specific bit of HTML. Block objects do this through a combination of PHP code, and including PHP .phtml template files.

Blocks objects are meant to interact with the Magento system to retrieve data from Models, while the phtml template files will produce the HTML needed for a page.

For example, the page header Block app/code/core/Mage/Page/Block/Html/Head.php uses the head.phtml file page/html/head.phtml.

Another way of thinking about it is the Block classes are almost like little mini-controllers, and the .phtml files are the view.

By default, when you call

$this->loadLayout();
$this->renderLayout();

Magento will load up a Layout with a skeleton site structure. There will be Structure Blocks to give you your html, head, and body, as well as HTML to setup single or multiple columns of Layout. Additionally, there will be a few Content Blocks for the navigation, default welcome message, etc.

"Structure" and "Content" are arbitrary designations in the Layout system. A Block doesn't programmatically know if it's Structure or Content, but it's useful to think of a Block as one or the other.

To add Content to this Layout you need to tell the Magento system something like

"Hey, Magento, add these additional Blocks under the "content" Block of the skeleton"
or
"Hey, Magento, add these additional Blocks under the "left column" Block of the skeleton"

This can be done programmatically in a Controller action

public function indexAction()
{
    $this
->loadLayout();
    
$block $this->getLayout()->createBlock('adminhtml/system_account_edit')
    
$this->getLayout()->getBlock('content')->append($block);
    
$this->renderLayout();
}

but more commonly (at least in the frontend cart application), is use of the XML Layout system.

The Layout XML files in a theme allow you, on a per Controller basis, to remove Blocks that would normally be rendered, or add Blocks to that default skeleton areas. For example, consider this Layout XML file:

<catalog_category_default>
    <
reference name="left">
        <
block type="catalog/navigation" name="catalog.leftnav" after="currency" template="catalog/navigation/left.phtml"/>
    </
reference>
</
catalog_category_default>

It's saying in the catalog Module, in the category Controller, and the default Action, insert the the catalog/navigation Block into the "left" structure Block, using the catalog/navigation/left.phtml template.

One last important thing about Blocks. You'll often see code in templates that looks like this:
$this->getChildHtml('order_items')

This is how a Block renders a nested Block. However, a Block can only render a child Block if the child Block is included as a nested Block in the Layout XML file. In the example above our catalog/navigation Block has no nested Blocks. This means any call to $this->getChildHtml() in left.phtml will render as blank.

If, however, we had something like:

<catalog_category_default>
    <
reference name="left">
        <
block type="catalog/navigation" name="catalog.leftnav" after="currency" template="catalog/navigation/left.phtml">
            <
block type="core/template" name="foobar" template="foo/baz/bar.phtml"/>
        </
block>
    </
reference>
</
catalog_category_default>

From the catalog/navigation Block, we'd be able to call
$this->getChildHtml('foobar');

Observers

Like any good object-oriented system, Magento implements an Event/Observer pattern for end users to hook into. As certain actions happen during a Page request (a Model is saved, a user logs in, etc.), Magento will issue an event signal.

When creating your own Modules, you can "listen" for these events. Say you wanted to get an email every time a certain customer logged into the store. You could listen for the "customer_login" event (setup in config.xml)

<events>
    <
customer_login>
        <
observers>
            <
unique_name>
                <
type>singleton</type>
                <class>
mymodule/observer</class>
                <
method>iSpyWithMyLittleEye</method>
            </
unique_name>
        </
observers>
    </
customer_login>
</
events>

and then write some code that would run whenever a user logged in:

class Packagename_Mymodule_Model_Observer
{
    
public function iSpyWithMyLittleEye($observer)
    
{
        $data 
$observer->getData();
        
//code to check observer data for out user,
        //and take some action goes here
    
}
}

Class Overrides

Finally, the Magento System offers you the ability to replace Model, Helper and Block classes from the core modules with your own. This is a feature that's similar to "Duck Typing" or "Monkey Patching" in a language like Ruby or Python.

Here's an example to help you understand. The Model class for a product is Mage_Catalog_Model_Product.

Whenever the following code is called, a Mage_Catalog_Model_Product object is created

$product Mage::getModel('catalog/product');

This is a factory pattern.

What Magento's class override system does is allow you to tell the system

"Hey, whenever anyone asks for a catalog/product, instead of giving them a Mage_Catalog_Model_Product,
give them a Packagename_Modulename_Model_Foobazproduct instead".

Then, if you want, your Packagename_Modulename_Model_Foobazproduct class can extend the original product class

class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product
{
}

Which will allow you to change the behavior of any method on the class, but keep the functionality of the existing methods.

class Packagename_Modulename_Model_Foobazproduct extends Mage_Catalog_Model_Product
{
    
public function validate()
    
{
        
//add custom validation functionality here
        
return $this;
    
}

}

As you might expect, this overriding (or rewriting) is done in the config.xml file.

<models>
    <!-- 
does the override for catalog/product-->
    <
catalog>
        <
rewrite>
            <
product>Packagename_Modulename_Model_Foobazproduct</product>
        </
rewrite>
    </
catalog>
</
models>

One thing that's important to note here. Individual classes in your Module are overriding individual classes in other Modules. You are not, however, overriding the entire Module. This allows you to change specific method behavior without having to worry what the rest of the Module is doing.

Wrap Up

We hope you've enjoyed this whirlwind tour of some of the features the Magento eCommerce system offers to developers. It can be a little overwhelming at first, especially if this is your first experience with a modern, object-oriented PHP system. If you start to get frustrated, take a deep breath, remind yourself that this is new, and new things are hard, but at the end of the day it's just a different way of coding. Once you get over the learning curve you'll find yourself loath to return to other, less powerful systems.

Author Bio: Alan Storm is a technology strategist, software developer and writer living and working in Portland, OR. He publishes articles on his website and is the creator of the Magento development extension Commerce Bug.

Discussion:

Explore the Knowledge Base