- Magento Front Controller
- Reinstalling Magento Modules
- Clearing the Magento Cache
- Magento’s Class Instantiation Abstraction and Autoload
- 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
- 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
- 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) - 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.