Magento Controller Dispatch and Hello World

The Model-View-Controller (MVC) architecture traces its origins back to the Smalltalk Programming language and Xerox Parc. Since then there have been many systems that describe their architecture as MVC. Each system is slightly different, but all have the goal of separating data access, business logic and user-interface code from one another.

The architecture of most PHP MVC frameworks will look something like this.

  1. A URL is intercepted by a single PHP file (usually called a Front Controller)
  2. This PHP file will examine the URL, and derive a controller name and an action name (a process that’s often called routing)
  3. The derived controller is instantiated
  4. The method name matching the derived action name is called on the controller
  5. This action method will instantiate and call methods on models, depending on the request variables
  6. The action method will also prepare a data structure of information, which is passed on to the view
  7. The view then renders HTML, using the information in the data structure it has received from the controller

While this pattern was a great leap forward from the “each php file is a page” pattern established early on, for some software engineers, it’s still a hack. Common complaints are

  • The Front Controller PHP file still operates in the global namespace
  • Convention over configuration leads to less modularity.
    • URLs routing is often inflexible
    • Controllers are often bound to specific views
    • Even when a system offers a way to override these defaults, the convention leads to applications where it’s difficult/impossible to drop in new a new model, view, or controller implementation without massive re-factoring.

As you’ve probably guessed, the Magento team shares this world view and has created a more abstract MVC pattern that looks something like this.

  1. A URL is intercepted by a single PHP file
  2. This PHP file instantiates a Magento application
  3. The Magento application instantiates a Front Controller object
  4. Front Controller instantiates any number of Router objects (specified in global config)
  5. Routers check the request URL for a “match”
  6. If a match is found, an Action Controller and Action are derived
  7. Action Controller is instantiated and the method name matching the Action Name is called
  8. This action method will instantiate and call methods on models, depending on the request
  9. This Action Controller will then instantiate a Layout Object
  10. This Layout Object will, based on some request variables and system properties (also known as “handles”), create a list of Block objects that are valid for this request
  11. Layout will also call an output method on certain Block objects, which start a nested rendering (Blocks will include other Blocks)
  12. Each Block has a corresponding Template file. Blocks contain PHP logic, templates contain HTML and PHP output code
  13. Blocks refer directly back to the models for their data. In other words, the Action Controller does not pass them a data structure

We’ll eventually touch on each part of this request, but for now we’re concerned with the Front Controller -> Routers -> Action Controller section

Hello World

Enough theory, it’s time for Hello World. We’re going to

  1. Create a Hello World module in the Magento system
  2. Configure this module with routes
  3. Create Action Controller(s) for our routes

Create Hello World Module

First, as we did before, we’ll create a directory structure for this module

app/code/local/Alanstormdotcom/Helloworld/Block
app/code/local/Alanstormdotcom/Helloworld/controllers
app/code/local/Alanstormdotcom/Helloworld/etc   
app/code/local/Alanstormdotcom/Helloworld/Helper
app/code/local/Alanstormdotcom/Helloworld/Model
app/code/local/Alanstormdotcom/Helloworld/sql

and then a configuration file (config.xml) for the module

PATH: app/code/local/Alanstormdotcom/Helloworld/etc/config.xml
<config>    
    <modules>
        <Alanstormdotcom_Helloworld>
            <version>0.1.0</version>
        </Alanstormdotcom_Helloworld>
    </modules>
</config>   

and a file to activate the module

PATH: app/etc/modules/Alanstormdotcom_Helloworld.xml
<config>
    <modules>
        <Alanstormdotcom_Helloworld>
            <active>true</active>
            <codePool>local</codePool>
        </Alanstormdotcom_Helloworld>
    </modules>
</config>       

Finally, ensure the module is active

  1. Clear your Magento cache
  2. In the Magento Admin, go to
    System->Configuration->Advanced
  3. Expand “Disable Modules Output” (if it isn’t already)
  4. Ensure that Alanstormdotcom_Helloworld shows up

Configuring Routes

Next, we’re going to configure a route. A route will turn a URL into an Action Controller and a method. Unlike other PHP MVC systems, you need to explicitly define a route in the global Magento config.

In your config.xml file, add the following section

<config>    
    ...
    <frontend>
        <routers>
            <helloworld>
                <use>standard</use>
                <args>
                    <module>Alanstormdotcom_Helloworld</module>
                    <frontName>helloworld</frontName>
                </args>
            </helloworld>
        </routers>  
    </frontend>
    ...
</config>

We have a lot of new terminology here

What is a <frontend />?

The <frontend /> tag refers to a Magento Area. For now, think of Areas as individual Magento applications. The “frontend” Area is the public facing Magento shopping cart application. The “admin” Area is the the private administrative console application. The “install” Area is the application you use to run though installing Magento the first time.

Why a <routers /> tags if we’re configuring individual routes?

There’s a famous quote about computer science, often attributed to Phil Karlton

There are only two hard things in Computer Science: cache invalidation and naming things

Magento suffers from the naming problem in spades. You’ll find there are are many places in the global config, and the system in general, where the naming conventions seem unintuitive or even ambiguous. This is one of those places. Sometimes the <routers> tag will enclose configuration information about routers, other times it will enclose configuration information about the actual router objects that do the routing. This is going to seem counter intuitive at first, but as you start to work with Magento more and more, you’ll start to understand its world view a little better. (Or, in the words of Han Solo, “Hey, trust me!”).

What is a <frontName />?

When a router parses a URL, it gets separated as follows

http://example.com/frontName/actionControllerName/actionMethod/

So, by defining a value of “helloworld” in the <frontName> tags, we’re telling Magento that we want the system to respond to URLs in the form of

http://example.com/helloworld/*

Many developers new to Magento confuse this frontName with the Front Controller object. They are not the same thing. The frontNames belong solely to routing.

What’s the <helloworld /> tag for?

This tag should be the lowercase version of you module name. Our module name is Helloworld, this tag is helloworld.

You’ll also notice our frontName matches our module name. It’s a loose convention to have frontNames match the module names, but it’s not a requirement. In fact, a single module could define multiple <routers> and therefore have multiple frontNames.

What’s <module>Alanstormdotcom_Helloworld</module> for?

This module tag should be the full name of your module, including its package/namespace name. This will be used by the system to locate your controller files.

Create Action Controller(s) for our routes

One last step to go, and we’ll have our Action Controller. Create a file at

app/code/local/Alanstormdotcom/Helloworld/controllers/IndexController.php

That contains the following

class Alanstormdotcom_Helloworld_IndexController extends Mage_Core_Controller_Front_Action {        
    public function indexAction() {
        echo 'Hello Index!';
    }
}

Clear your config cache, and load the following URL

http://exmaple.com/helloworld/index/index

You should also be able to load

http://exmaple.com/helloworld/index/
http://exmaple.com/helloworld/

You should see a blank page with the text “Hello World”. Congratulations, you’ve setup your first Magento controller!

Where do Action Controllers go?

Action Controllers should be placed in a module’s controllers (lowercase c) folder. This is where the system will look for them.

How should Action Controllers be named?

Remember the <module> tag back in config.xml?

<module>Alanstormdotcom_Helloworld</module>

An Action Controller’s name will

  1. Start with this string specified in config.xml (Alanstormdotcom_Helloworld)

  2. Be followed by an underscore (Alanstormdotcom_Helloworld_)

  3. Which will be followed by the Action Controller’s name (Alanstormdotcom_Helloworld_Index)

  4. And finally, the word “Controller” (Alanstormdotcom_Helloworld_IndexController)

All Action Controller need Mage_Core_Controller_Front_Action as an ancestor.

What’s that index/index nonsense?

As we previously mentioned, Magento URLs are routed (by default) as follows

http://example.com/frontName/actionControllerName/actionMethod/

So in the URL

http://exmaple.com/helloworld/index/index

the URI portion “helloworld” is the frontName, which is followed by index (The Action Controller name), which is followed by another index, which is the name of the Action Method that will be called. (an Action of index will call the method public function indexAction(){...}.

If a URL is incomplete, Magento uses “index” as the default, which is why the following URLs are equivalent.

http://exmaple.com/helloworld/index/
http://exmaple.com/helloworld/

If we had a URL that looked like this

http://exmaple.com/checkout/cart/add

Magento would

  1. Consult the global config to find the module to use for the frontName checkout (Mage_Checkout)

  2. Look for the cart Action Controller (Mage_Checkout_CartController)

  3. Call the addAction method on the cart Action Controller

Other Action Controller tricks

Let’s try adding a non-default method to our Action Controller. Add the following code to IndexController.php

public function goodbyeAction() {
    echo 'Goodbye World!';
}       

And then visit the URL to test it out.

http://example.com/helloworld/index/goodbye

Because we’re extending the Mage_Core_Controller_Front_Action class, we get some methods for free. For example, additional URL elements are automatically parsed into key/value pairs for us. Add the following method to your Action Controller.

public function paramsAction() {
    echo '<dl>';            
    foreach($this->getRequest()->getParams() as $key=>$value) {
        echo '<dt><strong>Param: </strong>'.$key.'</dt>';
        echo '<dl><strong>Value: </strong>'.$value.'</dl>';
    }
    echo '</dl>';
}

and visit the following URL

http://example.com/helloworld/index/params?foo=bar&baz=eof

You should see each parameter and value printed out.

Finally, what would we do if we wanted a URL that responded at

http://example.com/helloworld/messages/goodbye

Here our Action Controller’s name is messages, so we’d create a file at

app/code/local/Alanstormdotcom/Helloworld/controllers/MessagesController.php

with an Action Controller named

Alanstormdotcom_Helloworld_MessagesController

and an Action Method that looked something like

public function goodbyeAction()         
{
    echo 'Another Goodbye';
}

And that, in a nutshell, is how Magento implements the controller portion of MVC. While it’s a little more complicated than other PHP MVC framework’s, it’s a highly flexible system that will allow you build almost any URL structure you want.

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 August 2, 2009
blog comments powered by Disqus