Categories


Archives


Recent Posts


Categories


Magento’s Class Instantiation Abstraction and Autoload

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

This entry is part 4 of 43 in the series Miscellaneous Magento Articles. Earlier posts include Magento Front Controller, Reinstalling Magento Modules, and Clearing the Magento Cache. Later posts include Magento Development Environment, Logging Magento's Controller Dispatch, Magento Configuration Lint, Slides from Magento Developer's Paradise, Generated Magento Model Code, Magento Knowledge Base, Magento Connect Role Directories, Magento Base Directories, PHP Error Handling and Magento Developer Mode, Magento Compiler Mode, Magento: Standard OOP Still Applies, Magento: Debugging with Varien Object, Generating Google Sitemaps in Magento, IE9 fix for Magento, Magento's Many 404 Pages, Magento Quickies, Commerce Bug in Magento CE 1.6, Welcome to Magento: Pre-Innovate, Magento's Global Variable Design Patterns, Magento 2: Factory Pattern and Class Rewrites, Magento Block Lifecycle Methods, Goodnight and Goodluck, Magento Attribute Migration Generator, Fixing Magento Flat Collections with Chaos, Pulse Storm Launcher in Magento Connect, StackExchange and the Year of the Site Builder, Scaling Magento at Copious, Incremental Migration Scripts in Magento, A Better Magento 404 Page, Anatomy of the Magento PHP 5.4 Patch, Validating a Magento Connect Extension, Magento Cross Area Sessions, Review of Grokking Magento, Imagine 2014: Magento 1.9 Infinite Theme Fallback, Magento Ultimate Module Creator Review, Magento Imagine 2014: Parent/Child Themes, Early Magento Session Instantiation is Harmful, Using Squid for Local Hostnames on iPads, and Magento, Varnish, and Turpentine.

If you’re at all conversant with object oriented programming, the following line should look familiar to you.

$customer_group = new Mage_Customer_Model_Group();

We’re instantiating an instance of the Mage_Customer_Model_Group class. However, if you were to search the Magento codebase for Mage_Customer_Model_Group, you’d never find a single expression that looks anything like the one above. You would, however, see a lot code that looks something like this

$customer_group = Mage::getModel('customer/address');

By the end of this article, you’ll understand the how and why of instantiating classes in Magento, as well as how to include custom Models, Helpers, and Blocks in your own modules.

PHP Autoload

Before we get into the nitty gritty of Magento’s class instantiation abstraction, we need to spend a minute or two talking about PHP include files.

Prior to version 5, PHP didn’t offer much guidance on how project files should be structured. PHP has a simple concept of including or requiring a file. You pass the include or require statement a string that contains a path to another PHP file

include('/path/to/file.php');

and PHP acts as though the code in file.php was part of your main program. There’s no automatic namespacing, linking, packaging or any attempt to reconcile the context of the included files.

Over the years this has led every PHP shop and project to develop their own conventions as to how their base system/library code should be included. If two headstrong developers have a difference of opinion on how this should be accomplished, the resulting codebase quickly becomes a rat’s nest of redundant includes and requires.

While PHP still doesn’t enforce any particular strategy, PHP 5 introduced the concept of autoloading your class files. Developers can create an autoload function that will be called whenever an undefined classes is referenced anywhere in the codebase, with a single argument of the referenced class name.

For example, consider the following simple autoload function

function __autoload($class_name){
    require('lib/'.strToLower($class_name).'.class.php');
}

The team that uses this function stores all its classes in a directory structure that looks something like this

lib/customer.class.php
lib/email.class.php

While not suitable for every project, if used judiciously the autoload concept frees developers from having to care about when and where they include their class files, as well as enforcing/encouraging a consistent naming convention.

Magento Autoload

As you’d imagine, Magento’s module system leverages the autoload feature heavily. While tricky to master, once you understand the naming convention, you’ll know exactly where to find any Magento class is, as well as know where to place your own classes.

You can split all Magento classes into four parts that we’ll call Namespace, Module Name, Class Type, and Name. Consider the class mentioned above

Mage_Customer_Model_Group
Namespace_ModuleName_ClassType_Name
Namespace
A class’s namespace lets you know who’s responsible for creation and maintenance of the class, and helps prevent name collisions between modules. All Magento core modules use the Mage namespace, and the recommended convention is to use a version of your company name for your own modules.
ModuleName
All customization of Magento is done through modules, which are a collection of source code and config files that can be loaded into the Magento system. In the example above, the ModuleName is Customer.
Class Type
Without getting into too much detail, there are several broad categories of classes in Magento, including Model, Singleton, Helper, and Block.
Name
Finally, each class should have a unique name that describes its intended use or function. In the above example, this is Group.

Directory Structure

PHP source files for modules are stored in the app folder. Magento core files are stored separately from local files

//base for core files
app/code/core

//base for your custom and downloaded modules
app/code/local

There’s also a legacy folder named community. You can safely ignore this folder. Varien’s current recommendation is that all non-core modules be stored in the local folder.

So, Magento’s autoload uses the above parts to determine where you’ll find the source for a given class. Starting with the appropriate base folder, source code can be found in the file at

Namespace/ModuleName/ClassType/Name.php

So, the class Mage_Customer_Model_Group may be found in the following folder

app/code/core/Mage/Customer/Model/Group.php

A custom class named Companyname_RoiMagic_Helper_Moneymaker would be found in

app/code/local/Companyname/RoiMagic/Helper/Moneymaker.php

So, you may be wondering what to do with a class like this

Mage_Customer_Model_Address_Config

Magento separates the class name into the same sections, so the class’s “Name” is considered to be Address_Config. However, you will NOT find this class at the following location

//not here
app/code/core/Mage/Customer/Model/Address_Config.php

Instead, underscores in a name tell Magento to go looking for the class in sub-directories, meaning you’d find this source file at

app/code/core/Mage/Customer/Model/Address/Config.php

Abstracting Away Class Instantiation

So, we now know the basic naming convention for a Magento class, but we’re still left with this mystery

$customer_group = Mage::getModel('customer/address');

It’s always important to remember that Magento is more than an application, it’s a programatic system. Whenever they’re creating a new system, engineers are always looking for things to abstract away. With Magento, the Varien engineers have abstracted away the declaration of classes into a series of static “get” method on the global Mage object.

The line of code above is saying, in effect

Give me an instance of the Address Model for the concept of a customer address

Let the word concept percolate in the back of your brain for a bit because we need to take another side trip, this time into the world of config files

Magento Config Files

Every Magento module has a file named config.xml, located in the module’s etc folder. When the Magento system loads a request, all of these config files are merged into one large XML tree.

When you call the various Mage::getModel, Mage::getSingleton, Mage::getBlockSingleton or Mage::getHelper, you’re telling Magento

“Hey, go look in your config tree and tell me what class I should be instantiating for this URI”

The URI is the path-like string you’re passing into these methods. Consider the following method call and config fragment.

$customer_group = Mage::getModel('customer/address'    )
<config>
    <!-- ... -->
    <global>
        <models>
            <customer>
                <class>Mage_Customer_Model</class>
                <resourceModel>customer_entity</resourceModel>
            </customer>
            <!-- ... -->
        </models>
    </global>
    <!-- ... -->
</config>    

When you call getModel, you’re telling Magento to look in the globals/models section of the config file. Then, the first section of the URI tells Magento which child of <models> it should be looking at. In this case that’s customer (customer/address). The <class> node will then give us the base name for our class.

The second section of the URI is used to complete the class name. In this example it is not used to look up anything in the XML config (see rewrite below for an example of where the opposite is true). So in our example, that’s address (customer/address), giving us a final class name of

Mage_Customer_Model_Address

Here’s another example. Consider the following getModel call

Mage::getModel('dataflow/batch_export')

We look in the <models> block of our merged config for a node called dataflow, and we find

<dataflow>
    <class>Mage_Dataflow_Model</class>
    <resourceModel>dataflow_mysql4</resourceModel>
</dataflow>

That gives us a base class name of Mage_Dataflow_Model, which we combine with the second portion of the URI to get a final class name of

Mage_Dataflow_Model_Batch_Export

With your Own Modules

So far we’ve only used getModel with the core Mage modules. However, the same concept applies to your own modules, which is why most Magento tutorials recommend you setup a default <models>, <blocks>, and <helpers> section

The following module config

<global> 
    <models> 
        <roimagic> 
            <class>Companyname_RoiMagic_Model</class> 
        </roimagic> 
    </models> 
</global>

would allow you instantiate an instance of Company_Roimagic_Model_Spam using

Mage::getModel('roimagic/spam');

High Concept

So, that’s a lot of take in. This last bit is the most mind bending of the lot, and the part that makes all this effort worth it. Earlier we talked about the concept of a customer address. With regular class instantiation abstracted away, we’re now free to override (if you went to college before 1999) or monkey-patch (if you went to college after 1999) your Magento system.

Overriding base Magento classes is one of the primary ways you extend Magento. While Magento has an event system for you to hook into, there’s no way the Varien engineers can know what events you might need.

By building an override mechanism into their system, there’s no part of Magento’s Models, Helpers or Blocks you can’t modify if you need to.

Consider the Magento shopping cart class

//getSingleton is identical to getModel, except it ensures only
//one instance of the class is ever created.  i.e. any Magento model
//may be treated as a singleton    
class Mage_Checkout_Model_Cart
$cart = Mage::getSingleton('checkout/cart');

Magento has a basic concept of what a cart is, but your concept might differ. Let’s say the folks we’re building our RoiMagic module for are paranoid about people changing their mind, so they want to log each and every item that gets removed from the cart.

The Cart Model has a method named removeItem. What we’re setting out to do is

  1. Create a new model that will extend the Mage_Checkout_Model_Cart class, but redefine the removeItem method to include our customer logic (while still maintaining previous behavior)

  2. Tell Magento we want to override the standard customer model with our own

So, first thing we’ll do is create a file and stub for our new class called Companyname_RoiMagic_Model_Cart

touch app/code/local/Companyname/RoiMagic/Model/Cart.php
#paste the following code inside Cart.php
class Companyname_RoiMagic_Model_Cart extends Mage_Checkout_Model_Cart{}

Next, we’ll add the new method to our class. This is standard class inheritance at work.

class Companyname_RoiMagic_Model_Cart extends Mage_Checkout_Model_Cart{
    public function removeItem($itemId){
        Mage::Log('Item '.$itemId.' was removed from the cart');
        return parent::removeItem($itemId);
    }
}

Notice we’re returning the results of a call to our parent. This leaves the previous behavior completely unchanged, but still lets us throw in some extra logging. As a general rule, unless you’re intimately familiar with the behavior of the Magento system, you should always attempt to maintain the standard behavior with calls back to the parent method.

So, with our new class in place, the last step is to tell Magento to use our class. When Magento modules want to get an instance of the shopping cart, the call that’s used is

Mage::getSingleton('checkout/cart');

As a reminder, the getSingleton method is identical to the getModel method, except that Magento will instantiate the model once, and then cache the results for later retrieval. What we’re concerned about here in the URI of checkout/cart. That means Magento will be looking in the merged config for a node at

<global>
    <models>
        <checkout>
            <!-- ... -->

So, as part of our module’s config, we’ll want to add a checkout section that contains our rewrite rule

<global>
    <models>
        <!-- standard model section -->
        <roimagic>
            <class>Companyname_RoiMagic_Model</class>
        </roimagic>
        <!-- new checkout section -->
        <checkout>
            <rewrite>                
                <cart>Companyname_RoiMagic_Model_Cart</cart>
            </rewrite>
        </checkout>
    </models>
</global>

So, the <checkout> node is the name of the core Magento module we’re overriding, or the first part of the URI from the call to Mage::getSingleton(‘checkout/cart’).

The <rewrite> node lets Magento know we want to override something in the default customer module. Remember, Magento merges all the config files together, so it’s seeing something more like

<checkout>
    <class>Mage_Checkout_Model</class>
    <resourceModel>checkout_mysql4</resourceModel>          
    <rewrite>                
        <cart>Companyname_RoiMagic_Model_Cart</cart>
    </rewrite>
</checkout>

The presence of the <rewrite> node lets Magento know it shouldn’t assume the class you want is Mage_Checkout_Model_Cart. First it looks at the second part of the URI Mage::getSingleton(‘checkout/cart‘) and looks for a node with that name in the <rewrite> section. If it finds one, it will use the value found there to instantiate the class. In this case, that’s Companyname_RoiMagic_Model_Cart

Wrap-up

Magento is hard, but not impossible. The majority of its difficulty comes from the lack of core-documentation, and a plethora of “give a man a fish” style tutorials. As arbitrary and counter-intuitive as many of the rules seem, there is a logic and a design to the system. Once you understand that logic, you’ll have one of the most customizable, powerful e-commerce systems at your command.

Like this article? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Read more about Magento
Originally published May 9, 2009
Series Navigation<< Clearing the Magento CacheMagento Development Environment >>

Copyright © Alana Storm 1975 – 2023 All Rights Reserved

Originally Posted: 9th May 2009

email hidden; JavaScript is required