View helpers in modules

13th December 2010

I came across a situation last week where I needed to access a view helper that was in the default module's views/helpers directory when I was in another module. This came about because my layout.phtml uses a view helper that is in application/views/helpers.

By default, it doesn't work and you get an error along the lines of:

Plugin by name 'LoggedInAs' was not found in the registry; used paths:
Plan_View_Helper_: /www/funkymongoose/habuplan/application/modules/plan/views/helpers/
Zend_View_Helper_: Zend/View/Helper/

The fix is simple enough. All you need to do is add this line to your application.ini file:

resources.view.helperPath.Zend_View_Helper = APPLICATION_PATH "/views/helpers"

This will then ensure that the view helpers in your application/views/helpers directory are always available.

This also works for accessing the view helpers in any other module too where the syntax looks like:

resources.view.helperPath.{Module}_View_Helper =
    APPLICATION_PATH "/modules/{module}/views/helpers"

Finally, don't forget that the order is important if you have view helpers of the same name in different modules. Personally, I would try to avoid that if you possibly can :)

Local config files and Zend_Application

29th November 2010

A friend of mine recently had a requirement where she wanted to have two config files loaded into Zend_Application, so that the specific settings for the server were not stored in the version control system.

Hence she has two config files: application.ini and local.ini where local.ini is different on each server.

Update:
As takeshin points out with ZF 1.10 or later, an easier solution to this is to pass both ini files into Zend_Application:

$application = new Zend_Application(
    APPLICATION_ENV,
    array(
        'config' => array(
            APPLICATION_PATH '/configs/application.ini',
            APPLICATION_PATH '/configs/local.ini'
        )
    )
);

Original content:

The obvious way to approach this problem is to load the two files within index.php, merge them and then pass the merged config file to Zend_Application.

The code to do this looks like this:

require_once 'Zend/Application.php';
require_once 'Zend/Config/Ini.php';

$config = new Zend_Config_Ini(APPLICATION_PATH '/configs/application.ini'APPLICATION_ENV,
            array('allowModifications'=>true));
$localConfig = new Zend_Config_Ini(APPLICATION_PATH '/configs/local.ini'APPLICATION_ENV);
$config->merge($localConfig);
$config->setReadOnly();

// Create application, bootstrap, and run
$application = new Zend_Application(
    APPLICATION_ENV,
    $config
);
$application->bootstrap()
            ->run();

I've included the require_once 'Zend/Application.php'; call for context.

As you can see, we create two Zend_Config objects, one for application.ini and one for local.ini and we load up the correct APPLICATION_ENV section in both cases. This means that local.ini must have the same set of "master" environment sections that application.ini has.

For application.ini, we set the option "allowModifications' so that we can then use the merge() method to override the $config with the new data within $localConfig.

Having merged the local config into the main one, we can then setReadOnly() to ensure that the config isn't changed anymore.

Finally, when we instantiate Zend_Application, we pass in our $config object rather than the name of the config file and then Zend_Application does the right thing.

Validating UK Postcodes

15th November 2010

I'm sure everyone else already knows this, but I've recently discovered that Zend_Validate_PostCode doesn't work with UK postcodes unless you first remove the space between the first and second parts. This is due to a bug in the underlying regular expresssions that are provided by CLDR.

It's easy enough to add a filter to remove the space, but I'm a little worried that when (and if) it gets fixed, will the fixed version Zend_Validate_PostCode then fail to validate postcodes without the space? In theory it should as the space is part of the spec. I'd hate my code to unexpectedly break due to a valid bug fix.

I can easy work around this worry though by simply creating my own extension of Zend_Validate_PostCode, so that's what I did:

library/App/Validate/PostCode.php:

<?php

/**
 * @see Zend_Validate_PostCode
 */
require_once 'Zend/Validate/PostCode.php';

class App_Validate_PostCode extends Zend_Validate_PostCode
{
    public function isValid($value)
    {
        $this->_setValue($value);
        if (!is_string($value) && !is_int($value)) {
            $this->_error(self::INVALID);
            return false;
        }

        if ($this->getLocale() == 'en_GB') {
            // From PEAR's Validate_UK - http://pear.php.net/package/Validate_UK
            $value strtoupper(str_replace(' '''$value));
            $format "/^([A-PR-UWYZ]([0-9]([0-9]|[A-HJKSTUW])?|[A-HK-Y][0-9]"
                "([0-9]|[ABEHMNPRVWXY])?)[0-9][ABD-HJLNP-UW-Z]{2}|GIR0AA)$/";
        } else {
            $format $this->getFormat();
        }
        
        if (!preg_match($format$value)) {
            $this->_error(self::NO_MATCH);
            return false;
        }

        return true;
    }
}

And of course, with the benefit of ZF's plugin loader system, once I'm happy that the bug is fixed in Zend_Validate_PostCode, I'll be able to just remove my override and Zend_Form will carry on working.

Another option is to use Paul Court's Pmc_Postcode_Validate which has also been written to the UK spec.

Validating dates

8th November 2010

I discovered recently that Zend Framework 1's Zend_Date has two operating modes when it comes to format specifiers: iso and php, where iso is the default.

When using Zend_Validate_Date in forms, I like to use the php format specifiers as they are what I'm used to and so can easily know what they mean when reviewing code that I wrote months ago.

My code looks something like this:

$subForm->addElement('text''start_date', array(
            'filters' => array('StringTrim''StripTags'),
            'required' => true,
            'label' => 'Start date',
            'validators' => array(
                array('Date'true, array('format'=>'j F Y')),
            ),
        ));

As you can see, I want the text field to be filled in with a date like "8 November 2010".

This is easy to achieve using this code in your Bootstrap.php like this:

function _initDateFormat()
{
    Zend_Date::setOptions(array('format_type' => 'php'));
}

Note that this is a static method call and so it affects all instances of Zend_Date.

I also discovered that when you are in php formatting mode, then all the Zend_Date formatting constants like Zend_Date::MONTH do not work. This was a problem as I have other code in the project that uses them.

There are a number of choices.

One option is to change the formatting mode when you need to. As it is a static, you need to keep track of what you're doing, so the code looks like this:

$currentOptions Zend_Date::setOptions();
$currentFormatType $currentOptions['format_type'];
Zend_Date::setOptions(array('format_type' => 'iso'));

// You can now use Zend_Date::MONTH, ZEND_DATE::ISO etc

// After use, reset the format type back to what it was originally set to
Zend_Date::setOptions(array('format_type' => $currentFormatType));

Similarly, we can override Zend_Validate_Date with the same logic:

class App_Validate_Date extends Zend_Validate_Date
{
    public function isValid ($value)
    {
        $currentOptions Zend_Date::setOptions();
        $currentFormatType $currentOptions['format_type'];
        Zend_Date::setOptions(array('format_type' => 'php'));

        $valid parent::isValid($value);

        Zend_Date::setOptions(array('format_type' => $currentFormatType));
     }
}

I also have two other requirements for date validation in this project:

  1. An empty $value fails validation.
  2. Regardless of the format that's been set, it would be helpful to always allow a valid Y-m-d formatted date.

Whilst talking on IRC about date validation issues that I was having, I also found out that a $value of 1-1-1TEST passes validation! This is noted in issue ZF-7583.

Allowing an empty $value is easy to add to my App_Validate_Date. Similarly, allowing a Y-m-d format is also fairly easy by resetting the formatting and then calling parent::isValid() again.

However, I really don't want '1111' or '1-1-1TEST' or to be considered a valid date! I couldn't see an easy way to fix this, so I went for the easy way out and wrote my own validator:

class App_Validate_Date extends Zend_Validate_Date
{
    public function isValid ($value)
    {
        $this->_setValue($value);
        
        if (empty($value)) {
            return true;
        }

        $valid $this->_testDateAgainstFormat($value$this->getFormat());
        if (!$valid) {
            // re-test for Y-m-d as this format is always a valid option
            $valid $this->_testDateAgainstFormat($value'Y-m-d');
        }

        if ($valid) {
            return true;
        }
        $this->_error(self::INVALID_DATE);
        return false;
    }

    protected function _testDateAgainstFormat($value$format)
    {
        $ts strtotime($value);
        if ($ts !== false) {
            $testValue date($format$ts);
            if ($testValue == $value) {
                return true;
            }
        }
        return false;
    }
}

Obviously this code is highly unlikely to work if you need to validate localised dates! However, it solves my needs and it's useful to document here for when I forget how I solved these issues!

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.