Django Routes with Magento and Simple Page

Magento is not designed for URL designers.

When an interactive developer creates a content site, or uses another system (like Magento) to build a content site, the URL structure is as important as the content sitting on the page. Unfortunately, Magento’s routing system doesn’t make this a pleasant or easy task.

When you’re setting up a code module in Magento, (which is how you properly add anything to the system), you need to pick a module “frontname”. This frontname becomes the first part of any URL you end up exposing with your module.

http://example.magento.com/frontname

The second and third part of your URL are driven by the name of your controller file, and its action methods. Something like this

#File: Packagename/Modulename/controllers/TestController.php
class Packagename_Modulename_TestController extends Mage_Core_Controller_Front_Action
{
    public function pageAction()
    {
    }
}

would give you a URL like this

http://example.magento.com/frontname/test/page

There’s two major problems here for URL designers. The first is this scheme forces your into a three/deep/hierarchy approach to your URLs. While you can achieve something shallower using Index controllers and index actions, doing so incurs a confusing amount of mental overhead. Additionally, the three deep URL is still exposed to the world, which exposes your site/application to duplicate content problems.

The other problem is the price of change. If you decide mid-project that a URL should be designed differently, you’re going to impact other parts of the system that use front names, controller names and action names to drive behavior. This means the URL designer needs to take on that work, or burn cycles getting the rest of the team aligned to the change. Inertia almost always wins in these situations and the sub-optimal URL remains.

What about Rewrites?

We’ve previously covered Magento’s rewrite system, which offers another solution for URL designers. Again though, there’s a base level of complexity to the system that reenforces natural inertia. Which URL rewrite system should we use? Rumor has it the configuration based system is considered deprecated, but the model based rewrite system requires transferring database information between environments (always a tricky tasks in interactive production environments).

Also, there’s nothing obvious about either rewrite system. A new developer will default to using the front name, controller name and action name because a database based rewrite stays hidden in the system. Again, inertia wins, and developers inclined towards URL design end up stewing and loathing Magento projects.

This may seem esoteric if you’re not involved with the bleeding edge front-end development community, but I see it time and time again with the interactive agencies I consult with. Because of that, I’ve created the Simple Page extension.

Getting Started with Simple Page

Simple Page provides Django style routing for Magento. It doesn’t replace the existing routing system, but instead focuses on providing developers with an easy alternative to setup URLs for Magento pages. It’s easy to approach for beginners, but also provides access to Magento’s powerful layout features without the need for Layout XML Updates.

Once you’ve installed the Magento Connect package (direct download here), go to the following URL

http://store.example.com/test/page

Assuming you’re using the default theme, you should see a page that looks something like this

If you’re using a different theme, copy the

app/design/frontend/default/default/template/pulsestorm_simplepage

folder to your theme folder.

The above page is the only default route that ships with the extension, but don’t worry. The next section covers adding your own URLs to the system.

URL Configuration in simplepage.xml

Let’s take a look at the existing URL configuration. Open up the following file (we’ve removed some of the comments for easy web viewing)

<!-- File: app/code/community/Pulsestorm/Simplepage/etc/simplepage.xml -->
<config>
    <pulsestorm_simplepage_routes>        
        <!-- this is the minimum configuration for a route -->
        <a_uniquely_identified_node_name_maybe_use_your_package_namespace>
            <url_regex><![CDATA[^test/(?P<foo>page)]]></url_regex>
            <view>hello</view>
        </a_uniquely_identified_node_name_maybe_use_your_package_namespace>    
    </pulsestorm_simplepage_routes>
</config>    

This configuration file contains a single Simple Page url configuration node

<!-- File: app/code/community/Pulsestorm_Simplepage/etc/simplepage.xml -->
<a_uniquely_identified_node_name_maybe_use_your_package_namespace>
    <url_regex><![CDATA[^test/(?P<foo>page)]]></url_regex>
    <view>hello</view>
</a_uniquely_identified_node_name_maybe_use_your_package_namespace>    

At its most basic, a simple page url route is two things: A regular expression that’s matched against an incoming URL (<url_regex/>) and the name a view function (<view/>) to call if the URL matches. The route configured above has a regular expression of

^test/(?P<foo>page)

This says

match any url starting with test/page. If this matches an incoming URL, the view function that’s called is hello

Notice that the URL structure is completely decoupled from the PHP code that’s called. This lets us avoid making a decision on what to do with dashes, underscores, camel case, and all the other URL form edge cases that other PHP systems deal with differently.

Let’s add a view handler for our own custom URL

http://example.magento.com/tutorial-example

To do this, we’d add a new node to the simplepage.xml configuration file

<!-- File: app/code/community/Pulsestorm_Simplepage/etc/simplepage.xml -->
<config>
    <pulsestorm_simplepage_routes>
        <!-- ... -->
        <pulsestorm_tutorial_example> <!-- pulsestorm_tutorial_example
                                           doesn't matter, but needs to be
                                           unique -->

            <url_regex><![CDATA[^tutorial-example$]]></url_regex>
            <view>tutorial</view>
        </pulsestorm_tutorial_example>                
    </pulsestorm_simplepage_routes>
</config>

With the above configuration in place, load up your URL

http://magento1point6point1.dev/tutorial-example

If you’re in developer mode, you’re going to see an error something like this

Warning: call_user_func() expects parameter 1 to be a valid callback, function 'tutorial' not found or invalid function name  in /path/app/code/community/Pulsestorm/Simplepage/Controller/Base.php on line 33

The important bit is function 'tutorial' not found. PHP is yelling at us because we’ve configured this view function without defining it. We’ll be taking care of that next.

View Function

Views are defined in the following file

#File: app/code/community/Pulsestorm/Simplepage/views/simplepage.php    
function hello($block, $layout, $request, $response)
{    
    $block->setTemplate('pulsestorm_simplepage/example.phtml');  
    return $block;
}    

Right now the file contains our hello view function. Let’s add a tutorial function

#File: app/code/community/Pulsestorm_Simplepage/views/simplepage.php    
function tutorial($block)
{
    $block->setTemplate('tutorial-example.phtml');
    return $block;
}

We’ll also need to add a template for this block. In the base of your theme’s template folder, add the following file (this example assumes the default theme’s template folder)

<!-- File: app/design/frontend/default/default/template/tutorial-example.phtml -->
<h1>Tutorial Template</h1>
<p>
    Hello World
</p>

With the above in place, load your URL again, and you should see something like the following

Congratulations, you’ve created your first Simple Page route!

A view function’s responsibility is to return a Magento block. The block returned by a view function will be automatically inserted into the Layout’s content block. In the interest of making this as simple as possible, the first paramater of any view function is a pre-instantiated template block.

function tutorial($block)
{
    $block->setTemplate('tutorial-example.phtml');
    return $block;
}

In the examples above we’ve set a template on the block that was passed in and returned it. However, there’s no reason you need to use that default block. You’re free to instantiate your own. Give the following a try

function tutorial($block, $layout, $request, $response)
{
    $new_block = $layout->createBlock('core/text')
    ->setText('<h1>No Templates Here</h1>');
    return $new_block;
}

The first thing to notice is the full paramater list.

function tutorial($block, $layout, $request, $response)

In addition to passing in an instantiated block, every view function will receive a $layout, $request, and $response variable. The $layout variable contains the Magento layout singleton, while $request and $response contain the global Magento request and response objects.

The above function uses the layout object to create a new text block and return that instead of the passed in template block. At this point you could also use the $layout object to show and hide particular columns, or add a number of different blocks if you want. Anything you normally do in local.xml you can do here via PHP.

Adding to your Own Module

There’s one problem with the examples above: We’re editing files inside the Pulsestorm_Simplepage module itself. When there’s an update for the module, a manual change merge will need to happen (sound familiar?). Fortunately, the Simple Page module is built in a way that lets you apply your routes in any code module.

With Pulsestorm_Simplepage installed, create a new empty module. We’ll call ours Pulsestorm_Helloroute. Then, add both a simplepage.xml and simplepage.php file to your new module

<!-- File: app/code/local/Pulsestorm/Helloroute/etc/simplepage.xml -->
<config>
    <pulsestorm_simplepage_routes>
        <pulsestorm_tutorial_example_two>
            <url_regex><![CDATA[^another-example$]]></url_regex>
            <view>our_view</view>
            <module>Pulsestorm_Helloroute</module>
        </pulsestorm_tutorial_example_two>                    
    </pulsestorm_simplepage_routes>
</config>

 

<!-- File: app/code/local/Pulsestorm/Helloroute/views/simplepage.php -->
<?php       function our_view($block)
    {
        $block->setTemplate('tutorial-example-two.phtml');
        return $block;
    }

 

<!-- File: app/design/frontend/default/default/template/tutorial-example-two.phtml -->
<h1>Another Example</h1>
<p>
    This is a test.
</p>

With the above three files in place, try loading the URL

http://magento.example.com/another-example

You should see something like this

Other than changing a few of the details, there’s two new things with the above code. The first, and most obvious, is we’ve placed our files in a module completely separate from the Simple Page module.

The second is the new <module/> node in simplepage.xml file

<pulsestorm_tutorial_example_two>
    <!-- ... -->
    <module>Pulsestorm_Helloroute</module>
</pulsestorm_tutorial_example_two> 

By including <module>Pulsestorm_Helloroute</module>, we’ll telling Magengto that if this particular URL regular expression matches, the views/simplepage.php file should be loaded from Pulsestorm_Helloroute instead of Pulsestorm_Simplepage.

URL Paramaters

The final thing we’ll cover is URL parameters. Try changing your our_view function so it matches the following

function our_view($block)
{
    $block->setTemplate('tutorial-example-two.phtml');
    var_dump($block->getParams());
    return $block;
}

You should see a data dump at the top of your page something like

array
    0 => string 'another-example'

Simple Page will populate its default block with an array of data parameters. By default, the first parameter is the request path. However, you can add to this array by adding capture parenthesis to your url regular expression. Change <url_regex> to match the following.

<url_regex><![CDATA[^(another)-(example)$]]></url_regex>

Reload your page, and you’ll see a data dump something like this

array
  0 => string 'another-example' (length=15)
  1 => string 'another' (length=7)
  2 => string 'example' (length=7)

Simple Page has passed your captured parenthesis into the block’s parameters data member. You can also also use named parameters

<url_regex><![CDATA[^(?<first>another)-(example)$]]></url_regex>

and your data will include string indexed keys

array
  0 => string 'another-example' (length=15)
  'first' => string 'another' (length=7)
  1 => string 'another' (length=7)
  2 => string 'example' (length=7)

A practical use for this would be setting up an ID grabbing URL, something like this

<url_regex><![CDATA[^magazine-articles/(?<id>\d+)]]></url_regex>        

Wrap Up

While Magento provides you a lot of functionality out of the box, it’s inevitable that you, your clients, or your employers will outgrow the built in Magento content management functionality. Magento CMS is great for content entry, but not ideal for the sort of hand crafted HTML that world class agencies are known for. Simple Page offers you the raw tools you’ll need to quickly setup and build out pages in your Magento system without diving deep into the module configuration.

This first release is stable, but only a beta. The module’s been submitted for review in Magento Connect, so keep an eye on Twitter for an announcement, or download the extension here. Feedback from other software engineers or interactive developers is more than welcome. Either use the comment below or get in touch directly.

Originally published October 31, 2011
blog comments powered by Disqus