A form in your layout

11th October 2010

I recently received an email asking for my advice about how to handle a form that appears on every page.

I want to add a newsletter sign up box to layout.phtml so it will appear on every page. The layout->content() comes from several different action controllers... So how do I handle the newsletter sign up?

I thought that the answer is long-winded enough to be worth writing a blog post about.

One way to do this is to use a action helper, so let's build a simple application to show this solution.

Start by setting up a ZF application:

$ zf create project layoutform
$ cd layoutform
$ zf enable layout

Copy the Zend Framework's library/Zend folder into layoutform/library check that you get the ZF Welcome screen when you navigate to the project in your browser. Empty application/views/scripts/index/index.phtml and replace with something simple like this:

application/views/scripts/index/index.phtml:

<p>This is the home page</p>

Now we have a clean, simple place to start from.

The form

We need a form, so we'll create one:

$ zf create form signup

Now, let's create the fields we need:

application/forms/Signup.php:

class Application_Form_Signup extends Zend_Form
{
    public $processed false;

    public function init()
    {
        $this->addElement('text''name', array(
            'label' => 'Name',
            'required' => true,
            'validators' => array(
                array('StringLength'false, array('max'=>75)),
            ),
        ));
        $this->addElement('text''email', array(
            'label' => 'Email',
            'required' => true,
            'validators' => array(
                array('StringLength'false, array('max'=>150)),
                'EmailAddress',
            ),
        ));
        $this->addElement('submit''go', array(
            'label' => 'Sign up',
        ));
    }
}

now we have a form, we need to instantiate it and then display it.

The action helper

We use an action helper to instantiate the form and then later process it.

Firstly, setup the action helper. add a line to the [production] section of the config file:

application/configs/application.ini:

resources.frontController.actionhelperpaths.Application_Controller_Helper APPLICATION_PATH "/controllers/helpers"

Now the system knows were we are storing our action helpers, we can register a helper called Signup:

application/Bootstrap.php:

<?php

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initMyActionHelpers()
    {
        $this->bootstrap('frontController');
        $signup Zend_Controller_Action_HelperBroker::getStaticHelper('Signup');
        Zend_Controller_Action_HelperBroker::addHelper($signup);
    }
}

The action helper looks like this:

application/controllers/helpers/Signup.php:

<?php

class Application_Controller_Helper_Signup extends Zend_Controller_Action_Helper_Abstract
{
    public function preDispatch()
    {
        $view $this->getActionController()->view;
        $form = new Application_Form_Signup();

        $request $this->getActionController()->getRequest();
        if($request->isPost() && $request->getPost('submitsignup')) {
            if($form->isValid($request->getPost())) {
                $data $form->getValues();
                // process data

                $form->processed true;
            }
        }
        
        $view->signupForm $form;
    }
}

This is pretty standard code for form handling. The only unusual thing we do is that set the processed property of the form to true so that we know that we have done something. We then use this to display a thank you message.

The view helper

To display the form, we use a view helper which is called from the layout view script.

The view helper looks like this:
application/views/helpers/SignupForm.php:

<?php

class Zend_View_Helper_SignupForm extends Zend_View_Helper_Abstract
{
    public function signupForm(Application_Form_Signup $form)
    {
        $html '<h2>Sign up for our newsletter</h2>';
        if($form->processed) {
            $html .= '<p>Thank you for signing up</p>';
        } else {
            $html .= $form->render();
        }
        return $html;
    }
}

And to round it off, we render this in layout.phtml, whilst also taking the opportunity to create a minimal layout script:

application/layouts/scripts/layout.phtml:

<?php
$this->headMeta()->prependHttpEquiv('Content-Type''text/html; charset=UTF-8');
$this->headTitle('Layout form test');
echo $this->doctype(); ?>
<html>
<head>
<?php echo $this->headMeta()->setIndent(4); ?>  
<?php echo $this->headTitle()->setIndent(4); ?>  
</head>
<body>
    <div id="maincontent">
    <?php echo $this->layout()->content?>  
    </div>
    <div id="secondary">
    <?php echo $this->signupForm($this->signupForm); ?>
    </div>
</body>
</html>

All done

And that's it. The form is displayed on every page and if filled in, it is processed and a thank you message is displayed. Obviously validation also works.

Completely un-styled, it looks like this:
Sign up form in a layout

This is the project code that I used: zf-tutorial-layoutform.zip

if you found this useful, then you should have a read of Matthew's article on using action helpers to implement re-usable widgets also.

Zend\Tool providers in ZF2 (dev1)

27th September 2010

I've started playing with the development versions of ZF 2.0 and one of the first things I thought I'd do was to port Akrabat_Db_Schema_Manager. It turned out to be reasonably easy.

All I needed to do was rework my use of ZF components to use the new ZF2 ones. Whilst I was at it, I also converted it to use namespaces. I also had to reorganise the http://github.com/akrabat/Akrabat library so that I could have ZF1 and ZF2 code in it.

The DatabaseSchemaProvider

Before:

<?php

class Akrabat_Tool_DatabaseSchemaProvider extends Zend_Tool_Project_Provider_Abstract
{
    //etc

After:

<?php

namespace Akrabat\Tool;

class DatabaseSchemaProvider extends \Zend\Tool\Project\Provider\AbstractProvider
{
    //etc

The filename, DatabaseSchemaProvider.php, is the same and the file lives in the Akrabat/Tool directory as before.

You can see the class now extends from \Zend\Tool\Project\Provider\AbstractProvider. This shows one of the consequences of moving to namespaces: with a one to one mapping, we would have ended up with a class called Abstract which isn't allowed, so the classname has been changed to AbstractProvider. There are a fair few class name changes like this throughout ZF2, so expect to do a bit of file browsing :)

The rest of the changes that I had to make are exactly the same type namespace conversions and that was all I had to do. Maybe when the autoloader is updated, then more changes will be required and if so, I'll no doubt write it up!

The new Akrabat\Db\Schema\Manager is available on my github account. The readme contains instructions on how to set it up with ZF2 too.

Setting up ZF2's Zend\Tool side by side with ZF1

20th September 2010

If you want to play with the development versions of Zend Framework 2.0, then it's handy to be able to create ZF2 projects using the Zend\Tool command line tool.

Rather unhelpfully, ZF2's Zend\Tool uses the same ini file (~/.zf.ini) as ZF1's Zend_Tool and the same zf.sh script filename, so you can't just put zf2 on to your path and it'll all just work.

I am assuming that you're like me and have production sites using ZF1, so you probably don't want to mess up your current zf.sh usage. This is how I implemented side-by-side ZF cli scripts.

1. Install ZF2 somewhere

I like to install in /usr/local/include. From the command line type:

cd /usr/local/include
git clone git://git.zendframework.com/zf.git zf2

Don't forget to periodically update it with:

cd /usr/local/include
git pull origin master

(And don't be surprised when it breaks whatever you've already coded in ZF2.dev!)

2. Create .zf2.ini

You need an ini file for ZF2, so call it .zf2.ini and store it in your home directory next to .zf.ini. You need to set the correct include path so that Zend\Tool's zf.php can find your ZF2 installation. From the command line type:

echo 'php.include_path = "/usr/local/include/zf2/library:/usr/local/include/Akrabat/zf2/"' >> ~/.zf2.ini

This creates the .zf2.ini file with the correct include_path set up.

3. Create a zf2 alias

Update your ~/.bash_profile to set up an alias to the ZF2 zf.sh script.

Using a text edit, add this line to the end of ~/.bash_profile:

alias zf2='export ZF_CONFIG_FILE=~/.zf2.ini; /usr/local/include/zf2/bin/zf.sh'

Restart your terminal or type source ~/.bash_profile

Now you can type zf2 to run ZF2's zf.sh and Zend\Tool will run and not be affected by any ZF1 configurations you may have!

ZF1 and ZF2's cli scripts both work!

TweetGT: an example of Zend_Service_Twitter via OAuth

13th September 2010

TweetGT is a simple application that talks to Twitter. I wrote it as I couldn't find another way to send a geotagged tweet sent from an arbitrary location.

Screenshot of tweetgt.funkymongoose.com

Also, my friend Cal Evans says that writing a Twitter app is the new Hello World, so I thought I'd better find out how to do it! Obviously, I used Zend Framework :)

The source is up on github so you can have a look at the entire project. The section I want to concentrate on in this post is the Twitter OAuth integration, which was added to Zend_Service_Twitter in version 1.10.6.

To implement my Twitter integration, I used a model, Application_Model_Twitter, which has a protected member variable to an instance of Zend_Service_Twitter. This means that the rest of the application has to go through the model to get to the service and so in principle at least, I get to control access.

OAuth integration requires that we get an access token from twitter. The basic process is that we hand off from our website to Twitter, who then call back to a URL on our site once the user has logged in.

Login: Redirect to Twitter

The loginAction looks like this:


    public function loginAction()
    {
        $twitter $this->_helper->twitter(); /* @var $twitter Application_Model_Twitter */

        // We need the request token for use in the callback when the user is
        // redirected back here from Twitter after authenticating
        $session = new Zend_Session_Namespace();
        $session->requestToken $twitter->getRequestToken();

        // redirect to the Twitter website
        $twitter->loginViaTwitterSite();
    }

Three things going on here. Firstly, I have set up an action helper to retrieve an instance of my model for me. This is mainly for ease of use as there's a bit of configuration required, so having it centralised saves having to duplicate code. There's other ways of doing this of course, but a action helper suited me this time :)

When we hand over to Twitter, we send a request token over too. We will need the request token in the call back so we store to the session ready for use after the user has authenticated on Twitter's site.

Finally we do the redirect to Twitter's site via a model method, loginViaTwitterSite, which simply proxies to the redirect method within Zend_Service_Twitter's OAuth consumer.

Instantiating the model

The model is instantiated within a controller action helper. To log in to twitter using OAuth we need a consumer key and a consumer secret that are available from Twitter on a per-application basis. I've chosen to store these in the application.ini file. We also need to configure Zend_Service_Twitter with the callback URL to use and, if we are logged in, the username and access token from Twitter. This action helper does all that for us and looks like this:


class Application_Controller_Helper_Twitter extends Zend_Controller_Action_Helper_Abstract
{
    /**
     * @var Application_Model_Twitter
     */
    protected $_twitter;

    public function direct()
    {
        if (!$this->_twitter) {
            $controller $this->getActionController();

            $config = array();

            $session = new Zend_Session_Namespace();
            if ($session->accessToken) {
                $token $session->accessToken;
                $config['username'] = $token->screen_name;
                $config['accessToken'] = $token;
            }
            
            $options $controller->getInvokeArg('bootstrap')->getOptions();
            $config['consumerKey'] = $options['twitter']['consumerKey'];
            $config['consumerSecret'] = $options['twitter']['consumerSecret'];

            $request $controller->getRequest();
            $url $request->getScheme() . '://' .  $request->getHttpHost() . $request->getBaseUrl();
            $config['callbackUrl'] = $url '/callback';

            $this->_twitter = new Application_Model_Twitter($config);
        }

        return $this->_twitter;
    }

}

I didn't want my model interacting with sessions or the bootstrap options, so I used an action controller. I could equally have used a service layer object, or instantiated the model in the bootstrap or in a Front Controller plugin. The most important thing is that I only deal with the config of sorting out the config array that Zend_Service_Twitter needs once.

Callback

I have chosen /callback as the URL for Twitter to use. The easiest way to set this up is to have in indexAction() within a CallbackController class. This code will use the model's twitter service to retrieve the access token and store it to the session.

It looks like this:

class CallbackController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $session = new Zend_Session_Namespace();
        if (!empty($this->getRequest()->getQuery()) && isset($session->requestToken)) {

            // Get the model instance from the action helper
            $twitter $this->_helper->twitter(); /* @var $twitter Application_Model_Twitter */

            // turn the request token into an access token
            $accessToken $twitter->getAccessToken($this->getRequest()->getQuery(),
                    $session->requestToken);

            // store the access token
            $session->accessToken $accessToken;

            // we don't need the request token any more
            unset($session->requestToken);

            // redirect back to home page
            $this->_helper->redirector('index''index');
        } else {
            throw new Zend_Exception('Invalid callback request. Oops. Sorry.');
        }
    }

}

The code should be fairly self-explanatory with the inline comments to help :)

That's it. We are now logged into Twitter and back on our own website able to do whatever we want to do :) Have a look at the source to see the rest of the details of how it all fits together.

Unit testing controller actions with Zend_Test_PHPUnit_ControllerTestCase

6th September 2010

Testing controllers has traditionally been a hassle due to the requirements of setting up the bootstrap, the front controller and initiating the dispatch cycle. In June, Matthew addressed this with the release of Zend_Test_PHPUnit_ControllerTestCase way back in 2008.

Later, Matthew helpfully wrote an article on how to use it and I have used that as a starting point for the information here. (Thanks Matthew!)

The project I'm using is TodoIt, which is a simple ZF demo application, which needs unit tests.

Setting up PHPUnit

All your unit tests will live in the /tests folder. The ZF cli tool will create a phpunit.xml file for you, but you'll discover that it's empty! This is what it should look like:


<phpunit colors="true" bootstrap="./TestHelper.php">
    <testsuite name="TodoIt Test Suite">
        <directory>./</directory>
    </testsuite>

    <filter>
        <whitelist>
            <directory suffix=".php">../library/</directory>
            <directory suffix=".php">../application/</directory>
            <exclude>
                <directory suffix=".phtml">../application/</directory>
            </exclude>
        </whitelist>
    </filter>

    <logging>
        <log highlowerbound="80" lowupperbound="50" highlight="true" yui="true" charset="UTF-8" target="./log/report" type="coverage-html"></log>
    </logging>
 
</phpunit>

This file is used to configure phpunit itself and saves having to use command line options. As it's XML, it's fairly easy to read. The testsuites element is used to specify the testsuite we're going to test. In principle you can have many test suites; in this case, one is enough! The filter section is used to specify which files to use for code coverage reporting and the logging section is used to configure the reports.

We also specify TestHelper.php as the bootstrap. This mean that it is called for us and contains the necessary PHP setup we need to do in order to load and use Zend Framework. In effect TestHelper.php acts like public/index.php does for your web application. TestHelper.php looks like this:

<?php
// Based on http://weierophinney.net/matthew/archives/190-Setting-up-your-Zend_Test-test-suites.html

// PHP settings
error_reporting(E_ALL E_STRICT);
date_default_timezone_set('Europe/London');

define('APPLICATION_ENV''unittesting');
define('APPLICATION_PATH'realpath(dirname(__FILE__) . '/../application'));

// Directories for include path
$root realpath(dirname(__FILE__) . '/../');
$library $root '/library';
$models $root '/application/models';

$path = array(
    $library,
    $models,
    get_include_path()
);
set_include_path(implode(PATH_SEPARATOR$path));

require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();

// Unset global variables
unset($root$library$models$path);

As with public/index.php, we set APPLICATION_ENV and APPLICATION_PATH, update the include_path and then set up the autoloader. Now we're all ready to write some tests!

A controller test class

I place my controller test classes in tests/application/controllers to make them easy to find. (Model tests go in tests/application/models!). The TodoIt application has a login form in AuthController::indexAction() which is accessed via the /auth URL. We'll start by testing this form is displayed.

The controller's test class is called AuthControllerTest and lives in tests/application/controllers/AuthControllerTest.php:

<?php

// Call AuthControllerTest::main() if this source file is executed directly.
if (!defined("PHPUnit_MAIN_METHOD")) {
    define("PHPUnit_MAIN_METHOD""AuthControllerTest::main");
}

require_once 'PHPUnit/Framework/TestCase.php';

/**
 * @group Controllers
 */
class AuthControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
    public static function main()
    {
        $suite  = new PHPUnit_Framework_TestSuite(get_class($this));
        $result PHPUnit_TextUI_TestRunner::run($suite);
    }
    
    public function setUp()
    {
        $application = new Zend_Application(
            APPLICATION_ENV,
            APPLICATION_PATH '/configs/application.ini'
        );

        $this->bootstrap $application;
        return parent::setUp();
    }

    public function tearDown()
    {
        /* Tear Down Routine */
    }

    public function testLoginDisplaysAForm()
    {
        $this->dispatch('/auth/index');
        $this->assertQueryContentContains('h1''Login');
        $this->assertQuery('form#login'); // id of form
    }
}

There's three things going on in here, so let's look at each in turn.

Firstly we set up the file to allow PHPUnit to run this file on it's own using the command line:

phpunit tests\application\controllers\AuthControllerTest.php

This is done by setting the PHPUnit_MAIN_METHOD constant to the static method AuthControllerTest::main(). The phpunit cli tool will then run this method which will in turn run this file as a test suite.

The methods setUp() and tearDown() are called before and after every test method and are used to ensure that we have a clean slate for each one. As we extended from Zend_Test_PHPUnit_ControllerTestCase rather than from PHPUnit_Framework_TestCase, we are able to leverage functionality specifically designed to make testing controllers easier. We use this in setUp() to set the property bootstrap to an instance of Zend_Application, which is then used in the tests themselves.

Each test is a method that starts with the word test, like this one:


    public function testLoginDisplaysAForm()
    {
        $this->dispatch('/auth/index');
        $this->assertResponseCode(200);
        $this->assertQueryContentContains('h1''Login');
        $this->assertQuery('form#login'); // id of form
    }

We start by calling dispatch() to run the correct action and then we use the various assert methods to check that the result is what we expect. The assertResponseCode method checks that we didn't error as the errorController will set the code to 500 or 404. We can then use the assertQuery methods to check what has been rendered to the response object. These use DOM paths to select a specific element. The call to assertQueryContentContains allows us to check the text within the H1 element is what we expect and the assertQuery just checks that the element is on the page.

That's it.

This is just the tip of the iceberg and I strongly suggest that you have a read of the documentation to see for yourself how many different assertions you can use to check that your code is performing as expected.