Vagrant in Zend Framework 1

9th May 2012

I recently added support for vagrant to the Zend Framework codebase to enable easier testing. I was motivated by some work the joind.in folks have done to get a working development environment for joind.in development using Vagrant.

Vagrant is a fantastic tool that enables you to manage and run virtual machines from the command line, including automatic provisioning of them using puppet or chef. The really cool thing about it however from my point of view is that vagrant automatically sets up the VM with a folder called /vagrant that holds the code on your local hard drive from where you started the VM. This means that you can continue to edit your code in your local editor/IDE and test it within the VM easily.

I highly recommend checking it out.

ZF1's Vagrant set up

The Vagrant set up for ZF1 is designed for testing ZF1 against multiple PHP versions. As such it sets up a simple Ubuntu VM with the required toolchain for compiling PHP and provides a script called php-build.sh which will download and build any PHP 5.2, 5.3 or 5.4 version that you are interested in. I based this script on information in Derick Rethans' excellent Multiple PHP versions set-up article.

One thing that I discovered was that by default, the VM cannot create symlinks in /vagrant. The way to solve this is to add the following to the Vagrantfile:

  config.vm.customize [
    "setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"
  ]

I chose to use puppet to install the required packages and created a simple default.pp configuration. I'm sure this isn't optimal, but it works :)

Running ZF1 unit tests

The process to set up ZF1 to run unit tests in a VM using PHPUnit 3.4, follow these steps:

1. Install requirements for running the VM:

2. Checkout the ZF1 repository:

    $ svn checkout http://framework.zend.com/svn/framework/standard/trunk zf1-dev
    $ cd zf1-dev

3. Start the process by running Vagrant.

    $ vagrant up

This will take a long while as it has to download a VM image and then provision it. Once it has finished, it will exit and leave you back at the command prompt.

4. SSH into the VM

    $ vagrant ssh

Vagrant sets up key-less ssh connections to the VM that "just works" :)

5. Build a version of PHP.

    $ php-build.sh 5.3.11

This also takes a while as it compiles PHP for you! It also installs PHPUnit 3.4 as that's the version we need to unit test ZF1.

Each version of PHP that you compile is stored in /usr/local/php/{version number}. You can compile any version; I have 5.2.4, 5.2.12, 5.3.3 and 5.3.11 installed at the moment…

6. Select PHP to use:

   $ pe 5.3.11

pe is a handy shell function that Derick wrote that changes your PHP environment to whichever version your specify.

7. Run tests

   $ cd /vagrant/tests
   $ php runtests.php

Alternatively, you can run each component's tests individually:

   $ phpunit --stderr -d memory_limit=-1 Zend/Acl/AclTest.php
   $ phpunit --stderr -d memory_limit=-1 Zend/Amf/AllTests.php
   (etc...)

Obviously, you repeat steps 5 through 7 for each version of PHP you want to test on.

To stop your Vagrant VM, exit the SSH shell and type `vagrant halt` or `vagrant suspend`. For more details on controlling your Vagrant VM, I recommend Lorna's article

Running unit tests on Zend Framework 1 is now considerably easier!

Unit testing Zend Framework 1

30th April 2012

As part of our release process for Zend Framework 1.12, I've been working through the unit tests and running them on PHP 5.2.4 as it seems that recent changes weren't being tested with that version. This isn't totally surprising as Open Source contributors are, almost by definition, interested in new things and so are much more likely to be running PHP 5.4 rather than 5.2! This is, of course, a compelling reason for using continuous integration and I'm quite excited with Travis-CI and we are using it with ZF2.

Installing PHPUnit 3.4

The first challenge that I encountered was that ZF1's unit test are not compatible with PHPUnit 3.6. As there are over 14,000 ZF1 unit tests which have been written since 2006, there hasn't been much enthusiasm for rewriting them to be PHPUnit 3.6 compatible. (However, if someone wants to volunteer, please contact me!)

As I have PHPUnit 3.6 installed for testing other projects, I needed to install PHPUnit 3.4 side-by-side with version 3.6 This turns out to be relatively easy and has been documented by Christer Edvartsen in his article Running Multiple Versions of PHPUnit.

I ran into one problem with his instructions though and needed this code to be added to the top of phpunit:

set_include_path(implode(PATH_SEPARATOR, array(
    __DIR__ '/../share/php',
    '/usr/share/php',
    get_include_path()
)));

Having done this though, PHPUnit 3.4 works correctly and we can run ZF1 unit tests.

Running the ZF1 unit tests

To run ZF1's unit tests, you first need to check out the code from the Subversion repository.

svn co http://framework.zend.com/svn/framework/standard/trunk/

Due to the number of tests and the memory that they take up, you should run tests for each component individually.

The command to use is:

phpunit34 --stderr -d memory_limit=-1 Zend/{Component}/AllTests.php

Note:

  • --stderr pipes PHPUnit's output to stderr which means that any tests that rely on header() will work. (i,e. don't test Zend_Session without it!)
  • -d memory_limit=-1 will turn off PHP's memory_limit setting. The ZF1 unit tests use a lot of memory, so this is easiest.
  • The tests rely on being set up correctly. This is done using AllTests.php so don't forget this. Tests will fail if you forget!

All that needs to happen now is that any failing tests are fixed!

One-to-many joins with Zend_Db_Table_Select

8th February 2012

Let's say that you want to set up a one-to-many relationship between two tables: Artists and Albums because you've refactored my ZF1 tutorial.

Let's assume that an artist has many albums. These are the basic table definitions:

artists table: id, artist
albums table: id, artist_id, title

When you list the albums, you obviously want to see the artist name rather than the id, so clearly you use a join!

Assuming you're using Zend_Db_Table, the easiest way is to turn off the integrity check and do a join in a mapper or table method.

Something like this:


class AlbumTable extends Zend_Db_Table_Abstract
{
    protected $_name 'album';

    public function fetchAllWithArtistName($order = array('title ASC'))
    {
        $select $this->select();
        $select->setIntegrityCheck(false);
        $select->from($this->_name);
        
        $select->joinLeft('artist''album.artist_id = artist.id', 
            array('artist_name' => 'name'));
        $select->order($order);
        
        $rows $this->fetchAll($select);
        return $rows;
    }
    
}

The row set returned will have all the columns from the albums table and one additional column called artist_name which is an alias of the name column from the artists table.

Zend_Config_Ini and a string

20th June 2011

One thing that is different between Zend_Config_Xml and Zend_Config_Ini is that with Zend_Config_Xml you can pass in an XML string as the first parameter of the constructor and it will work. This doesn't work with Zend_Config_Ini as we use parse_ini_file() under the hood.

With PHP 5.3 however there is is a new function called parse_ini_string() which will allow us to load arbitrary ini string into Zend_Config objects. This can't go into Zend Framework 1 though due to our PHP 5.2.4 minimum version requirement.

As I needed this for a project, I extended Zend_Config_Ini to support this feature, which means simply overloading a single method

class App_Config_Ini extends Zend_Config_Ini
{
    /**
     * Load the INI file from disk using parse_ini_file(). Use a private error
     * handler to convert any loading errors into a Zend_Config_Exception
     *
     * @param string $filename
     * @throws Zend_Config_Exception
     * @return array
     */
    protected function _parseIniFile($filename)
    {
        set_error_handler(array($this'_loadFileErrorHandler'));
        if (substr($filename, -4) == '.ini') {
            $iniArray parse_ini_file($filenametrue);
        } else {        
            $iniArray parse_ini_string($filenametrue);
        }
        restore_error_handler();

        // Check if there was a error while loading file
        if ($this->_loadFileErrorStr !== null) {
            /**
             * @see Zend_Config_Exception
             */
            require_once 'Zend/Config/Exception.php';
            throw new Zend_Config_Exception($this->_loadFileErrorStr);
        }

        return $iniArray;
    }
}

The actual change is to see if the last 4 characters of the filename are ".ini" and if they aren't then use parse_ini_string() instead of parse_ini_file(). The rest of the code is just error handling.

This is one area where I really like it when a class implements methods that done just one thing.

Exploring Zend_Paginator

4th May 2011

One area of displaying lists on web pages that I've generally disliked doing is pagination as it's a bit of a faff. Recently, I needed to do just this though as I couldn't delegate it as my colleague was too busy on other work. As a result, I thought that I should look into Zend_Paginator this time. Turns out that it's really easy to use and the documentation is great too.

The really useful thing about Zend_Paginator is that it uses adapters to collect its data. There are a variety of adapters, including array, dbSelect, dbTableSelect and iterator. The interesting ones for me being dbSelect and dbTableSelect as I use Zend_Db based data access layers.

This is how I used it with a Zend_Db based data mapper within TodoIt.

Setting up the paginator

My current method looks like this:

class Application_Model_TaskMapper
{
    public function fetchOutstanding()
    {
        $db $this->getDbAdapter();
        $select $db->select();
        $select->from($this->_tableName);
        $select->where('date_completed IS NULL');
        $select->order(array('due_date ASC''id DESC'));
        $rows $db->fetchAll($select);
        foreach ($rows as $row) {
            $task = new Application_Model_Task($row);
            $tasks[] = $task;
        }
        return $tasks;
    }

    // etc

This is pretty standard code for a data mapper. We select the data from the database and convert it to an array of entities. For the paginator to do its stuff though, we have to pass it the select object so that it can set the limit() on the select object.

The code therefore becomes:

    public function fetchOutstanding()
    {
        $db $this->getDbAdapter();
        $select $db->select();
        $select->from($this->_tableName);
        $select->where('date_completed IS NULL');
        $select->order(array('date_completed DESC''id DESC'));
        
        $adapter = new Zend_Paginator_Adapter_DbSelect($select);
        $paginator = new Zend_Paginator($adapter);
        return $paginator;
    }

As you can see, we create an instance of Zend_Paginator_Adapter_DbSelect which takes the $select object and the instantiate a Zend_Paginator and return it. The Zend_Paginator object implements Interator, so you can use it exactly like an array in a foreach loop and hence, in theory, your view script doesn't need to change.

However, the code that consumes TaskMapper expects an array of Task objects, not an array of arrays. To tell the paginator to create our objects, we extend Zend_Paginator_Adapter_DbSelect and override getItems() like this:

class Application_Model_Paginator_TaskAdapter extends Zend_Paginator_Adapter_DbSelect
{
    /**
     * Returns an array of items for a page.
     *
     * @param  integer $offset Page offset
     * @param  integer $itemCountPerPage Number of items per page
     * @return array
     */
    public function getItems($offset$itemCountPerPage)
    {
        $rows parent::getItems($offset$itemCountPerPage);
        
        $tasks = array();
        foreach ($rows as $row) {
            $task = new Application_Model_Task($row);
            $tasks[] = $task;
        }
        return $tasks;
    }
}

Here, we've used the entity-creation code that was in our original implementation of fetchOutstanding() and placed it in getItems().

Obviously we have to update fetchOutstanding() to use our new adapter, so we replace

$adapter = new Zend_Paginator_Adapter_DbSelect($select);

with

$adapter = new Application_Model_Paginator_TaskAdapter($select);

Now, when we iterate over the pagination object, we get instances of Task and all is well with the world.

Using the paginator

Now that we have a paginator in place, we need to use it. Specifically we need to tell the paginator which page number we want to view and how many items are on a page. Within TodoIt, this is done in the ServiceLayer object and looks something like this:

class Application_Service_TaskService
{
    // ...

    public function fetchOutstanding($page$numberPerPage 25)
    {
        $mapper = new Application_Model_TaskMapper();
        $tasks $mapper->fetchOutstanding();
        $tasks->setCurrentPageNumber($page);
        $tasks->setItemCountPerPage($numberPerPage);        
        return $tasks;
    }

    // ...

Clearly the $page parameter comes via the URL at some point, so the controller looks something like this:

class IndexController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $page $this->_getParam('page'1);

        $taskService = new Application_Service_TaskService();
        $this->view->outstandingTasks $taskService->fetchOutstanding($page);
        
        $messenger $this->_helper->flashMessenger;
        $this->view->messages $messenger->getMessages();
    }

    //...

and then the view uses a foreach as you'd expect.

Adding the paging controls

Finally, to complete a paged list, we have to provide the user a mechanism to select the next and previous pages along with maybe jumping to a specific page. This is done using a separate view script that you pass to the paginator. In your view script, you put something like:

<?php echo $this->paginationControl($this-> outstandingTasks,
                    'Sliding',
                    'pagination_control.phtml'); ?>

The first parameter is your paginator object. The second is the 'scrolling style' to use. There are four choices documented in the manual: All, Elastic, Jumping and Sliding. Personally, I have chosen to not display the page numbers themselves, so it doesn't matter which one I pick. The last parameter is the partial view script that you want to be rendered. This allows you to have complete customisation of the HTML.

Here's what I'm using which is based heavily on and example in the documentation:

<?php if ($this->pageCount): ?>
<div class="pagination-control">
<!-- Previous page link -->
<?php if (isset($this->previous)): ?>
  <a href="<?php echo $this->url(array('page' => $this->previous)); ?>">
    Previous
  </a> |
<?php else: ?>
  <span class="disabled">&lt; Previous</span> |
<?php endif; ?>

<!-- Next page link -->
<?php if (isset($this->next)): ?>
  <a href="<?php echo $this->url(array('page' => $this->next)); ?>">
    Next &gt;
  </a>
<?php else: ?>
  <span class="disabled">Next &gt;</span>
<?php endif; ?>
<span class="pagecount">
    Page <?php echo $this->current?> of <?php echo $this->pageCount?>
</span>
</div>
<?php endif; ?>

And that's it; I now have paginated tasks in TodoIt and as you can see, Zend_Paginator is very easy to use and, more importantly, simple to customise to your own needs.