<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
><channel><title>A.J. Brown&#039;s Blog &#187; Zend Framework</title> <atom:link href="http://ajbrown.org/blog/tags/zend-framework/feed" rel="self" type="application/rss+xml" /><link>http://ajbrown.org/blog</link> <description>Coding adventures and technology musing for the masses</description> <lastBuildDate>Fri, 26 Mar 2010 17:57:50 +0000</lastBuildDate> <language>en</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <generator>http://wordpress.org/?v=3.0.1</generator> <item><title>Controlling Access with Zend Framework&#039;s Controller Plugins</title><link>http://ajbrown.org/blog/2010/03/20/controlling-access-with-zend-frameworks-controller-plugins.html</link> <comments>http://ajbrown.org/blog/2010/03/20/controlling-access-with-zend-frameworks-controller-plugins.html#comments</comments> <pubDate>Sat, 20 Mar 2010 18:56:53 +0000</pubDate> <dc:creator>A.J. Brown</dc:creator> <category><![CDATA[PHP]]></category> <category><![CDATA[Zend Framework]]></category> <category><![CDATA[Zend_Auth_Identity]]></category> <category><![CDATA[Zend_Controller_Plugin]]></category><guid isPermaLink="false">http://ajbrown.org/blog/?p=219</guid> <description><![CDATA[A common problem with developing web applications is controlling user access to certain sections or single pages. Zend Framework's Controller Plugin system provides a clean and efficient way to manage these checks, without muddling up your Controllers. Controller plug-ins are an extension of Zend_Controller_Plugin_Abstract. They allow you to modify the request (or perform any other [...]]]></description> <content:encoded><![CDATA[<p>A common problem with developing web applications is controlling user access to certain sections or single pages.  Zend Framework's Controller Plugin system provides a clean and efficient way to manage these checks, without muddling up your Controllers.</p><p>Controller plug-ins are an extension of <a href="http://framework.zend.com/apidoc/core/Zend_Controller/Plugins/Zend_Controller_Plugin_Abstract.html">Zend_Controller_Plugin_Abstract</a>.  They allow you to modify the request (or perform any other action) during any point in the <a href="http://bit.ly/9wzwei">Zend Framework request life-cycle</a>.  For more information about controller plug-ins themselves, visit the <a href="http://framework.zend.com/manual/en/zend.controller.plugins.html">Zend Framework manual</a>.</p><p>Lets start out with the controller plugin itself.  The first thing we need to know is what point(s) of the request process lifecycle we should be operating on.  In our case, we need to know what controller and action the end user is requesting, so all routing must have occurred.  I chose to use preDispatch() because it ensures that an optimal amount of plugins have had their opportunity to make changes to the request, and gives us the highest chance of checking access to the final destination.  We can also catch any forwards from our action controller by performing the check here.</p><pre class="brush: php;">
&lt;?php
/**
 * A front controller plugin to check the current user against an access control list.
 *
 * @author A.J. Brown
 * @category Examples
 * @package Application_Controller
 * @subpackage Plugin
 * @version $Id$
 *
 */
class Application_Controller_Plugin_CheckHasAccess
    extends Zend_Controller_Plugin_Abstract
{

    public function preDispatch( Zend_Controller_Request_Abstract $request )
    {
        $isLoggedIn = Zend_Auth::getInstance()-&gt;hasIdentity();

        // Save some cycles if we're already logged in
        if( $isLoggedIn ) {
             return;
        }

 	$config     = $this-&gt;_getConfig();
        $action     = $request-&gt;getParam( 'action' );
        $controller = $request-&gt;getParam( 'controller' );

        // Make sure we don't end up in a loop
        if( $controller == $config-&gt;loginController
            &amp;&amp; $action == $config-&gt;loginAction )
        {
            return;
        }

        $secure = $this-&gt;_checkIsSecure(
			$request-&gt;getParam( 'action' )
			, $request-&gt;getParam( 'controller' )
		);

        if( $secure ) {
            $request-&gt;setParam( 'ref', $request-&gt;getPathInfo() );
            $request-&gt;setControllerName( $config-&gt;loginController );
            $request-&gt;setActionName( $config-&gt;loginController );
            $request-&gt;setDispatched( false );
        }
    }

    /**
     * Load the configuration.
     *
     * @return Zend_Config_Ini
     */
    protected function _getConfig()
    {
        static $config = null;
        if( null === $config ) {
            $config = new Zend_Config_Ini(
                APPLICATION_PATH . '/configs/access.ini' , 'global' );
        }
        return $config;
    }

    protected function _checkIsSecure( $action, $controller, $module = 'default' )
    {
        $config = $this-&gt;_getConfig();

        // If no match is found, what should be the default?
        $public = ( isset( $config-&gt;defaultAccess ) &amp;&amp; $config-&gt;defaultAccess == 'public' );

        // Check the action level, then controller
        if( isset( $config-&gt;controllers-&gt;$controller-&gt;actions-&gt;$action-&gt;access ) ) {
            $public = ( $config-&gt;controllers-&gt;$controller-&gt;actions-&gt;$action-&gt;access == 'public' );
        } elseif( isset( $config-&gt;controllers-&gt;$controller-&gt;access ) ) {
            $public = ( $config-&gt;controllers-&gt;$controller-&gt;access == 'public' );
        }

        return !$public;
    }
}
</pre><p>As you can see, the plug-in makes use of an ini configuration file to determine if a given controller and action is public or not.  If a setting for the action doesn't exist, we fall back to the controller's setting.  If the controller doesn't have a setting, we use whatever the default is.</p><p>Lets take a look at the configuration file.</p><pre class="brush: plain; wrap-lines: false;">
[global]

defaultAccess = &quot;private&quot;
loginController = &quot;index&quot;
loginAction = &quot;login&quot;

controllers.index.acccess = &quot;public&quot;
controllers.index.actions.profile.access = &quot;private&quot;

controllers.account.access = &quot;private&quot;
controllers.account.actions.confirm = &quot;public&quot;
</pre><p>In this configuration, all routes will be private unless they're specifically made private.  All of our actions within the "index" controller will be public, except for the "profile" action.  For the "account" controller, only the "confirm" action will be public.</p><p>The last step is making sure our plugin is registered with the front controller.  If we forget this part, our plug-in will never have a chance to intercept the request.  Registering can be done anywhere you want and at any point during the request process.  In fact, your plug-ins can even register other plug-ins.  The easiest and most common way is by adding an entry in our application.ini file for the FrontController resource.</p><pre class="brush: plain; highlight: [3]; wrap-lines: false;">
resources.frontController.controllerDirectory = APPLICATION_PATH &quot;/controllers&quot;
resources.frontController.params.displayExceptions = 0
resources.frontController.plugins.CheckHasAcess = &quot;Application_Controller_Plugin_CheckHasAccess&quot;
</pre><h2 id="toc-conclusion">Conclusion</h2><p>The nice part about designing an access control system using Zend Framework's controller plug-in system is that our code is separated from our controller code. Using this system, a developer doesn't necessarily have to concern himself with modifying the system.  Any new controllers that are added will automatically use the system without any additional code, and permissions can be changed quickly simply by modifying a configuration file.</p><p>Happy Coding!</p> ]]></content:encoded> <wfw:commentRss>http://ajbrown.org/blog/2010/03/20/controlling-access-with-zend-frameworks-controller-plugins.html/feed</wfw:commentRss> <slash:comments>3</slash:comments> </item> <item><title>Zend_Service_PayPal Proposal Back on Track</title><link>http://ajbrown.org/blog/2009/02/03/zend_service_paypal-proposal-back-on-track.html</link> <comments>http://ajbrown.org/blog/2009/02/03/zend_service_paypal-proposal-back-on-track.html#comments</comments> <pubDate>Tue, 03 Feb 2009 19:01:37 +0000</pubDate> <dc:creator>A.J. Brown</dc:creator> <category><![CDATA[PHP]]></category> <category><![CDATA[Zend Framework]]></category> <category><![CDATA[Zend_Service_PayPal]]></category><guid isPermaLink="false">http://ajbrown.org/blog/?p=192</guid> <description><![CDATA[I just wanted to drop a quick note letting everyone know that after nearly 2 years of dormancy, Zend_Service_PayPal is back in the works. Shahar Evron has been too busy with his work over at Zend Technologies to continue work on the proposal, so I've volunteered my time to get it back on track. If [...]]]></description> <content:encoded><![CDATA[<p>I just wanted to drop a quick note letting everyone know that after nearly 2 years of dormancy, Zend_Service_PayPal is back in the works. <a href="http://prematureoptimization.org/blog/">Shahar Evron</a> has been too busy with his work over at Zend Technologies to continue work on the proposal, so I've volunteered my time to get it back on track.</p><p>If you're interested in seeing this component in Zend Framework soon, please follow progress at the <a href="http://framework.zend.com/wiki/display/ZFPROP/Zend_Service_PayPal+-+A.J.+Brown">Zend Framework Contributors Wiki</a>, This blog, and on the <a href="http://github.com/ajbrown/zend_service_paypal" alt="GitHub Zend_Service_Paypal">project page at GitHub</a>.  Any feedback, suggestions, and patches you can provide will be nothing but useful.  Send me an email, comment on the wiki, or This proposal is still in it's early stages, but I hope to be knocking out a lot of the work in the next couple of weeks.</p> ]]></content:encoded> <wfw:commentRss>http://ajbrown.org/blog/2009/02/03/zend_service_paypal-proposal-back-on-track.html/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Automated Testing Using Zend Framework, Part 1</title><link>http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html</link> <comments>http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#comments</comments> <pubDate>Sun, 04 Jan 2009 20:05:41 +0000</pubDate> <dc:creator>A.J. Brown</dc:creator> <category><![CDATA[PHP]]></category> <category><![CDATA[PHPUnit]]></category> <category><![CDATA[Testing]]></category> <category><![CDATA[Zend Framework]]></category> <category><![CDATA[Zend_Test]]></category><guid isPermaLink="false">http://ajbrown.org/blog/?p=157</guid> <description><![CDATA[Automated testing for your web applications is an important step in having the confidence to make changes to your application, and still be confident you're delivering a quality, regression-free product.  With Zend Framework's testing framework, you can build a thorough suite of test cases for your web application with very little hassle.]]></description> <content:encoded><![CDATA[<div class="toc"><ol><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-preparing-your-application">Preparing Your Application</a></li><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-a-basic-example">A Basic Example</a></li><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-running-your-tests">Running Your Tests</a></li><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-extending-the-testing-functionality">Extending the Testing Functionality</a><ol><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-disposable-testing-models">Disposable Testing Models</a></li><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-authentication-support">Authentication Support</a></li><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-putting-it-all-together">Putting It All Together</a></li></ol></li><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-writing-our-controller-test">Writing Our Controller Test</a></li><li><a href="http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html#toc-conclusion">Conclusion</a></li></ol></div><p>Automated testing for your web applications is an important step in having the confidence to make changes to your application, and still be confident you're delivering a quality, regression-free product.  With Zend Framework's testing framework (built with <a href="http://phpunit.de">PHPUnit</a>), you can build a thorough suite of test cases for your web application with very little hassle.</p><p>Part 1 will give you all of the basic information you need to start writing automated tests for your Zend Framework applications.   In Part 2, I'll provide more real-world example covering some other ways to write tests.</p><p>Lets get right to it.</p><p>For the examples below, I will be using an actual controller from one of my projects.  This controller handles account activies, such as logins, logouts, registrations, and confirmations.  We'll be using a test database with a schema that clones our production database, and Doctrine to manage ORM (Sorry Zend_Db <img src='http://ajbrown.org/blog/wp-includes/images/smilies/icon_sad.gif' alt=':(' class='wp-smiley' /> ).  I'll assume that you're using the prescribed Zend Framework (1.6+) project layout, and that you're somewhat familiar with <a href="http://framework.zend.com/manual/en/zend.config.html" rel="nofollow">Zend_Config</a> and using an <a href="http://devzone.zend.com/article/3372-Front-Controller-Plugins-in-Zend-Framework">Initializer controller plugin</a> (created by default if you're using Zend Studio for Eclipse 6.1).</p><h3 id="toc-preparing-your-application">Preparing Your Application</h3><p>The first step to setting up automated testing is to prepare your application's environment and settings appropriately.  Depending no your setup, this can involve setting global variables, changing database connections, or reconfiguring paths.  Fortunately for us, this capability is easy using Zend_Config and an Initializer controller plugin.</p><p>Zend_Config allows you to specify "sections", which can inherit from another section.  This allows you to modify configuration for different environments without duplicating settings across different files (and thereby helping us ensure we don't forget to set something!).  In our example project, we'll need only to modify our database connection string, so we're using the test database.</p><pre class="brush: xml;">
&lt; ?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;config&gt;

&lt;production&gt;
  &lt;db&gt;
	&lt;dsn&gt;mysql://dbowner:password@localhost/maindb&lt;/dsn&gt;
	&lt;attributes&gt;
		&lt;model_loading&gt;conservative&lt;/model_loading&gt;
	&lt;/attributes&gt;
  &lt;/db&gt;
&lt;/production&gt;
&lt;test extends=&quot;production&quot;&gt;
  &lt;db&gt;
	&lt;dsn&gt;mysql://dbowner:password@localhost/maindb_test&lt;/dsn&gt;
  &lt;/db&gt;
&lt;/test&gt;
&lt;/config&gt;
</pre><p><sup>Notice how we can even inherit child attributes:  Even though we specified a a <db> node, we didn't have to specify everything below the </db><db> node</db></sup></p><p>Now that we have our configurations in order, we need something to manage this configuration for us, and switch off based on the environment we're running in.  That's the role of our Initializer plugin, which accepts the environment to initialize as a constructor parameter.  Showing the source of an Initializer is outside the scope of this article, but <a href="http://pastie.org/352285" rel="nofollow">here's a pastie for the curious</a>.</p><h3 id="toc-a-basic-example">A Basic Example</h3><p>Lets start with the basic framework of a controller test.  If you're using Zend Studio for Eclipse, you can easily create this structure by right-clicking on your controller in PHP Explorer and selecting<cite>New > Zend Framework Item</cite>, and then selecting<cite>Zend Controller Test Case</cite>.  Then, simply make sure the controller you want to test is chosen, and click finish.</p><pre class="brush: php;">
require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';
require_once 'application/Initializer.php';
require_once 'application/default/controllers/IndexController.php';

class AccountControllerTest extends Zend_Test_PHPUnit_ControllerTestCase {

	/**
	 * Prepares the environment before running a test.
	 */
	protected function setUp() {
		$this-&gt;bootstrap = array ($this, 'appBootstrap' );
		parent::setUp ();
		// TODO Auto-generated FooControllerTest::setUp()
	}

	/**
	 * Prepares the environment before running a test.
	 */
	public function appBootstrap() {
		$this-&gt;frontController-&gt;registerPlugin ( new Initializer( 'test' ) );
	}

	/**
	 * Cleans up the environment after running a test.
	 */
	protected function tearDown() {
		// TODO Auto-generated FooControllerTest::tearDown()
		parent::tearDown ();
	}

	/**
	 * Constructs the test case.
	 */
	public function __construct() {
		// TODO Auto-generated constructor
	}

	/**
	 * Tests FooController-&gt;barAction()
	 */
	public function testIndexAction() {
		// TODO Auto-generated FooControllerTest-&gt;testBarAction()
		$this-&gt;dispatch ( '/index/index' );
		$this-&gt;assertController ( 'index' );
		$this-&gt;assertAction ( 'index' );
	}
}
</pre><p>You'll see on <strong>line 20</strong> where we're using our initializer to setup the test environment before we run the tests. Before each test metod is run, PHPUnit will call our<cite>setup()</cite> method, which has been programmed to call our appBootstrap method.  This ensures us that we're using a clean configuration and environment before each test, as if each test was a separate process.  When each test case is done, the<cite>tearDown()</cite> method is called.<cite>tearDown()</cite> is the place to put any code for removing resources or resetting any persistable changes that tests might make.  We'll make use of this in our advanced examples later.</p><p><strong>Line 41</strong> contains a bare-bones test case, which will ensure that dispatching to '/index/index' results in the controller named 'index' and an action called 'index' are the last to be executed.  This might seem trivial, but it helps detect errors with your controllers.  If an uncaught exception is thrown, the controller assertion will fail, since your controller will be the last executed.</p><h3 id="toc-running-your-tests">Running Your Tests</h3><p>To keep this article focused, I've decided to remove this section and cover only writing the tests.  If you need help creating Test Suites and running tests from the command line, check out <a rel="nofollow" href="http://www.phpunit.de/manual/3.3/en/">PHPUnit Documentation</a>, specifically the sections on <a rel="nofollow" href="http://www.phpunit.de/manual/3.3/en/textui.html">the Command Line Test Runner</a> and <a rel="nofollow" href="http://www.phpunit.de/manual/3.3/en/organizing-test-suites.html">Organizing Test Suites</a>.  If you have any specific questions, feel free to shoot me an email.</p><h3 id="toc-extending-the-testing-functionality">Extending the Testing Functionality</h3><p>Now that we covered the basics, lets get to fully testing our Accounts controllers.  There are a couple of "outside the box" requirements we have for testing our accounts controller.  First off, we need a way to test the full account creation process, as if a user was actually registering.  When we're done with a test, we want to get rid of that data whenever possible, so we can run tests as many times as we want and not worry about growing our test database.  Secondly, We need a way to simulate an authenticated user, as well as checking for whether a user has been authenticated or not.</p><p>Because these operations are pretty general and an opportunity for reuse exists, Lets put our supporting logic in a parent class and let our test cases inherit them.</p><h4 id="toc-disposable-testing-models">Disposable Testing Models</h4><p>There are two ways to ensure the data you're creating during a test is deleted once a test is complete.  Some choose to create the database on the fly using seed data.  Since I'm using Doctrine for my projects and working directly with models (no raw queries) in the tests, I decided that just deleting the data was the best approach.  To do this, all we need to do is "schedule" our model for deletion after it has been created (or loaded).</p><pre class="brush: php;">
protected function _setDisposable( Doctrine_Record $model )
{
    $this-&gt;_disposables[] = $model;
}
</pre><p>This function simply takes a reference to a model, and stores it in array, which will be handled by our tearDown() function later:</p><pre class="brush: php;">
	protected function tearDown()

	{
	    parent::tearDown();

	    foreach ( $this-&gt;_disposables as $model ) {
	        if ( $model instanceof Doctrine_Record ) {
	            $model-&gt;delete();
	        }
	        unset( $model );
	    }
	}
</pre><p>We simply loop through all of our "scheduled" models and delete them.  This must be done in tearDown() and not inside of any test method because it's the only way to ensure it happens.  Once an assertion fails or an unexpected exception occurs in a test, that method stops executing.  IF we were to try to dispose our models after an assertion, it may never happen<sup class='footnote'><a href='#fn-157-1' id='fnref-157-1'>1</a></sup>.   Similarly, we obviously can't dispose of a model <em>before</em> an assertion if that model is needed for the assertion (and why would it exist if you didn't need it?).</p><h4 id="toc-authentication-support">Authentication Support</h4><p>There are 3 things we must be able to do in order to fully test authentication.</p><ul><li>Create a fake identity</li><li>Set our environment to a state equivalent to a user being logged in.</li><li>Assert whether the environment has been changed to a logged in state.</li></ul><p>Our example controller uses a database adapter for authentication and identity retrieval<sup class='footnote'><a href='#fn-157-2' id='fnref-157-2'>2</a></sup>, so generating a fake identity for us means creating (or loading) a record in our accounts table, and returning the identity data we would normally get.</p><pre class="brush: php;">
/**
 * Generates a fake identity, usefull for simulating a logged in user
 *
 * @return StdClass an identity
 */
protected function _generateFakeIdentity()
{
	$identity = new stdClass();

        $account = new Account();
        $account-&gt;username     = 'AutoTest' . time();
        $account-&gt;emailAddress = 'autotest@example.org';
        $account-&gt;password     = md5( 'password' );
        $account-&gt;confirmed    = true;
        $account-&gt;enabled      = true;
        $account-&gt;save();
        $this-&gt;_setDisposable( $account );

	foreach( $account-&gt;toArray() as $key =&gt; $val ) {
	        $identity-&gt;$key = $val;
	}
	unset( $identity-&gt;password );

	return $identity;
}
</pre><p><cite>Account</cite> is our model, a<cite>Doctrine_Record</cite> type.  We're just simply creating a random account, and returning it's data as our idenity.  Notice that we're also scheduling this model for disposal (as we covered above.) Now, we just need a way to set our environment to the "logged in state" as this fake user.</p><pre class="brush: php;">
/**
 * Sets the current state as if there is a logged in user
 *
 * @param object $identity the idenity to use, otherwise one is generated
 * @return void
 */
protected  function _doLogin( $identity = null )
{
    if ( $identity === null ) {
        $identity = $this-&gt;_generateFakeIdentity();
    }
    Zend_Auth::getInstance()-&gt;getStorage()-&gt;write( $identity );
}
</pre><p>In our example application, if <a rel="nofollow" href="http://framework.zend.com/manual/en/zend.auth.html">Zend_Auth</a> has an identity, a user must be logged in.  Therefore, all we have to do is store an identity in Zend_Auth's storage adapter, and call ourselves logged in.  That makes asserting login as simple as checking for an identity.</p><pre class="brush: php;">
public function assertNotLoggedIn()
{
    $this-&gt;assertFalse( Zend_Auth::getInstance()-&gt;hasIdentity(), 'Login assertion failed' );
}

public function assertLoggedIn()
{
    $this-&gt;assertTrue( Zend_Auth::getInstance()-&gt;hasIdentity(), 'Login assertion failed' );
}
</pre><p>These simple assertions <sup class='footnote'><a href='#fn-157-3' id='fnref-157-3'>3</a></sup> ensure that we're logged in (or not logged in)</p><h4 id="toc-putting-it-all-together">Putting It All Together</h4><p>Putting it all together, we now have a base class which provides all of our test cases with the functionality we need.</p><pre class="brush: php;">
&lt; ?php

require_once 'Zend/Test/PHPUnit/ControllerTestCase.php';

class BaseControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{

    /**
     * Contains models which should be destroyed on tear down
     *
     * @var array
     */
    protected $_disposables = array();

	protected function tearDown()
	{
	    parent::tearDown();

	    foreach ( $this-&gt;_disposables as $model ) {
	        if ( $model instanceof Doctrine_Record ) {
	            $model-&gt;delete();
	        }
	        unset( $model );
	    }
	}

	/**
	 * Sets a model as disposable, so teardown automatically deletes it
	 *
	 * @param Doctrine_record $model
	 */
	protected function _setDisposable( Doctrine_record $model )
	{
	    $this-&gt;_disposables[] = $model;
	}

	/**
	 * Sets the current state as if there is a logged in user
	 *
	 * @param object $identity the idenity to use, otherwise one is generated
	 * @return void
	 */
	protected function _doLogin( $identity = null )
	{
	    if ( $identity === null ) {
	        $identity = $this-&gt;_generateFakeIdentity();
	    }

	    Zend_Auth::getInstance()-&gt;getStorage()-&gt;write( $identity );
	}

	/**
	 * Generates a fake identity, usefull for simulating a logged in user
	 *
	 * @param boolean $unique
	 * @return StdClass an identity
	 */
	protected function _generateFakeIdentity( $unique = false )
	{
		$identity = new stdClass();

		$account = new Account();
	        $account-&gt;username     = 'AutoTest' . time();
	        $account-&gt;emailAddress = 'autotest' . time() . '@example.org';
	        $account-&gt;password     = md5( 'password' );
	        $account-&gt;confirmed    = true;
	        $account-&gt;enabled      = true;
	        $account-&gt;save();
	        $this-&gt;_setDisposable( $account );

	    	foreach( $account-&gt;toArray() as $key =&gt; $val ) {
	        	$identity-&gt;$key = $val;
	    	}
	    	unset( $identity-&gt;password );

	    	return $identity;
	}

	public function assertNotLoggedIn()
	{
	    $this-&gt;assertFalse( Zend_Auth::getInstance()-&gt;hasIdentity(), 'Login assertion failed' );
	}

	public function assertLoggedIn()
	{
	    $this-&gt;assertTrue( Zend_Auth::getInstance()-&gt;hasIdentity(), 'Login assertion failed' );
	}

}
</pre><h3 id="toc-writing-our-controller-test">Writing Our Controller Test</h3><p>Now that our groundwork has been laid, we can finally start writing our controller test.</p><p>Our first set of test cases cover the requirement<cite>"when a user registers, they must confirm their email address before they can access their account"</cite>.  To do this, we need to simulate a user posting their valid registration details to our controller, and verify that the account created is not set as confirmed.  We then need to test that submitting login information for a non-confirmed account to our login action does not result in an authenticated user.</p><pre class="brush: php;">
public function testRegisterCreatesNewUnconfirmedAccount()
{
    $email = 'autotest' . time() . '@example.org';
    $data = array(
        'emailAddress'    =&gt; $email,
        'password'		  =&gt; 'testpassw0rd',
        'passwordconfirm' =&gt; 'testpassw0rd'
    );

    $_POST = $data;

    $this-&gt;dispatch( '/account/register' );
    //try to find the account record
    $table = Doctrine_Table::create( 'Account' ) ;
    $account = $table-&gt;findOneByEmailAddress( $email );
    $this-&gt;_setDisposable( $account );
    $this-&gt;assertNotNull( $account );
    $this-&gt;assertFalse( $account-&gt;confirmed, 'Account was not marked as unconfirmed' );
}

/**
 * Asserts that a user that hasn't been confirmed cannot login
 *
 */
public function testUnconfirmedUserCannotLogin()
{
    $email = 'autotest' . time() . '@example.org';

    $account = new Account();
    $account-&gt;username     = $email;
    $account-&gt;password     = md5( 'password' );
    $account-&gt;emailAddress = $email;
    $account-&gt;confirmed    = false;
    $account-&gt;enabled      = true;
    $account-&gt;save();

    $this-&gt;_setDisposable( $account );

    $_POST['username'] = $email;
    $_POST['password'] = 'password';

    $this-&gt;dispatch( '/account/login' );
    $this-&gt;assertFalse( Zend_Auth::getInstance()-&gt;hasIdentity() );
    $this-&gt;assertNotRedirect();
}
</pre><p>Our first test simply uses the $_POST global variable to simulate submitting our registration form with some test data.  After dispatch(), we use Doctrine_Table to find the model created by AccountController::registerAction(), then assert that the record was found and that it was not marked as confirmed.</p><p>The second test works by manually inserting a record that's not confirmed, and making sure no user is authorized when attempting to login with that user's account information.  As an added bonus, we also use<cite>assertNotRedirect()</cite> to make sure our controller didn't redirect.  Our controller should only redirect if login was successful, otherwise it would be confusing to a user.</p><h3 id="toc-conclusion">Conclusion</h3><p>Automated testing of your controllers is relatively simple using the combined power of PHPUnit and Zend Framework's Zend_Test component.  We can add additional functionality to allow our tests to simulate authentication, create fake identities, and even clean up our database after our tests have run.  I showed you how you can put this all together to test a registration and confirmation process in your controllers.</p><p>In part 2, I'll cover more areas of testing in our AccountsController, including testing our actions that require an authorized user in order to access them.</p><div class='footnotes'><div class='footnotedivider'></div><ol><li id='fn-157-1'>Of course, if tearDown() is never run for some reason the models won't be deleted either, but we have more control over this.  A third way that isn't discussed is creating an on_shutdown procedure, but that seems a little overkill <span class='footnotereverse'><a href='#fnref-157-1'>&#8617;</a></span></li><li id='fn-157-2'>I'm actually using ZendX_Doctrine_Auth_Adapter from the extras incubator <span class='footnotereverse'><a href='#fnref-157-2'>&#8617;</a></span></li><li id='fn-157-3'>Taknig this further, we should actually create new PHPUnit criteria instead of wrapping the assertTrue() criteria so that our failure message is different.  That's something for later, though <span class='footnotereverse'><a href='#fnref-157-3'>&#8617;</a></span></li></ol></div> ]]></content:encoded> <wfw:commentRss>http://ajbrown.org/blog/2009/01/04/automated-testing-using-zend-framework-part-1.html/feed</wfw:commentRss> <slash:comments>12</slash:comments> </item> <item><title>Memcached in PHP Made Easy With Zend Framework</title><link>http://ajbrown.org/blog/2008/12/24/memcached-in-php-made-easy-with-zend-framework.html</link> <comments>http://ajbrown.org/blog/2008/12/24/memcached-in-php-made-easy-with-zend-framework.html#comments</comments> <pubDate>Wed, 24 Dec 2008 15:00:12 +0000</pubDate> <dc:creator>A.J. Brown</dc:creator> <category><![CDATA[PHP]]></category> <category><![CDATA[Memcached]]></category> <category><![CDATA[Zend Framework]]></category> <category><![CDATA[Zend_Cache]]></category><guid isPermaLink="false">http://ajbrown.org/blog/?p=51</guid> <description><![CDATA[Implementing Memcached to improve your application's performance can be done fairly quickly without using Zend Framework.  If you're just wanting Memcached, skip Zend Framework (and probably this post).  On the other hand, if you're using Zend Framework but you're not using Memcached, this will help you get things going.  But whatever your situation: If you're not already using Memcached, start tomorrow.]]></description> <content:encoded><![CDATA[<p>After my previous gang beating on my (obviously wrong) take on CakePHP a few days ago, I'm going to start this new post off with a disclaimer.  Implementing <a rev="vote-for" href="http://www.danga.com/Memcached">Memcached</a> to improve your application's performance can be done fairly quickly without using <a rev="vote-for" href="http://framework.zend.com">Zend Framework</a>.  If you're just wanting Memcached, skip Zend Framework (and probably this post).  On the other hand, if you're using Zend Framework but you're not using Memcached, this will help you get things going.  But whatever your situation:</p><blockquote><p>If you're not already using Memcached, Start tomorrow.</p></blockquote><h4 id="toc-what-is-memcached">What is Memcached</h4><p>Taken straight from the Memcached website, "Memcached is a high-performance, distributed memory object caching system".  In other words, Memcached helps you increase application perfomance by caching data in memory.  Using Memcached to load data instead of a database or file system can have a major impact on your applications performance.  (Insert meaningless benchmark term such as "ten-fold" here.)  The best part is these benefits only increase as load increases, so you get increase scalability for free.</p><p>For more information on Memcached, you should visit the <a rev="vote-for" href="http://www.danga.com/Memcached">Danga Memcached</a>.</p><h4 id="toc-a-quick-overview-of-zend_cache">A Quick Overview of Zend_Cache</h4><p>Before continuing down the Memcached path in ZF, it's important to get a picture of how caching is implemented with <code>Zend_Cache</code>.  With <code>Zend_Cache</code>, you must configure a frontend "strategy" (Zend_Cache_Frontend) and a backend "engine" (<code>Zend_Cache_Backend</code>).  Each caching object must have a backend and a frontend.</p><p>The backend engine is exactly what you think it is:  the method that <code>Zend_Cache</code> uses to actually store cached data.  Zend Framework currently supports File, SqlLite, XCache, Memcached, Apc, and Zend Platform as backends.  There's another backend called "TwoLevels", which stores data in either a fast backend or a slow backend depending on priority. (I admittedly am not completely familiar with how this works or why you would use it.)</p><p>The frontend strategy determines both how expiration is handled, and how you implement caching.  For example, you can implement <code>Zend_Cache_Frontend_File</code> to cache data until a certain file is changed.  A real-world example of this is caching an instance of <code>Zend_Config_Xml</code> until the XML file it points to is changed.  The most basic frontend strategy is <code>Zend_Cache_Core</code>, which basically says "tell me what to cache, and tell me when it should expire". <code>Zend_Cache_Core</code> is what we'll use in our example.</p><h4 id="toc-implementing-with-zend-framework">Implementing With Zend Framework</h4><p>The first thing we need to do is setup our caching object.  As you guessed, Zend Framework implements Memcached through <code>Zend_Cache_Backend_Memcached</code>.  I found Zend_Cache_Core to be the most useful for a first run in my applications, so I suggest you start there as well.</p><pre class="brush: php;">

// configure caching backend strategy
$oBackend = new Zend_Cache_Backend_Memcached(
	array(
		'servers' =&gt; array( array(
			'host' =&gt; '127.0.0.1',
			'port' =&gt; '11211'
		) ),
		'compression' =&gt; true
) );

// configure caching logger
$oCacheLog =  new Zend_Log();
$oCacheLog-&gt;addWriter( new Zend_Log_Writer_Stream( 'file:///tmp/pr-memcache.log' ) );

// configure caching frontend strategy
$oFrontend = new Zend_Cache_Core(
	array(
		'caching' =&gt; true,
		'cache_id_prefix' =&gt; 'myApp',
		'logging' =&gt; true,
		'logger'  =&gt; $oCacheLog,
		'write_control' =&gt; true,
		'automatic_serialization' =&gt; true,
		'ignore_user_abort' =&gt; true
	) );

// build a caching object
$oCache = Zend_Cache::factory( $oFrontend, $oBackend );
</pre><p>Here we're creating a new Zend_Cache_Core (frontend) instance and a Zend_Cache_Backend_Memcached instance which uses a Zend_Log instance for logging.</p><p><dl> <code>Zend_Cache_Backend_Memcached</code> configuration:</p><dt>servers</dt><dd>A comma separated list of servers that this instance should use.</dd><dt>compression</dt><dd>Determines if data should be compressed before it's written.</dd></dl><p><dl> <code>Zend_Cache_Core</code> configuration:</p><dt>caching</dt><dd>this enables caching.  This is useful if you want to keep the on/off switch for caching out of your logic.  In my applications, I'm passing a <code>Zend_Config</code> variable here.</dd><dt>cache_id_prefix</dt><dd>This is the value that will be pre-pended to they id (index) you choose for each cached item.  This allows you to use Memcached for multiple applications without worrying about naming collisions.</dd><dt>logging</dt><dd>This enables logging for this backend.  You must pass in a logger to the `logger` option, described below.</dd><dt>logger</dt><dd>This should be an instance of Zend_Log that you want to use for logging.</dd><dt>write_control</dt><dd>this performs a consistency check whenever data is written to cache.  It's safer, but slower.</dd><dt>automatic_serialization</dt><dd>Turning this option on allows you to transparently pass data which is not a string or number.  For example, you can pass in an instance of Zend_Xml_Config without serializing it first, and expect an instance back when you read from cache.</dd><dt>ignore_user_abort</dt><dd>If this is set, ignore_user_abort will be set to true while cache is being written.  This helps prevent data corruption.</dd></dl><p> Once everything is configured and instantiated, we put it together with Zend_Cache::factory(), which gives us a fully configured caching object.  Now, we can use our caching object in our code:</p><pre class="brush: php;">&gt;$sCacheId = 'LargeDataSet';

if ( ! $oCache-&gt;test( $sCacheId ) ) {

	//cache miss, so we have to get the data the hard way

	$aDataSet = doExpensiveQuery();
	$oCache-&gt;save( $aDataset, $sCacheId );

} else {

	//cache hit, load from memcache. Zoom Zoom.

	$aDataSet = $oCache-&gt;load( $sCacheId );
}
</pre><p> The logic is rather simple. First, we do a test against or memcache server to see if the data exists and is usable.  If it's not, we load the data the way we normally would without caching.  If it is available, we just load it from memcache and go about our business.</p><p> Note that if you do not enable automatic_serialization like we did above, you can use a much simpler construct.  When you're only storing strings, and Zend_Cache doesn't do any transformation for you, the data is not tested for integrity (which I assume means that it can be unserialized correctly).</p><pre class="brush: php;">
//------------------------------------------------------------
// Simpler construct when automatic_serialization is turned off
//------------------------------------------------------------
$sCacheId = 'LargeDataSet';

if ( ! ( $aDataSet = $oCache-&gt;load( $sCacheId ) ) ) {

	//cache miss, so we have to get the data the hard way

	$aDataSet = doExpensiveQuery();
	$oCache-&gt;save( $aDataset,  $sCacheId );
}
</pre><p>Notice that you save a few lines of code, and you essentially only have to wrap your existing code in a single if block.  Just remember -- if it's not a string or a number, you will have to serialize/deserialize it yourself</p><h4 id="toc-conclusion">Conclusion</h4><p>Implementing Memcached with Zend Framework is incredibly simple.  If you have the proper resources and privileges to install Memcached, I can't think of any reason you would not want to do it.   A few additional lines of code can instantly make your application perform better and scale easier.</p><p>For more information about caching using Zend_Cache, please visit the Zend Framework documentation on this subject: <a href="http://framework.zend.com/manual/en/zend.cache.html">http://framework.zend.com/manual/en/zend.cache.html</a></p> ]]></content:encoded> <wfw:commentRss>http://ajbrown.org/blog/2008/12/24/memcached-in-php-made-easy-with-zend-framework.html/feed</wfw:commentRss> <slash:comments>7</slash:comments> </item> <item><title>Four reasons to hate CakePHP</title><link>http://ajbrown.org/blog/2008/12/22/four-reasons-to-hate-cakephp.html</link> <comments>http://ajbrown.org/blog/2008/12/22/four-reasons-to-hate-cakephp.html#comments</comments> <pubDate>Mon, 22 Dec 2008 20:16:45 +0000</pubDate> <dc:creator>A.J. Brown</dc:creator> <category><![CDATA[PHP]]></category> <category><![CDATA[CakePHP]]></category> <category><![CDATA[Rapid Application Development]]></category> <category><![CDATA[Zend Framework]]></category><guid isPermaLink="false">http://ajbrown.org/blog/?p=37</guid> <description><![CDATA[Before starting a project recently, I spent some time doing some research comparing CakePHP and Zend Framework for rapid development.  I noticed that of the few articles out there, not many of them where able to offer any advice on using either framework for a new project.  In most blogs, it seemed to come down to preference. This article will hopefully sway you away from CakePHP]]></description> <content:encoded><![CDATA[<p><strong>A follow-up has been posted <a href="http://ajbrown.org/blog/2008/12/23/maybe-i-was-too-hard-on-cakephp/">here</a></strong></p><p>Before starting a project recently, I spent some time doing some research comparing <a href="http://cakephp.org"  rev="vote-against">CakePHP</a> and <a href="http://framework.zend.com"  rev="vote-for">Zend Framework</a> for rapid development.  I noticed that of the few articles out there, not many of them where able to offer any advice on using either framework for a new project.  In most blogs, it seemed to come down to preference.  My goal today will be to steer you away from Cake.</p><p>This article is intended for those that are familiar with the concept of rapid application development, and the benefit of using a framework such as Zend or Cake.  If you want more information on why you would use such a framework, This article is outside of that scope.</p><p>It's worth mentioning that CakePHP and Zend Framework aren't the only popular open source rapid application frameworks out there.  I'm only focusing on these two frameworks because I have the most experience with them, and can offer useful insight into their strengths and weaknesses.  If you're interested in other frameworks, have a look at any of the following.</p><h4 id="toc-other-popular-frameworks">Other Popular Frameworks</h4></p><ul><li><a rev="vote-abstain" href="http://www.symfony-project.org/">Symphony</a> - Built in <a rev="vote-for" href="http://www.doctrine-project.org/">Doctrine</a> ORM.</li><li><a rev="vote-abstain" href="http://www.zym-project.com/">Zym Framework</a> - a fork of Zend Framework, developed in parallel.</li></ul><h4 id="toc-cake-is-heavy">Cake is Heavy</h4><p>Staying out of your way is a very important for many aspects in life, including your application's third-party framework.  You want something that provides functionality and structure, but allows you to fulfill your own needs in your own way.</p><blockquote><p>Cake is no low-calorie meal</p></blockquote><p>There's nothing lightweight about it.  Cake tends to get in the way of your application by requiring your application to be built on top of (instead of along side of) itself.  When cake is bootstrapped (configured), it will look for your application specific configuration files in a hard coded file location that it expects. Although you can predefine the defines (which is a whole-other reason to avoid using cake) it uses to determine paths, you'll never be able to completely seperate cake from your life without modifying cake itself.</p><h4 id="toc-code-and-feature-management">Code and Feature Management</h4><p>One of my cleints' websites is was written on top of one of the Cake 1.2 alpha releases from a few months ago.  (By the way, did no one tell the developers that in <a title="Software Engineering" href="http://ajbrown.org/blog/tag/software-engineering">software engineering</a> "alpha" release is not meant for the public?) I recently upgraded to 1.2 Release Candidate 4 and had a few problems.  This is to be expected when you're using any non-stable version of anything, but what I didn't expect was API changes. I don't mean additional features, I mean changing APIs.  Granted they were few and far in between, but they were still unexpected.</p><h4 id="toc-hanging-on-to-old-times">Hanging on to Old Times</h4><p>The most annoying thing to me is that Cake uses PHP4 style objects. I feel safe in saying that any community software being developed today that believes hindering best practices in order to support an obsolete (in the loosest of terms) version of software is not very forward thinking.  No class members or functions have access modifier, and I don't remember seeing any type hinting whatsoever.</p><div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">// This is Bad!</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">class</span> Animal
<span style="color: #009900;">&#123;</span>
    <span style="color: #000000; font-weight: bold;">var</span> <span style="color: #000088;">$kingdom</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'Animal'</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">function</span> foobar<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #666666; font-style: italic;">//do something very private</span>
     <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// This is GOOD!</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">class</span> Animal
<span style="color: #009900;">&#123;</span>
     <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000088;">$_kingdom</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'Animal'</span><span style="color: #339933;">;</span>
&nbsp;
      <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000000; font-weight: bold;">function</span> foobar<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
      <span style="color: #009900;">&#123;</span>
          <span style="color: #666666; font-style: italic;">//do something private</span>
      <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div><h4 id="toc-no-namespace-consideration">No Namespace Consideration</h4><p>Cake uses very generic class names with no consideration for names pace collisions.  By using cake, you're forcing yourself out of using generic class names in your own application (which is a better place for generic names to be!).  Want to name a class `Controller`, `App`, or `Model`?  Too bad -- cake has claimed those names for you!</p><p>Another horrible practice of Cake's is using global functions. Hsughughugh.  I cannot begin to tell you how painful and fragile this is.  All it takes to cure a few of the problems this creates is to wrap them in an abstract class!  I don't know why they would rather have the function uses() in a global namespace instead of wrapping it in a class as `Cake::uses()`.  Some (maybe most?) have been fixed in RC4 (you use `App::import()` instead of `uses()`), but they still allow the abuse by keeping the functions there.</p><h4 id="toc-conclusion">Conclusion</h4><p>If you are reading this article, you're probably at a crossroads between using CakePHP or using some other suite.  I could go on and on about why I personally hate cake, but when it comes down to it, the decision is yours.  Despite my gripes, Cake is still a good tool to get a simple website up and running quickly in a structured manner, without reinventing the wheel.  Just make sure your website will never need to be scalable, and that its codebase will never grow beyond the small flexibility it offers.  I'm personally experiencing this pain right now.</p><blockquote><p>Great for mom &#038; pop, but corporations shouldn't dare</p></blockquote><p>That's how I once described my thoughts on using <a href="http://www.joomla.org/" rev="vote-against">Joomla</a> as a foundation for websites to a potential client.  I think I'll go ahead and reuse that for this article.</p> ]]></content:encoded> <wfw:commentRss>http://ajbrown.org/blog/2008/12/22/four-reasons-to-hate-cakephp.html/feed</wfw:commentRss> <slash:comments>42</slash:comments> </item> <item><title>Zend Framework: Content Types in Your Routes</title><link>http://ajbrown.org/blog/2008/10/17/zend-framework-content-types-in-your-routes.html</link> <comments>http://ajbrown.org/blog/2008/10/17/zend-framework-content-types-in-your-routes.html#comments</comments> <pubDate>Sat, 18 Oct 2008 04:52:58 +0000</pubDate> <dc:creator>A.J. Brown</dc:creator> <category><![CDATA[PHP]]></category> <category><![CDATA[Usability]]></category> <category><![CDATA[Zend Framework]]></category><guid isPermaLink="false">http://ajbrown.org/blog/?p=7</guid> <description><![CDATA[One of the things that makes a useful website useful is openness and usefulness of information. Whenever you provide some sort of interesting or unique information, anyone who wants it is going to get it. If you don't make it easy for them, they'll either go somewhere else or find a way to get it. Imagine if everyone who wanted a stock ticker on their site had to write their own screen scraper? Do I even have to tell you why RSS/Atom feeds are a good thing?]]></description> <content:encoded><![CDATA[<p>I love information.  Recently, I caught myself complaining because it took a friend more than 5 minutes to Google something we were arguing about while sitting in a hot tub.  Five years ago, we would be lucky if we could even connect to the internet on our cell phones. Mmmm, information is delicious.</p><p>One of the things that makes a useful website useful is openness and usefulness of information.  Whenever you provide some sort of interesting or unique information, anyone who wants it is going to get it.  If you don't make it easy for them, they'll either go somewhere else or find a way to get it.  Imagine if everyone who wanted a stock ticker on their site had to write their own screen scraper?  Do I even have to tell you why RSS/Atom feeds are a good thing?</p><h2 id="toc-the-problem">The Problem</h2><p>My new project will most definitely benefit from open data through a web service.  Since I felt this was high on the prioritized features list, I wanted to make sure I had an architecturally sound mechanism to handle all types of requests.</p><p>In my case, I wanted almost any page which interacts with data to be accessible in RDF format. Additionally, I wanted it to be easy to add new formats at the view level, without having to do anything to the code (for a true MVC architecture).  This means the controller itself should only be concerned with which set of views to push data to, not how to translate the data into any particular format.</p><p><a href="http://coding-adventures.blogspot.com/2008/10/zend-framework-using-transparent-routes.html">My first attempt</a> was rather complicated.  I created a router which used one set of routes to strip values from a path, and then a second to do the actual routing.  On top of that, the routes had to be confusing in order to get it to work.  It wasn't very innovative, but it worked.  You can see that attempt here: <a href="http://coding-adventures.blogspot.com/2008/10/zend-framework-using-transparent-routes.html">Zend Framework: Using Transparent Routes</a>.</p><h2 id="toc-the-solution-content-type-filtering">The Solution: Content Type Filtering</h2><p>After sleeping on it, I decided to keep it simple.  Instead of writing complicated regex routes to do all of the work for me, why not just have a router that knows how to detect the content type specification, and strip it?  That's <i>true</i> transparency: don't pass any information to anything that doesn't need it.</p><p>To revisit, here are my needs:</p><ul><li>User-specific content type through the URI.  Receiving the same data in a different format is as simple as changing the content-type part of the uri.</li><li>The content-type does not have to be specified.  In that case, the content type will default to html</li><li>Only content-types I wish to support will be caught through the detection mechanism.  For example /foo/bar.html will work, but /foo/bar.bazml will not result in 'bazml' being detected as a content type.</li><li>I do not want to break the default parameterization mechanism</li></ul><p>With those requirements in mind, I went to work.  The first step is to create a router which is aware of the 'content type' concept.  It would allow a user to specify a list of content types which it will attempt to detect, and the regex patterns to use in order to detect this patterns. Once it's done it's job of detecting the content types, it will strip that portion of the URI, and pass it back to the request object.</p><h2 id="toc-the-routing-schema">The Routing Schema</h2><p>After some deliberation, I decided to change my "route schema".  At first, I wanted the content type to be specified using an extension:</p><pre>/&lt;controller&gt;/&lt;action&gt;.&lt;contentType&gt;</pre></p><p>For example, the following calls AuthorsController::browseAction() and displays the results in RDF format:</p><pre>/authors/browse.rdf/page/1</pre></p><p>The problem with this is simply the way the URIs look.  I'm not sure of any implications other than it annoying me.  So, I went with my original route schema:</p><pre>/&lt;contentType>/&lt;controller&gt;/&lt;action&gt;</pre></p><p>However, <strong>pretend I never said anything</strong>.  For the purposes of this post, we'll be using the first routing schema.  That schema points out some extra efforts that are needed to make this work universally, and provides the best demonstration.</p><h2 id="toc-the-configuration">The configuration</h2><div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
</pre></td><td class="code"><pre class="xml" style="font-family:monospace;"><span style="color: #ddbb00;">&amp;lt;</span>routing<span style="color: #ddbb00;">&amp;gt;</span>
       <span style="color: #ddbb00;">&amp;lt;</span>contentTypes<span style="color: #ddbb00;">&amp;gt;</span>html,xml<span style="color: #ddbb00;">&amp;lt;</span>/contentTypes<span style="color: #ddbb00;">&amp;gt;</span>
        <span style="color: #ddbb00;">&amp;lt;</span>contentTypePattern<span style="color: #ddbb00;">&amp;gt;</span>\w+\.(#)<span style="color: #ddbb00;">&amp;lt;</span>/contentTypePattern<span style="color: #ddbb00;">&amp;gt;</span>
        <span style="color: #ddbb00;">&amp;lt;</span>contentTypeReplacePattern<span style="color: #ddbb00;">&amp;gt;</span>\.(#)<span style="color: #ddbb00;">&amp;lt;</span>/contentTypeReplacePattern<span style="color: #ddbb00;">&amp;gt;</span>
<span style="color: #ddbb00;">&amp;lt;</span>/routing<span style="color: #ddbb00;">&amp;gt;</span></pre></td></tr></table></div><p>So this will tell our controller that our router than we're only looking for xml and html.  We'll use contentTypePattern to detect the pattern, and contentTypeReplacePattern to strip the pattern from the the path before routing.</p><p>Whoa, <strong>what's up with the #?</strong>.  Simple, the # characters will be replaced with a list of content types from our configuration.  This allows us to abstract the list of content types we want to detect from the pattern used to find them.  Here's what the regex will look like after the str_replace is done to insert the content types:</p><pre>\w+\.(html|rdf|xml)</pre></p><p>That makes sense, but <strong>why do we need a detection pattern <em>and</em> a stripping pattern?</strong> This is exactly why the .extension content type schema makes a better demonstration.  Lets say we're trying to determine the content type of a request to `controller/action.xml`. What we want is the 'xml' part.  If we write a regex to match the 'xml' part, and use that same pattern to strip it out, we'll end up passing 'controller/action.' as the request path. Obviously, we don't want that stray dot.</p><p>Enter the replacement pattern.  If we tell our router the entire string that we want to remove, we can also match the dot, and pass 'controller/action' as the request path.  This adds much more flexibility to our routes.</p><h2 id="toc-the-router">The Router</h2><div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
</pre></td><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">class</span> ContentTypeRouter <span style="color: #000000; font-weight: bold;">extends</span> Zend_Controller_Router_Rewrite
<span style="color: #009900;">&#123;</span>
&nbsp;
 protected <span style="color: #000088;">$_aContentTypes</span>      <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
 protected <span style="color: #000088;">$_aContentTypeRegex</span>    <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
 protected <span style="color: #000088;">$_sDefaultContentType</span>   <span style="color: #339933;">=</span> <span style="color: #0000ff;">'html'</span><span style="color: #339933;">;</span>
&nbsp;
 <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> addContentType<span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sContentType</span> <span style="color: #009900;">&#41;</span>
 <span style="color: #009900;">&#123;</span>
  <span style="color: #990000;">assert</span><span style="color: #009900;">&#40;</span> <span style="color: #990000;">is_string</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sContentType</span> <span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #000088;">$sContentType</span> <span style="color: #339933;">=</span> <span style="color: #990000;">strtolower</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sContentType</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span> <span style="color: #339933;">!</span><span style="color: #990000;">in_array</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sContentType</span><span style="color: #339933;">,</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_aContentTypes <span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span>
  <span style="color: #009900;">&#123;</span>
   <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_aContentTypes<span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$sContentType</span><span style="color: #339933;">;</span>
  <span style="color: #009900;">&#125;</span>
 <span style="color: #009900;">&#125;</span>
&nbsp;
 <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> setDefaultContentType<span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sContentType</span> <span style="color: #009900;">&#41;</span>
 <span style="color: #009900;">&#123;</span>
  <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_sContentType <span style="color: #339933;">=</span> <span style="color: #000088;">$sContentType</span><span style="color: #339933;">;</span>
 <span style="color: #009900;">&#125;</span>
&nbsp;
 <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> addFilterRegex<span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sRegex</span><span style="color: #339933;">,</span> <span style="color: #000088;">$sReplacePattern</span><span style="color: #339933;">,</span> <span style="color: #990000;">array</span> <span style="color: #000088;">$aMap</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>  <span style="color: #009900;">&#41;</span>
 <span style="color: #009900;">&#123;</span>
&nbsp;
  <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_aContentTypeRegex<span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span>  <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span>
   <span style="color: #0000ff;">'regex'</span>  <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$sRegex</span><span style="color: #339933;">,</span>
   <span style="color: #0000ff;">'replace'</span><span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$sReplacePattern</span><span style="color: #339933;">,</span>
   <span style="color: #0000ff;">'map'</span>    <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$aMap</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
 <span style="color: #009900;">&#125;</span>
&nbsp;
 <span style="color: #009933; font-style: italic;">/**
  * @see Zend_Controller_Router_Rewrite
  *
  * @param Zend_Controller_Request_Abstract $oRequest
  * @return Zend_Controller_Request_Abstract
  */</span>
 <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> route<span style="color: #009900;">&#40;</span> Zend_Controller_Request_Abstract <span style="color: #000088;">$oRequest</span> <span style="color: #009900;">&#41;</span>
 <span style="color: #009900;">&#123;</span>
&nbsp;
  <span style="color: #000088;">$sContentType</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_sDefaultContentType<span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #000088;">$sContentTypes</span> <span style="color: #339933;">=</span> <span style="color: #990000;">join</span><span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">'|'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_aContentTypes <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000088;">$sPath</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_getPathInfo<span style="color: #009900;">&#40;</span> <span style="color: #000088;">$oRequest</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #b1b100;">foreach</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span>_aContentTypeRegex <span style="color: #b1b100;">as</span> <span style="color: #000088;">$aRegex</span> <span style="color: #009900;">&#41;</span>
  <span style="color: #009900;">&#123;</span>
   <span style="color: #000088;">$sRegex</span> <span style="color: #339933;">=</span> <span style="color: #990000;">str_replace</span><span style="color: #009900;">&#40;</span>
    <span style="color: #0000ff;">'#'</span><span style="color: #339933;">,</span>
    <span style="color: #000088;">$sContentTypes</span><span style="color: #339933;">,</span>
    <span style="color: #000088;">$aRegex</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'regex'</span><span style="color: #009900;">&#93;</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
&nbsp;
   <span style="color: #000088;">$aMatch</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
   <span style="color: #000088;">$sRegex</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;/<span style="color: #006699; font-weight: bold;">$sRegex</span>/&quot;</span><span style="color: #339933;">;</span>
&nbsp;
   <span style="color: #666666; font-style: italic;">//die ($sPath);</span>
&nbsp;
   <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span> <span style="color: #cc66cc;">1</span> <span style="color: #339933;">===</span> <span style="color: #990000;">preg_match</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sRegex</span><span style="color: #339933;">,</span> <span style="color: #000088;">$sPath</span><span style="color: #339933;">,</span> <span style="color: #000088;">$aMatch</span> <span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span>
   <span style="color: #009900;">&#123;</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">//grab the content type from the match</span>
    <span style="color: #000088;">$sContentType</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$aMatch</span><span style="color: #009900;">&#91;</span> <span style="color: #cc66cc;">1</span> <span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">//---------------------------------------</span>
    <span style="color: #666666; font-style: italic;">//- replace the content type in the route</span>
    <span style="color: #666666; font-style: italic;">//---------------------------------------</span>
&nbsp;
    <span style="color: #000088;">$sRegex</span> <span style="color: #339933;">=</span> <span style="color: #990000;">str_replace</span><span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">'#'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$sContentTypes</span><span style="color: #339933;">,</span> <span style="color: #000088;">$aRegex</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'replace'</span><span style="color: #009900;">&#93;</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$sPath</span> <span style="color: #339933;">=</span> <span style="color: #990000;">preg_replace</span><span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;/<span style="color: #006699; font-weight: bold;">$sRegex</span>/&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">''</span><span style="color: #339933;">,</span> <span style="color: #000088;">$sPath</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #b1b100;">break</span><span style="color: #339933;">;</span>
   <span style="color: #009900;">&#125;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #000088;">$oRequest</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setPathInfo</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sPath</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000088;">$oRequest</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setParam</span><span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">'requestedContentType'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$sContentType</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #000088;">$oRequest</span> <span style="color: #339933;">=</span> parent<span style="color: #339933;">::</span><span style="color: #004000;">route</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$oRequest</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
  <span style="color: #b1b100;">return</span> <span style="color: #000088;">$oRequest</span><span style="color: #339933;">;</span>
&nbsp;
 <span style="color: #009900;">&#125;</span>
&nbsp;
 <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000000; font-weight: bold;">function</span> _getPathInfo<span style="color: #009900;">&#40;</span> Zend_Controller_Request_Abstract <span style="color: #000088;">$oRequest</span> <span style="color: #009900;">&#41;</span>
 <span style="color: #009900;">&#123;</span>
   <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #990000;">method_exists</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$oRequest</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'getVersion'</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">||</span> <span style="color: #000088;">$oRequest</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getVersion</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #b1b100;">return</span> <span style="color: #000088;">$oRequest</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPathInfo</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span> <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #b1b100;">return</span> <span style="color: #000088;">$oRequest</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span>
 <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div><p>One thing you will notice about the route is that you can stack filters.  This means we can support more than one routing schema, and the first one to match will be applied.  Now, when I decide to switch back to the `&lt;controller&gt;/&lt;action&gt;.&lt;contentType&gt;` schema, I can do so without breaking existing URLs.  My users will love me.</p><h2 id="toc-usage-example">Usage Example</h2><div class="wp_syntax"><table><tr><td class="line_numbers"><pre>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
</pre></td><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$oRouter</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> ContentTypeRouter<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// load supported content types from config</span>
<span style="color: #000088;">$aContentTypes</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getMainConfiguration</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">routing</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">contentTypes</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$sPattern</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getMainConfiguration</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">routing</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">contentTypePattern</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$aContentTypes</span> <span style="color: #339933;">=</span> <span style="color: #990000;">split</span><span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">','</span><span style="color: #339933;">,</span> <span style="color: #000088;">$aContentTypes</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// add our regex filters</span>
<span style="color: #000088;">$oRouter</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">addFilterRegex</span><span style="color: #009900;">&#40;</span>
 <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getMainConfiguration</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">routing</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">contentTypePattern</span><span style="color: #339933;">,</span>
 <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getMainConfiguration</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">routing</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">contentTypeReplacePattern</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
&nbsp;
<span style="color: #666666; font-style: italic;">// add each supported content type to the router</span>
<span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span> <span style="color: #000088;">$aContentTypes</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$sContentType</span> <span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
 <span style="color: #000088;">$oRouter</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">addContentType</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$sContentType</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// make use of the router before dispatch</span>
<span style="color: #000088;">$oController</span> <span style="color: #339933;">=</span> Zend_Controller_Front<span style="color: #339933;">::</span><span style="color: #004000;">getInstance</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$oController</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">setRouter</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$oRouter</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>]]></content:encoded> <wfw:commentRss>http://ajbrown.org/blog/2008/10/17/zend-framework-content-types-in-your-routes.html/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Zend Framework: Using Transparent Routes</title><link>http://ajbrown.org/blog/2008/10/05/zend_framework_transparent_routes.html</link> <comments>http://ajbrown.org/blog/2008/10/05/zend_framework_transparent_routes.html#comments</comments> <pubDate>Mon, 06 Oct 2008 04:42:25 +0000</pubDate> <dc:creator>A.J. Brown</dc:creator> <category><![CDATA[PHP]]></category> <category><![CDATA[MVC]]></category> <category><![CDATA[Routes]]></category> <category><![CDATA[Zend Framework]]></category><guid isPermaLink="false">http://ajbrown.org/blog/?p=3</guid> <description><![CDATA[For my current project, I wanted my routes to work like the default routes, with a slight twist. The first part of the URL needed to be a content type to return to the user, and the following string be the controller, action, and parameters. So, for example,]]></description> <content:encoded><![CDATA[<p>I love Zend Framework.  I have to admit, my only othe real indepth experience with a rapid development framework in PHP has been CakePHP, though.  I hope that doesn't discredit me too much.</p><p>Routing with Zend Framework takes on the best of both worlds:  It's very simple in it's default form, but tools are provided that allow you to take on as much complexity as you desire.  Even if the shipped classes aren't enough for you, you can easily write your own route types, or even your own router.</p><div class="popout"><h3 id="toc-update">Update</h3><p>I've decided that this method of solving my problem was a little complicated and not very scalable.  Please check out <a href="http://coding-adventures.blogspot.com/2008/10/zend-framework-content-types-in-your.html">this article</a> for a better solution.</div><h3 id="toc-the-problem">The Problem</h3><p>For my current project, I wanted my routes to work like the default routes, with a slight twist.  The first part of the URL needed to be a content type to return to the user, and the following string be the controller, action, and parameters.  So, for example,</p><pre>/xml/auth/login
     controller: auth
     action: login
     requestedContentType: xml

/html/auth/login
     controller: auth
     action: login
     requestedContentType: html</pre><p>This is rather simple using a standard route in zend framework:</p><div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$oRouter</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$oController</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>getRouter<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$oRoute</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Zend_Controller_Router_Route<span style="color: #009900;">&#40;</span>
  <span style="color: #0000ff;">':/requestedContentType/:controller/:action/*
);
$oRouter-&amp;gt;addRoute( '</span>awesomeRoute<span style="color: #0000ff;">', $oRoute );
$oController-&amp;gt;setRouter( $oRouter );</span></pre></div></div><p>Perfect.  The accomplishes everything we need, and is very simple.  But then I had to go and throw a wrench in the works.  In addition to having a URI supplied requestedContentType, I wanted to:</p><ul><li>Make sure that only supported content types were parsed in this manner.</li><li>Be able to not specify a content type, and have it default to html.</li><li>Not break Zend Framework's default parameterization behavior.</li></ul><p>Example parsed routes:</p><pre>/xml/content/books
     controller: content
     action: books
     requestedContentType: xml

/html/content/books
     controller: content
     action: books
     requestedContentType: html

/content/books
     controller: content
     action: books
     requestedContentType: html

/rss/content/books/page/1
     controller: content
     action: books
     requestedContentType: rss
     page: 1

/content/books/foo/bar
     controller: content
     action: books
     requestedContentType: html
     foo: bar</pre><p>By now, you may have guessed it -- A custom is in order.  Unfortunately, I messed around with chaining routes of different types for hours before coming to this conclusion.  This was mosty out of fear, though.</p><h3 id="toc-the-solution">The Solution</h3><p>The approach I took was slightly different than the built-in routing flow.  As shipped, Zend's routers will loop through the routes you pass to it to it until it finds the first one to match (in the order of which they were supplied).  Once the route matches, the parameters extracted through that route are assigned to the request, and control is returned to the dispatcher.</p><p>I decided to rework this a little bit to meet my needs.  I essentially wanted to pass in a set of filters which could extract parameters from a URI before the actual routing takes place.  This would allow me to extract the content type from the URI if it existed, but allow other routes to actually handle the routing itself.  I called these routes 'Transparent Routes', since they are applied the same way as Zend's routes, however they do not actually invoke routing.</p><p>Here is my flow:</p><ol><li>Setup Your Routes</li><li>Assign Transparent Routes</li><li>Assign Routes</li><li>When route() is invoked, the transparent routes are applied, which extracts parameters from the URI</li><li>The standard routing mechanism is triggered.  When a route is matched, only parameters which are also extracted from this route will be applied.</li></ol><p>This completely solved my immediate issue, and allowed opportunties for much more down the road.  Almost any parameter can be generated this way.</p><h3 id="toc-the-transparencyrouter-class">The TransparencyRouter Class</h3><div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #009933; font-style: italic;">/**
 * Extended router which allows applying transparent routes which
 * only serve to extract paramters.  Once transparent routes
 * have been applied, controll is returned to the standard
 * Zend_Controller_Router_Rewrite.
 *
 * @author A.J. Brown
 * @version 1.0
 *
 */</span>
<span style="color: #000000; font-weight: bold;">class</span> TransparencyRouter <span style="color: #000000; font-weight: bold;">extends</span> Zend_Controller_Router_Rewrite
<span style="color: #009900;">&#123;</span>
 protected <span style="color: #000088;">$_transparentRoutes</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
 <span style="color: #009933; font-style: italic;">/**
  * Adds a route which will be used for extracting parameters only.
  *
  * @param string $sName the name for this route
  * @param Zend_Controller_Router_Route_Abstract $oRoute
  * @return TransparencyRouter
  */</span>
 <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> addTransparentRoute<span style="color: #009900;">&#40;</span>
  <span style="color: #000088;">$sName</span><span style="color: #339933;">,</span>
  Zend_Controller_Router_Route_Abstract <span style="color: #000088;">$oRoute</span>
 <span style="color: #009900;">&#41;</span>
 <span style="color: #009900;">&#123;</span>
        <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #990000;">method_exists</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$oRoute</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'setRequest'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
            <span style="color: #000088;">$oRoute</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>setRequest<span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>getFrontController<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>getRequest<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
        <span style="color: #009900;">&#125;</span>
&nbsp;
        <span style="color: #000088;">$this</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>_transparentRoutes<span style="color: #009900;">&#91;</span><span style="color: #000088;">$sName</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$oRoute</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #b1b100;">return</span> <span style="color: #000088;">$this</span><span style="color: #339933;">;</span>
 <span style="color: #009900;">&#125;</span>
&nbsp;
 <span style="color: #009933; font-style: italic;">/**
  * @see Zend_Controller_Router_Rewrite
  *
  * @param Zend_Controller_Request_Abstract $oRequest
  * @return Zend_Controller_Request_Abstract
  */</span>
 <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> route<span style="color: #009900;">&#40;</span> Zend_Controller_Request_Abstract <span style="color: #000088;">$oRequest</span> <span style="color: #009900;">&#41;</span>
 <span style="color: #009900;">&#123;</span>
&nbsp;
  <span style="color: #b1b100;">foreach</span> <span style="color: #009900;">&#40;</span><span style="color: #990000;">array_reverse</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$this</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>_transparentRoutes<span style="color: #009900;">&#41;</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$name</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #000088;">$route</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
      <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #990000;">method_exists</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$route</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'getVersion'</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">||</span> <span style="color: #000088;">$route</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>getVersion<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$match</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$oRequest</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>getPathInfo<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span> <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$match</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$request</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span>
&nbsp;
      <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$params</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$route</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>match<span style="color: #009900;">&#40;</span> <span style="color: #000088;">$match</span> <span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$this</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>_setRequestParams<span style="color: #009900;">&#40;</span><span style="color: #000088;">$oRequest</span><span style="color: #339933;">,</span> <span style="color: #000088;">$params</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
                <span style="color: #000088;">$iMatched</span><span style="color: #339933;">++;</span>
            <span style="color: #009900;">&#125;</span>
  <span style="color: #009900;">&#125;</span>
&nbsp;
  <span style="color: #b1b100;">return</span> parent<span style="color: #339933;">::</span><span style="color: #004000;">route</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$oRequest</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
 <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span></pre></div></div><h3 id="toc-example-usage">Example Usage</h3><div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">//--------------------------</span>
<span style="color: #666666; font-style: italic;">// Configure routes</span>
<span style="color: #666666; font-style: italic;">//--------------------------</span>
&nbsp;
<span style="color: #000088;">$oRouter</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> TransparencyRouter<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000088;">$oInterpreterRoute</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Zend_Controller_Router_Route<span style="color: #009900;">&#40;</span>
 <span style="color: #0000ff;">':controller/:action/*'</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000088;">$oNonContentTypeRoute</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Zend_Controller_Router_Route_Regex<span style="color: #009900;">&#40;</span>
 <span style="color: #0000ff;">'(\w+)/(\w+)(\/.*)?'</span><span style="color: #339933;">,</span>
 <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span>
  <span style="color: #0000ff;">'requestedContentType'</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #0000ff;">'html'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span>
 <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span>
  <span style="color: #cc66cc;">1</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #0000ff;">'controller'</span><span style="color: #339933;">,</span>
  <span style="color: #cc66cc;">2</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #0000ff;">'action'</span> <span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000088;">$oContentTypeRoute</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> Zend_Controller_Router_Route_Regex<span style="color: #009900;">&#40;</span>
 <span style="color: #666666; font-style: italic;">// TODO the content types themselves should be</span>
        <span style="color: #666666; font-style: italic;">// pulled in from a config file.</span>
        <span style="color: #0000ff;">'(html|xml)\/(\w+)\/(\w+)(\/.*)?'</span><span style="color: #339933;">,</span>
 <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span>
 <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span>
  <span style="color: #cc66cc;">1</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #0000ff;">'requestedContentType'</span><span style="color: #339933;">,</span>
  <span style="color: #cc66cc;">2</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #0000ff;">'controller'</span><span style="color: #339933;">,</span>
  <span style="color: #cc66cc;">3</span> <span style="color: #339933;">=&amp;</span>gt<span style="color: #339933;">;</span> <span style="color: #0000ff;">'action'</span> <span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #000088;">$oRouter</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>addTransparentRoute<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">'nonContentType'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$oNonContentTypeRoute</span>  <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$oRouter</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>addFilteringRoute<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">'contentType'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$oContentTypeRoute</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$oRouter</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>addRoute<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">'generic'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$oInterpreterRoute</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$oRouter</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>addRoute<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">'contentType'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$oContentTypeRoute</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">//-------------------------</span>
<span style="color: #666666; font-style: italic;">// Setup controller</span>
<span style="color: #666666; font-style: italic;">//-------------------------</span>
<span style="color: #000088;">$oController</span> <span style="color: #339933;">=</span> Zend_Controller_Front<span style="color: #339933;">::</span><span style="color: #004000;">getInstance</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$oController</span><span style="color: #339933;">-&amp;</span>gt<span style="color: #339933;">;</span>setRouter<span style="color: #009900;">&#40;</span> <span style="color: #000088;">$oRouter</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></div></div>]]></content:encoded> <wfw:commentRss>http://ajbrown.org/blog/2008/10/05/zend_framework_transparent_routes.html/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> </channel> </rss>
