Zend Framework tips and tricks

Posted January 29th, 2010 by Juozas

Zend Framework It’s always good to use great tools, but you need to make sure that you use them correctly, not just trying to code “just for it to work”. For this reason I decided to write down my usual list of things I mention when taking over some legacy project or just consulting someone how to start.

Most of the outlined problems and solutions are focused on testability, maintainability and other good code practices. If you are not familiar with them, I recommend read about them ASAP as there is big chance that you are doing those things described in this post and don’t even realize how wrong they are. Believe me, you will soon find yourself a way better developer.

Separate logic

This one is the most obvious one, but trust me, I have found cases of it in every single project I have worked before (more than 10 with Zend Framework in a past half year and counting). If it’s controller, don’t do business logic, if it’s model don’t base behavior on POST parameters etc. Same applies to forms, bootstrap, views, various helpers and many more other components – logic should be separate and have its place.

Move all logic from controller to a model or service. Use forms only to handle validation and filtering, not to actually process data and persist it in any fashion. Hide session and authentication handling in one place and provide API for other algorithms. I can go on, but I hope it’s starting to get pretty clear, or at least it will eventually when you start testing your code: when you need to setup frontController, request, cookie and mail server just to test a form you realize that something is really wrong.

Globals

If you haven’t seen this video, please do it immediately – you won’t regret it. Global state makes testing problematic and is completely opposite to what OOP proposes. This applies for use of $_SERVER, $_SESSION etc. values and all of them are accessible via request object (look at Zend_Controller_Request_Http) methods or separate classes, like Zend_Session.

Yet again I’m going to mention testability, because that’s something you always need to keep in mind. Test should not modify global variables, but rather inject mocked request/other objects which just return expected values (like client IP), because Zend Framework does pretty good job abstracting access to all global variables.

Use form values, not request

This one is both security and ease of coding issue, let’s look at this code sample:

$form = new Form();
 
if ($this->_request->isPost()) {
 
    if ($form->isValid($this->_request->getPost())
    {
        $model = new Model($this->_request->getPost());
        $model->save();
    }
    else
    {
        $form->populate($this->_request->getPost());
    }
}

After form is validated (and hence values are filtered), raw values from request are still used from $this->_request->getPost(). Not only do you lose Zend_Form functions like ignored elements (for submit buttons for example) but also none of the filters are applied (which you use, yes?). Also I can pass pretty much anything I want and model needs to do way more validation. Hence $form->getValues() should always be used as it returns form data with respect to all defined rules and filters.

Form populate() method is also very overused. This method is designed to set default values for a form, from for example GB entry (when editing something) apart from that, you simply don’t need to use it as method isValid() sets values for all elements, so there is no need to apply default values too.

Do not rely or use exit()/die()

One of the first things I do is remove all these exit() calls and handle such cases with exceptions or return statements. There is only very very limited amount of situations where you actually need to use one of those functions. Just for example, imagine this controller action code:

if (!$this->userHasPermissions())
{
    $this->_redirect('/');
}
 
$form = new Form_Add();
 
if (//submit form)
{
    // save with $form->getValues();
    $this->_redirect('/index');
    exit();
}
 
// do something else

First problem – thinking that $this->_redirect() will call exit() and hence nothing else will be executed. Even though this is true by default, this should be avoided in all cases. Not only it makes all post* events to not be fired, but also it makes testing impossible or incorrect. Zend_Test disables use of exit() in controller helpers, so in this case while testing you cannot test permissions checking as in all cases it will still execute later code. To fix this just add return in front of redirect (return $this->_redirect(‘/’)) and you are safe.

Furthermore, second exit() is completely useless and makes code even more untestable. Again you can just use return to cancel later code (view will not be rendered as viewRenderer helper checks for redirection header and does nothing if detects such). From my experience, after saving entry, there is only some assignments to view in left code so you won’t suffer a lot if you just leave redirect, of course without exit().

Use a framework, not PHP

This can sound wrong at first, but if you use a framework (Zend Framework in this case) don’t start throwing in hacks from 5 year old PHP apps. Like this (controller action):

$object = new Some_Object();
 
$image = $object->generateImage();
 
header ('Content-type: image/jpeg');
echo $image;

I don’t even know where to start… All this logic is incorporated in response object, so you can do things like:

$this->getResponse()->setHeader("Content-type", 'image/jpeg');
$this->getResponse()->setBody($image);

It might seem as same thing, but it’s not. Yet again you can actually test it, you are not working with global state (header() is global state function) and request dispatch process is left working as it should. Controllers do not output any data (hence no echo should be used) they just get request object and return response object, that’s it. In a similar fashion as exit() breaks dispatch process, outputting from controller does that too, so don’t forget to make sure that you preserve this flow.

Some small ones

Application.ini has a property includePaths, which is used to add additional paths to include path. Even though it works great, I still recommend not to use it because it will add those paths every time you create new application instance (true for 1.9, can change in future). If you try to do some controllers testing, you are probably going to instantiate it before every single test and after some hundreds of tests you will notice that somehow it’s getting slower and slower. That took me a few hours to find, though fix was easy, but still keep that in mind.

If you are using jQuery or any other javascript view helpers, take full power of them. By that I mean use functions like addJavascriptFile(), addJavascript(), addStylesheet(), addOnload() etc. to add some additional resources and code from views. If you add javascript straight to view everything will still work, but by using view helper container you will have all your code nicely placed in one place and not scattered all other the place.

Conclusion

This is just a small list of problems and issue I have seen in my work – there are way more to look at (you can share some tips too). I hope those will give you some idea how to work with Zend Framework in a clean fashion and you will soon find that your code is starting to look nicer and coding time is decreasing as everything is nicely separated and transparent to other code.

Trackbacks/Pingbacks

  1. Juozas Kaziukenas’ Blog: Zend Framework tips and tricks | Development Blog With Code Updates : Developercast.com
  2. Max’ Lesestoff zum Wochenende | PHP hates me - Der PHP Blog

Comments (15)

  1. Jeroen Keppens

    I think a lot of people started developing and are not in the “framework” mindset yet (but moving there). This is a good post on what they should be careful about. Nice one!

  2. Jellis

    Can you explain a little further about the ini file include paths being added every time?
    Are you simply inferring that you’re running unit tests which will instantiate the object hundreds of times within a small time period (possibly for performance/load testing) and chew up memory? The includePaths option doesn’t actually update any file.
    If so, because there’s an overlap in each request’s runtime, are these being added to memory? I guess this would make sense, but I kind of figured garbarge collection would remove that change at the expiration of the request.
    However, I may be confused.

  3. nuclear

    typo
    $this->getResponse()->setBody($mage);

  4. Can Aydoğan

    Very useful tips & tricks. Thanks!

  5. Richard S.

    It would be interesting to read about Zen Sessions. For example, CodeIgniter uses its own sessions, however they are not as secure as the default PHP and so on. What advantages does the Zend frameworks sessions have? Disadvantages?

  6. PHPGangsta

    Very good article, thank you for that! At the moment I’m viewing the Google video which seams to be very interesting.

    All your words are very correct, a good overview, a list to give to other developers.

  7. Juozas (author)

    Thanks everyone for good words, I’m glad it helped! :)

    Richard, Zend_Session doesn’t change PHP internal sessions. It’s a wrapper class, which makes working with sessions easier (you can create namespaces, expiration hoops/time etc.) and you can work with them in object oriented way:

    $session = new Zend_Session_Namespace('orders');
    $session->id = 123;

    Jellis, includePath option adds that path to include_path, PHP core variable used to find files. So in my tests I have something like that (for testing controllers):

    public function setUp()
    {
        $application = new Zend_Application('config');
        $application->bootstrap();
    }

    This code will silently add that path on every test. Even though it’s a correct path, but the longer it’s the more PHP struggles and slows down files finding/autoloading.

    So you are right that on each request you won’t notice that, but you’d notice it if you do something like I showed above – for testing purposes create app instance many times.

  8. Torsten

    Very good article about serious software development with PHP and the Zend Framework. I hope many PHP developers get inspired…

  9. stoimen

    Very good article indeed. What I was trying to write about Zend Framework in my blog is aiming exactly at that problem. The people are using mostly copy/paste from the great ZF doc pages. The real power sometimes lies beneath the ground!

  10. petrelevich

    Very interesting tips, Thank you.
    I have translated your article to Russian.
    http://www.smartyit.ru/article/77

  11. umpirsky

    Hi.

    $form = new Form();

    if ($this->_request->isPost()) {

    if ($form->isValid($this->_request->getPost())
    {
    $model = new Model($this->_request->getPost());
    $model->save();
    }
    else
    {
    $form->populate($this->_request->getPost());
    }
    }

    You don’t need to populate since isValid already populates the form.
    Also $model = new Model($this->_request->getPost()) should be replaced with $form->getValues() or you can pass Zend_Form object to your model.

    Thanks for nice post ;)

  12. Juozas (author)

    umpirsky, have you read the post? :) This exactly what I’m talking about, that you should use getValues and not populate after isValid :)
    Though passing Zend_Form is not entirely right as models do not care about forms

  13. umpirsky

    @Juokaz Damn, you got me there, I didn’t read it :)

    Now i see. Interesting approach.

    Well models can care about forms (http://weierophinney.net/matthew/archives/200-Using-Zend_Form-in-Your-Models.html) in some cases that can have sense, for filtering, for validation, I like to do it explicitly in controller. But sometimes it’s handy to pass entire form (ex search form) and get values with $form->getValue(‘q’) rather then $param['q'] if you pass values.

    When we are there, i’m strugling to extend Zend_Db_Table_Row_Abstract right way, maybe you have some suggestions, please take a look at http://n4.nabble.com/Extending-Zend-Db-Table-Row-Abstract-td1559336.html#a1559336.

  14. PHPGangsta

    It’s good that he repeated what you already wrote.

    Perhaps you should mark your WRONG code as being WRONG, otherwise people copy&paste the WRONG examples without reading the complete article.

  15. Julian

    Great work!

    It would be nice, if Zend could provide us with more of those hints. Less people would use the framework in a wrong way without knowing about it, I think.

    Without these little information it’s quite easy to do something wrong (aside from general OO purposes).

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">