Category Archives: Zend Framework

Translations of my Zend Framework Tutorial for ZF 1.10 & 1.11

Recently, a couple of people have very generously donated their time to translate my Zend Framework Tutorial into their native language to help their fellow countrymen.

Firstly, Mario Santagiuliana has provided an Italian translation: Introduzione allo Zend Framework.

Secondly, Radosław Benkel translated into Polish and I'm hosting the PDF here: Pierwsze kroki z Zend Framework.pdf.

Thank you both!

Remove index.php from your URL

One thing you may have noticed with Zend Framework projects that use the baseUrl() view helper to reference CSS and other static files is that it doesn't work if the URL contains contains index.php.

Let's explain, by using code from the tutorial.

In the layout.phtml file, we link to a CSS file like this:

<?php echo $this->headLink()->prependStylesheet($this->baseUrl().'/css/site.css'); ?>

This code uses a baseUrl() view helper that looks like this

<?php
class Zend_View_Helper_BaseUrl
{
function baseUrl()
{
$fc = Zend_Controller_Front::getInstance();
return $fc->getBaseUrl();
}
}

When you go to http://localhost/zf-tutorial/ you get an page that looks like this:

Screen short of Zend Framework tutorial

However, if you go to http://localhost/zf-tutorial/index.php, you get this:

zf-tutorial_no_css.jpg

This happens because the baseURL used in the href of the CSS link in the second case is /zf-tutorial/public/index.php/css/site.css rather than the correct /zf-tutorial/public/css/site.css.

Solution using mod_rewrite

One way to fix this is to use a mod_rewrite rule in your .htaccess file:


RewriteCond %{THE_REQUEST} ^[A-Z]{3,9} /([^/]+/)*index.php
RewriteRule ^index.php(.*)$ /zf-tutorial/public$1 [R=301,L]

I did have to put the full subdirectory into the rewrite rule to get it to work.

The quick and dirty solution

At the top of your index.php file take out the index.php from the REQUEST_URI:

$_SERVER["REQUEST_URI"] = str_replace('index.php','',$_SERVER["REQUEST_URI"]);

This obviously works, but feels a bit "hacky"! There's probably other ways to solve it too, but once I found the rewrite rule, I stuck with it.

Handling exceptions in a Front Controller plugin

If you have a Zend Framework Front Controller plugin which throws an exception, then the action is still executed and then the error action is then called, so that the displayed output shows two actions rendered, with two layouts also rendered. This is almost certainly not what you want or what you expected.

This is how to handle errors in a Front Controller plugin:

  1. Prefer preDispatch() over dispatchLoopStartup() as it is called from within the dispatch loop
  2. Catch the exception and the modify the request so that the error controller's error action is dispatched.
  3. Create an error handler object so that the error action works as expected.

This is the code:

<?php

class Application_Plugin_Foo extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
try {

// do something that throws an exception

} catch (Exception $e) {
// Repoint the request to the default error handler
$request->setModuleName('default');
$request->setControllerName('error');
$request->setActionName('error');

// Set up the error handler
$error = new Zend_Controller_Plugin_ErrorHandler();
$error->type = Zend_Controller_Plugin_ErrorHandler::EXCEPTION_OTHER;
$error->request = clone($request);
$error->exception = $e;
$request->setParam('error_handler', $error);
}
}

}

That's it.

View helpers in modules

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

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

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

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

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.

ZendTool providers in ZF2 (dev1)

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 ZendToolProjectProviderAbstractProvider. 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 AkrabatDbSchemaManager is available on my github account. The readme contains instructions on how to set it up with ZF2 too.

Setting up ZF2's ZendTool side by side with ZF1

Note: this article does not refer to the released version of Zend Framework 2!.

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 ZendTool command line tool.

Rather unhelpfully, ZF2's ZendTool 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 ZendTool'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 ZendTool will run and not be affected by any ZF1 configurations you may have!

ZF1 and ZF2's cli scripts both work!