Busy

4th August 2008

I'm kind of neglecting this blog at the moment. Sorry.

I was away last week and I'm head down into editing Zend Framework in Action. It's amazing how many details the technical proof reader and our copy editor have found. The book is going to be so much better as a result. I need to go through each suggestion though and ensure that the meaning hasn't changed and to implement the suggested technical changes.

When I get a little time, I'll try and write up what I've learnt recently about testing with Zend_Test_PHPUnit_ControllerTestCase which has reduced the testing scaffolding that I had by around 50% or so.

Recursion

16th July 2008

PHPWomen are running an article contest at the moment. All you have to do is write an article for the Best Practices forum and you could win a Zend Studio for Eclipse license and a a 1-year subscription to Linux Pro magazine!

Obviously, never one to miss an opportunity to win free swag, I've entered with an article on recursion.

Now it's your turn. Write a article on a best practice when coding PHP, but don't make it too good as I want to win!

Notes on Zend_Cache

11th July 2008

Recently I needed to speed up a legacy project that makes a lot of database calls to generate each page. After profiling, I discovered that 90% of the database calls returned data that rarely changed, so decided to cache these calls. One of the nice things about Zend_Framework is that its use-at-will philosophy means that you can use any given component with minimal dependencies on the rest of the framework code.

In my case, I wanted to use Zend_Cache, so I needed Zend/Cache/*, Zend/Cache.php, Zend/Loader/*, Zend/Loader.php and Zend/Exception.php and didn't bother with any other part of the framework.

The application I'm speeding up is completely procedural with lots of include files and no virtually no classes anywhere other than in the lib/ directory! I wanted to minimise the disruption to the current code and so it seemed that a simple static class that provided a set of proxy functions to an underlying Zend_Cache object would be easiest. I also provided a mechanism to turn off the cache using a simple boolean that could be set when initialising the class.

The class is called TheCache:


class TheCache
{
    /**
     * @var boolean
     */
    static protected $_enabled false;

    /**
     * @var Zend_Cache_Core
     */
    static protected $_cache;
    
    static function init($enabled$dir$lifetime=7200) {
        self::$_enabled $enabled;
        if(self::$_enabled) {
            require_once 'Zend/Cache.php';

            $frontendOptions = array(
               'lifetime' => $lifetime,
               'automatic_serialization' => true, 
            );
            $backendOptions = array(
                'cache_dir' => $dir, 
                'file_name_prefix' => 'thecache', 
                'hashed_directory_level' => 2, 
            );
            self::$_cache Zend_Cache::factory('Core''File'$frontendOptions$backendOptions);
        }
    }
    
    static function getInstance() {
        if(self::$_enabled == false) {
            return false;
        }
        return self::$_cache;
    }
    
    static function load($keyName) {
        if(self::$_enabled == false) {
            return false;
        }
        return self::$_cache->load($keyName);
    }
    
    static function save($keyName$dataToStore) {
        if(self::$_enabled == false) {
            return true;
        }
        
        return self::$_cache->save($dataToStore$keyName);
    }

    static function clean()
    {
        if(self::$_enabled == false) {
            return;
        }    
        self::$_cache->clean();   
    }
}

The init() function is used to set up the cache to use files stored in the supplied directory. The only configuration is to choose whether the cache is enabled and the lifetime of all objects stored in it. Note that this is where we get specific to the problem in hand. In this specific case, I didn't need different lifetimes for each item stored in the cache, which nicely simplified everything.

Let's look at how it is used. First we initialise the cache in an include file that happens to be included for every request:


$cacheEnabled = (bool)getenv('THE_CACHE_ENABLED') ? getenv('THE_CACHE_ENABLED') : false;
TheCache::init($cacheEnabledTMP_DIR.'/the-cache/');

I use an environment variable set using SetEnv within my Apache virtual hosts to determine if the cache should be enabled or not, so we use getenv() to retrieve the value and then call TheCache::init(). The use of THE_CACHE_ENABLED allows me to disable the cache on my development machine, and have it enabled on my live server without having any code changes. Obviously, TMP_DIR is defined previously.

Now that we have initialised the cache, we can use it. Within this application, we generally use the ADODB database classes to generate arrays of data from the database along these lines:


$sql 'SELECT x,y FROM z WHERE a=b';
$rs $db->Execute($sql);
$data $rs->GetArray();

To cache this information, I changed the code to look like this:


$keyName 'data-z-a-b'// unique name describing this data set
$data TheCache::load($keyName)
if($data === false) { 
    $sql 'SELECT x,y FROM z WHERE a=b';
    $rs $db->Execute($sql);
    $data $rs->GetArray();
    TheCache::save($keyName$data);
}

Firstly we invent a unique name for the dataset and assign to $keyName and then load the data from the cache object using the load() function. If the data is cached, then we are done. If not, we perform the SQL query to get the data and then store it into the cache using save(). Rinse and repeat for each operation whose results you want to cache.

And that's all there is to it.

License for code published on this site

25th June 2008

A few people have asked me, so I thought I'd better make it explicit.

All non-trivial code on this site is released under the New BSD license as noted here.

For code examples that are a line or two long, I consider them added to the public domain.

UK Readers: Don’t buy petrol from BP or ESSO

6th June 2008

This is very off-topic, so feel free to skip!

Received a round-robin today via email. As I don't send on such things, I thought I'd mention it here as I'm getting fed up with the price of petrol, especially given the profit that the big oil companies have recently announced:

Received from a good friend so leave the rest to you!

See what you think and pass it on if you agree with it.

We are hitting £123.9 a litre in some areas now, soon we will be faced with paying £2.00 a ltr. Philip Hollsworth offered this good idea:

This makes MUCH MORE SENSE than the 'don't buy petrol on a certain day' campaign that was going around last April or May! The oil companies just laughed at that because they knew we wouldn't continue to hurt ourselves by refusing to buy petrol. It was more of an inconvenience to us than it was a problem for them. BUT, whoever thought of this idea, has come up with a plan that can really work.

Please read it and join in!

Now that the oil companies and the OPEC nations have conditioned us to think that the cost of a litre is CHEAP, we need to take aggressive action to teach them that BUYERS control the market place not sellers. With the price of petrol going up more each day, we consumers need to take action. The only way we are going to see the price of petrol come down is if we hit someone in the pocket by not purchasing their Petrol! And we can do that WITHOUT hurting ourselves. Here's the idea:

For the rest of this year DON'T purchase ANY petrol from the two biggest oil companies (which now are one), ESSO and BP.

If they are not selling any petrol, they will be inclined to reduce their prices. If they reduce their prices, the other companies will have to follow suit. But to have an impact we need to reach literally millions of Esso and BP petrol buyers. It's really simple to do!!

…(snip boring bit)…

If this makes sense to you, please pass this message on.

PLEASE HOLD OUT UNTIL THEY LOWER THEIR PRICES TO THE 69p a LITRE RANGE

It's easy to make this happen. Just forward this email, and buy your petrol at Shell, Asda, Tesco, Sainsburys, Morrisons, Jet etc. i.e. boycott BP and Esso

I'll write about something a bit more on topic soon!

Zend Framework URLs without mod_rewrite

3rd June 2008

Some of our Zend Framework applications have to run on IIS without ISAPI_Rewrite installed. In these cases we need urls of the form http://www.example.com/index.php?module=mod&controller=con&action=act. I couldn't get this to work out of the box with Zend Framework 1.5, so wrote my own router called App_Controller_Router_Route_RequestVars.

This code obviously only supports what I needed and I've only tested it on IIS for Windows 2003 Server, so you may need to tweak to make it do what you want! Feel free to share any fixes :)

This is the code:

<?php

/** Zend_Controller_Router_Exception */
require_once 'Zend/Controller/Router/Exception.php';

/** Zend_Controller_Router_Route_Interface */
require_once 'Zend/Controller/Router/Route/Interface.php';

/**
 * Route
 *
 * @package    App_Controller
 * @subpackage Router
 * @copyright  Copyright (c) 2008 Rob Allen (rob@akrabat.com)
 */
class App_Controller_Router_Route_RequestVars implements Zend_Controller_Router_Route_Interface
{
    protected $_current = array();

    /**
     * Instantiates route based on passed Zend_Config structure
     */
    public static function getInstance(Zend_Config $config)
    {
        return new self();
    }

    /**
     * Matches a user submitted path with a previously defined route.
     * Assigns and returns an array of defaults on a successful match.
     *
     * @param string Path used to match against this routing map
     * @return array|false An array of assigned values or a false on a mismatch
     */
    public function match($path)
    {
        $frontController Zend_Controller_Front::getInstance();
        $request $frontController->getRequest();
        /* @var $request Zend_Controller_Request_Http */
        
        $baseUrl $request->getBaseUrl();
        if (strpos($baseUrl'index.php') !== false) {
            $url str_replace('index.php'"$baseUrl);
            $request->setBaseUrl($url);
        }
        
        $params $request->getParams();
        
        if (array_key_exists('module'$params)
                || array_key_exists('controller'$params)
                || array_key_exists('action'$params)) {
            
            $module $request->getParam('module'$frontController->getDefaultModule());
            $controller $request->getParam('controller'$frontController->getDefaultControllerName());
            $action $request->getParam('action'$frontController->getDefaultAction());

            $result = array('module' => $module, 
                'controller' => $controller, 
                'action' => $action, 
                );
            $this->_current $result;
            return $result;
        }
        return false;
    }

    /**
     * Assembles a URL path defined by this route
     *
     * @param array An array of variable and value pairs used as parameters
     * @return string Route path with user submitted parameters
     */
    public function assemble($data = array(), $reset=false)
    {
        $frontController Zend_Controller_Front::getInstance();
        
        if(!array_key_exists('module'$data) && !$reset 
            && array_key_exists('module'$this->_current)
            && $this->_current['module'] != $frontController->getDefaultModule()) {
            $data array_merge(array('module'=>$this->_current['module']), $data);
        }
        if(!array_key_exists('controller'$data) && !$reset 
            && array_key_exists('controller'$this->_current) 
            && $this->_current['controller'] != $frontController->getDefaultControllerName()) {
            $data array_merge(array('controller'=>$this->_current['controller']), $data);
        }
        if(!array_key_exists('action'$data) && !$reset 
            && array_key_exists('action'$this->_current)
            && $this->_current['action'] != $frontController->getDefaultAction()) {
            $data array_merge(array('action'=>$this->_current['action']), $data);
        }
        
        $url ";
        if(!empty($data)) {
            $urlParts = array();
            foreach($data as $key=>$value) {
                $urlParts[] = $key '=' $value;
            }
            $url '?' implode('&'$urlParts);
        }

        return $url;
    }
}

This route is then added to the Front Controller's router in my bootstrap like this:


$frontController Zend_Controller_Front::getInstance();
$router $frontController->getRouter();
$router->addRoute('requestVars', new App_Controller_Router_Route_RequestVars());

Hopefully this is a useful starting point for others who can't use mod_rewrite with Zend Framework.

Top Tip: XHTML with Zend Form Elements

29th May 2008

When you render a Zend_Form, the elements will render to HTML compliance rather than XHTML compliance, even if you have < ?php echo $this->doctype('XHTML1_STRICT');?> at the top of your layout script. Practically, this means that all the input elements do not end in "/>".

To resolve this, you need to call the doctype() view helper prior to rendering your form.

Within my projects, I do this within a front controller plug-in called ViewSetup that looks a little like this:


class App_Controller_Plugin_ViewSetup extends Zend_Controller_Plugin_Abstract
{
    public function dispatchLoopStartup(Zend_Controller_Request_Abstract $request)
    {
        // setup the layout
        Zend_Layout::startMvc(); 
        
        $viewRenderer Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
        $viewRenderer->init();
        
        $view $viewRenderer->view;
        $view->doctype('XHTML1_TRANSITIONAL');

        $view->headMeta()->appendHttpEquiv('Content-Type''text/html;charset=utf-8′);
    }
}

Obviously, this class lives in the file library/App/Controller/Plugin/ViewSetup.php.

As you can see, I also set up the meta tag for the content-type to UTF-8 ready for rendering later.

Simple Zend_Form File Upload Example Revisited

16th May 2008

I've been thinking about the Simple Zend_Form File Upload Example that I discussed last month.

To recap, if you haven't read the comments, if the form fails to validate for some reason then you get a nasty error:

Warning: htmlspecialchars() expects parameter 1 to be string, object given in /Users/rob/Sites/akrabat/Zend_Form_FileUpload_Example/lib/Zend/View/Abstract.php on line 786

Essentially, what is happening is that the App_Form_Element_File class that we wrote assigns the $_FILES array to the $value parameter for the form element. On redisplay of the form, the formFile view helper then calls the escape() view helper passing in the $value when rendering the <input> element. The escape() view helper calls htmlspecialchars() which throws the warning about $value not being a string.

*whew!*

What we need is something that's an array when the data is valid, but can also look like a string to htmlspecialchars(). This got me thinking about the SPL and creating an object for the data from the $_FILES array.

Let's call this object App_Form_Element_FileValue and store it in lib/App/Form/Element/FileValue.php:

<?php

class App_Form_Element_FileValue extends ArrayObject
{
    public function __toString()
    {
        $result ";
        if(isset($this->name)) {
            $result $this->name;
        }
        return $result;
    }
}

The ArrayObject class is part of the SPL and handily provides a set of functions that enables the object to work with most functions that we like to use with an array including the ability to access the data using array notation. We implement the PHP5 magic function __toString() so that htmlspecialchars() will get a string from the object when it asks for one which nicely knocks that problem on the head.

To integrate it into the code, we need to modify App_Form_Element_File::isValid() from:


    public function isValid($value$context null)
    {
        // for a file upload, the value is not in the POST array, it's in $_FILES
        $key $this->getName();
        if(null === $value) {
            if(isset($_FILES[$key])) {
                $value $_FILES[$key];
            }
        }
        // continues…

to


    public function isValid($value$context null)
    {
        // for a file upload, the value is not in the POST array, it's in $_FILES
        $key $this->getName();
        if(null === $value) {
            if(isset($_FILES[$key])) {
                $value = new App_Form_Element_FileValue($_FILES[$key]);
            }
        }
        // continues…

We also need to modify the validator App_Validate_ValidFile::isValid() function as it's rather too rigourous in its checking. We currently check that $value is an array using is_array():


    public function isValid($value)
    {
        // default value and error is "no file uploaded"
        $valueString ";
        $error UPLOAD_ERR_NO_FILE;
        
        if(is_array($value) && array_key_exists('error'$value)) {
            // set the error to the correct value
            $error $value['error'];
            
            // set the %value% placeholder to the uplaoded filename
            $valueString $value['name'];
        }
        // continues…

As $value is now an object of type App_Form_Element_FileValue, we need to change the test in the if statement to:


    public function isValid($value)
    {
        // default value and error is "no file uploaded"
        $valueString ";
        $error UPLOAD_ERR_NO_FILE;
        
        if((is_array($value) || $value instanceof ArrayObject) 
            && array_key_exists('error'$value)) {
            // set the error to the correct value
            $error $value['error'];
            
            // set the %value% placeholder to the uplaoded filename
            $valueString $value['name'];
        }
        // continues…

Note that we test for an instance of ArrayObject as that is where the functionality of array behaviour is implemented and is more generic in case we need to reuse this code with another object that behaves like an array.

Those are the only changes needed to elegantly remove the error message.

Here's a zip file of this project with the above changes: Zend_Form_FileUpload_Example_Revisited.zip (It includes Zend Framework 1.5.2 which is why it's 3.9MB big).

Test it out and see if it works for you as well as it works for me !

A review of “Learning PHP Data Objects”

4th May 2008

packt_learning_pdo.png

Packt Publishing recently sent me a couple of books to review. This post is about the second one I received, Learning PHP Data Objects by Dennis Popel. I was excited to receive this book as PDO underlies a lot of the Zend_Db_Adapter objects that I use in my day to day programming. It seemed like a good idea that I should know more about it.

Overview of the book

This book starts out introducing PDO and then takes us on a tour through all its features including error handling and prepared statements along with more advanced features like scrollable cursors.

The first chapter introduces PDO and shows the basics of how to use it to connect to a database, issue a query and retrieve the resultant data. The author also makes the distinction about how the code he is showing is example code and provides pointers to what's missing (such as proper error messages) if you were to use the code in production. I liked this very much as it's important as authors that we realise that inexperienced readers have a tendency to copy/paste examples and use as-is. The introductory chapter closes with a look at prepared statements and so covers all the high points of PDO.

Having introduced the subject, chapter 2 looks in detail at using PDO to connect to a database. This chapter is more tutorial-ish and the code is presented to be typed in. A worked example of a book database is used from here on throughout the rest of the book. The author appears to expect the user to learn what the code does by reading the comments within the code body, as there is little explanation of what the code does in the prose. One thing that's tricky is that when there is explanation after a code block, it's hard to work out which bit of the block the author is referring to. On the whole though, the tutorial nature provides step-by-step progress for people who learn best that way.

Chapter 3 covers error handling. I was pleased to see this important topic given an entire chapter and so early in the book. Error handling is not an after thought here. This chapter provides a good discussion of the types of errors that you will encounter and then provides instructions on how to handle them. Again, the tutorial aspect of the book is emphasised with lots of code to type in. Some of it is in bold, but I'm not sure why as no reference is made to it in the prose. This chapter also starts a dangerous trend where four pages of code is presented (without line numbers) and then the following page or so dissects the code with reference to line numbers that do not exist! This makes the explanation of the code really hard to follow, especially when the you get to the section about lines 189 to 191… This chapter also continues the tutorial by building more pages and showing you how to check for errors along the way.

Chapter 4 studies prepared statements and shows how to use them. Positional and named placeholders are looked at, along with how to insert blobs using bound parameters. In this chapter, the bold sections in the code make more sense as they are referenced in the prose. Again, we have pages of code with no line numbers and then are asked to study lines 60 to 73. The information in this chapter is nevertheless very good and I learnt stuff :)

Having looked at getting data into the application, chapter 5 looks at retrieval and rowsets. This chapter covers counting the number of rows returned and limiting rowsets. It's much shorter as it covers less topics and I'm glad the author didn't pad the chapter just to get the page count up! Chapter 6 is also relatively short, but this time covers a lot of ground. These are advanced topics and include connection attributes, buffered queries, dsn files for connections and transactions. The transactions section gets the most space and is covered quite less, though with rather less prose for the volume of code than I would have liked.

The last chapter in the book is a bit of an odd ball as it looks as designing a model within an MVC application. To me this didn't fit with the specialist PDO nature of the book, and I'd have rather have had more space devoted to transactions or database specific issues.

In summary

This book is an good, detailed tutorial for understanding PDO. It is not a reference book and so relatively hard to dip into to look up a specific thing. If you learn by starting from scratch and working your way through, then this is a very good book. The biggest distraction for me was the long code listings. It would have been better to have either put in line numbers or interspersed the code with the textual explanations.

A review of “Object-Oriented Programming with PHP5″

4th May 2008

packt_oop_with_php5.png
Packt Publishing recently sent me a couple of books to review, so let's start with Object-Oriented Programming with PHP5 by Hasin Hayder. According to the introduction, the book is intended for beginners to intermediate PHP5 programmers and the first chapter has a good introduction to what object oriented programming is and why you would want to use it.

Overview of the book

Chapters two and three of the book are an excellent discussion of how objects work in PHP and cover everything from the use of $this through to object cloning and fluent interfaces. Chapter 4 gives a basic introduction to design patterns, however I feel that it covers too many patterns in not enough detail. It does provide the terminology required though for communicating with other developers about design patterns which will also help when searching the web for more information.

Chapter 5 then introduces reflection and unit testing. This is an odd couple to put together and the entire chapter feels like the author was padding. There are long pages of code with very little explanation of what the code does and no example of real-world usage of the Reflection classes. The unit testing half provides a good introduction to unit testing and shows how to use it. Rather oddly, there's a 10 page table listing all the PHPUnit assert functions which would have been better left to the PHPUnit documentation as the table provides no added value.

Chapter 6 introduces the SPL. Like the design patterns chapter, it covers a lot of objects in relatively shallow depth. Good code examples are provided to show how to use the SPL objects, but again, there's not really enough textual explanation of the code or discussion of real-world usage. Similarly, chapter 7 covers object oriented database access with MySQLi, PDO, ADOdb and MDB2. It finishes up with a couple of pages ADOdb's ActiveRecord object. It's all a bit rushed.

XML is introduced in chapter 8 with SimpleXML and DOM are looked at. Again, a very basic introduction is provided. For example XPath is covered in 3 pages and I still have no idea how to actually use it in a project. The final chapter in the book covers the MVC design pattern as implemented in the author's home-grown framework. As with the rest of the book, a lot of code is presented with little explanation of why the code has been written. For example, the view class presented provides __get(), but not __set(). There is no explanation as to why __get() would be required in a view class, but not __set().

In summary

This book is a whistle-stop tour through object oriented concepts with PHP5 and I'm left with mixed feelings about it. The best parts are chapters 2 and 3 which provide a solid introduction to objects and classes. The rest of the book covers too many disparate topics in very little depth to be useable on its own for those topics.