Realm of Zod

Programming and Technology
RSS icon Home icon
  • Ajax - Too Much of a Good Thing

    Posted on June 23rd, 2009 brandon No comments

    I’ve stated this many times, but working as a lead php developer at eroi has been both an adventure and educational. Each project brings new challenges, requirements, and the obligatory ‘experiments’. I’ve had the opportunity to deploy some pretty good size projects, at least what I would dub ‘enterprise’ scale websites with enough traffic to keep it humming and with that much traffic comes a good helping of feedback. One of my latest projects involved heavy use of ajax to provide eye candy and the majority of the site navigation. Some people may shriek as they read this but being powered mostly by user-driven content, SEO was not a priority. What I discovered through my own use of the site as well as overwhelming feedback from the community is that simply put… too much ajax can be detrimental to user perception. This is ironic considering this is what it was deployed to improve.

    The keyword here ends up being perception. Depending on server and internet conditions, the time it takes to load an entire page is usually pretty consistent with how long an ajax request takes to respond. This means in the time it takes to populate a div with a paragraph of text, an entire page with images could have been built, rendered, and delivered. Wait…. wtf? Well let’s revisit my logic for deploying ajax so heavily:

    1. Less data is transmitted so it should take less time

    2. Less data is rendered so it should take less time

    3. Less stuff changes on the page so there is a perceived seamlesslness

    Ok now let’s explore why these are noble ideals but in practice don’t hold water.

    1. The problem isn’t how much data gets downloaded unless you’re on a painfully slow connection. The average person has some form of broadband so this isn’t as critical as it used to be, although you still should make your best effort to reduce the footprint of your sites as it still matters. The problem tends to be the overhead it takes for the server to respond to a request, especially for larger enterprise style sites. Now I tend to write pretty hefty MVC sites with a lot of infrastructure in the bootstrap so I don’t have to write very much code in my controllers, (The efficiency of this is debatable but it allows me to pump out sites very quickly). This means that every minor ajax call which is intended to quickly grab a tidbit of information must invoke the entire machination of the app’s infrastructure, given the transient nature of php processes. That means apache forks, a php process is started,  the bootstrap code is executed, routes are built,  permissions are checked in the Acl, the controller is loaded and invoked, and finally a template is loaded and rendered. That’s a lot of stuff to happen in the 1 second or less that the ajax request is supposed to respond within. On a site where content is getting polled for updates on a frequent basis, you are dramatically increasing the number of requests to the server, meaning you are increasing the server load and as traffic increases, the lag between requests starts to get noticeable. What ends up happening is your user is watching your animated spinny gif for just as long as it would take for the page to reload, only its a little more annoying than the former because the user expects the content refresh to be near instantaneous.

    2. It stands to reason that less content is quicker to render, but for all of the reasons why #1 doesn’t stick, #2 also fails the test.

    3. This one is the double edged sword. It’s true that it is usually perceived to be snappier and more application-like when parts of pages update in response to clicked links and/or buttons rather than being forced to another page but this is only the case if it works well. We all hate to wait. Waiting for what should be a minor update is especially egregious and the user really notices because there is usually a spinner or throbber there taunting them about it. It’s like those annoying trivia commercials before a movie in the theater that no one is really interested in watching, but they have to because the show hasn’t started yet.

    So let me put this all into perspective. I am not advocating using less ajax. On the contrary it’s become a critical component of modern websites, however experience is teaching me to use it only when you can get away with it. In other words, don’t make it obvious. If a user knows you are using ajax for a particular action, you are probably doing it wrong. Here are some rules of thumb that I’ve learned from my own mistakes:

    Ajax calls should return very quickly and be like their namesake: Asynchronous. Blocking further action while an ajax request is being handled contributes to a negative perception about the site’s performance.

    Ajax should not impede easy navigation. This means don’t break the back button. Additionally, everything that can be reached through ajax should be accessible through a permalink (ie. can be bookmarked).

    Beware of queued requests. A good example of this hazard is screen name availability checking where an ajax call gets made when a text field is changed in order to query the database. As the user types, they can potentially trigger several ajax requests each performing the same operation. Make sure to cancel existing requests before making new ones otherwise the response that comes back could be stale and appear slightly shizophrenic as it updates content.

    So in a nutshell I discovered most of my use of ajax in site navigation is usually pretty gratuitous and this has had a negative impact when the server load is high. I have also noticed that overuse leads to considerably more complex code on both the server side and in the javascript. The final issue is one that is a no-brainer for veteran developers, is that ajax does not always degrade gracefully. I never considered this much of a problem since most everyone with an IQ above 100 is using a modern browser, however with introduction of mobile browsers, this has become a bugabear from the shadows of the past and is once again an issue. It’s better to keep the site simple and functional and add eye candy only when available. Veterans may read this and say ‘well duh’ to all of this but my teacher has always been experience.

  • Implement a Rest API with the Zend Framework

    Posted on May 6th, 2009 brandon No comments

    As a developer and I admit that I judge the utility of a website by the availability of an API so that I can leverage it in my own projects. Most popular web services offer an API of some sort such as Flickr, Blip.TV, Youtube, Twitter, Facebook, etc. The most popular way to implement an api that is accessible to all styles of developers is to implement it using REST. In a nutshell, REST is a method of utilizing the HTTP protocol to send and retrieve information. REST leverages the various request types that HTTP supports (GET,POST,PUT,DELETE) while web browsers typically only use GET and POST to fetch web pages and submit form information. I won’t go into the intricacies of REST other than to note that the primary idea behind REST is that web services are presented in an object oriented fashion. REST urls typically comprise of a reference to an object or service of some type and an action to perform on the object. Parameters that are required to perform the requested action are provided as post variables in the header rather than as part of the url.

    An example of a typical REST url for retrieving a photo would be:

    http://typical.webservice.com/rest/photo/1

    When accessed using the GET method of the HTTP protocol, the assumption is that you want to retrieve this photo. The response might be the image itself, or more typically it will be a JSON or XML encoded response containing a url to the image stored on a CDN. Alternately, if this same url is accessed using a DELETE request, the photo would instead be deleted and an encoded status report would be returned.

    Chances are if you’re reading this article you already know all of this and just want to know how to implement this using the Zend Framework. This example utilizes controllers in an MVC style application. The first thing to understand is that REST is not implemented in the controller itself, instead the controller simply serves as a broker for various classes we have for handling different types of REST requests. Most of the boilerplate functionality is handled by an instance of  Zend_Rest_Server so all we have to do is implement our service.

    In keeping with the REST philosiphy you will want to visualize how your web service will be accessed in terms of objects and their associated actions. It might help to draw up some use cases, but in this case lets keep going with our photo example. Let’s assume we want to allow users to upload photos, retrieve them, delete them, etc. Using this example, the first place to start naturally would be a class that represents Photos. Before that however, make sure you have set up your MVC application how you like it. Make sure you have written an appropriate class to represent your photo models complete with your persistence layer implementation. If you have no idea what i’m talking about, take a look at my application skeleton examples.  For those who are ready, we can implement our REST handlers now.

    I like to start with a base class that contains functionality that will need to be shared across all the REST handlers. Here is a simple base class that we can extend.

    1.  
    2. class Rest_Server {
    3.    protected function authenticate($userid, $apikey){
    4.      /* Authentication implementation goes here*/
    5.    
    6.     if (!$authenticated)
    7.       throw new Exception(‘Access Denied’);
    8.    }
    9. }
    10.  

    This class implements one method right: authenticate(). Most web services require some form of authentication and/or api key to prevent abuse so this application shouldn’t be any different. This method is obviously just a stub as your authentication method can be whatever you prefer.

    Next lets implement a default REST handler that responds to generic requests.

    1.  
    2. class Rest_Server_Default extends Rest_Server {
    3.  public function getVersion($userid, $apikey){
    4.     $this->authenticate($userid, $apikey);
    5.  
    6.     return Rest_Response::Generate(array(‘version’ => 1.0));
    7.   }
    8. }
    9.  

    This class implements getVersion() which responds with the current version of the web service software. I call it the default handler because it will serve as a fallback handler when no context is specified in the REST url. The name of the method is important because that is the name of the action used in the url (Example: http://webservice.com/rest/getVersion).  This method takes two arguments ($userid and $apikey). You will notice that all REST methods will minimally require these arguments. The purpose of these arguments is so a developer implementing your API can provide their credentials. The names of these arguments map directly to the name of your request parameters (http://webservice.com/rest/getVersion?clientid=1&apikey=1234567890). These parameters can be provided as post variables as well.

    All REST methods will always return an encoded response using a custom response generator Rest_Response.

    1.  
    2. class Rest_Response {
    3.  
    4.   protected $_xml = null;
    5.   protected $_status = null;
    6.  
    7.   public function __construct($pStatus=true, array $pResponse=array()){
    8.     $this->_xml = simplexml_load_string(‘<?xml version="1.0" encoding="utf-8"?><response></response>’);
    9.     $this->appendStatus($pStatus);
    10.  
    11.     if (count($pResponse) > 0)
    12.       $this->appendResponse($this->_xml, $pResponse);    
    13.   }
    14.  
    15.   public function appendStatus($pStatus=true){
    16.     if (is_null($this->_status)){
    17.       if ($pStatus === true)
    18.         $this->_xml->addChild(’status’, ’success’);
    19.       else if ($pStatus === false)
    20.         $this->_xml->addChild(’status’, ‘fail’);
    21.       else
    22.         throw new Exception(‘Invalid response status’);
    23.     } else
    24.       throw Exception(‘Response already has status’);    
    25.   }
    26.  
    27.   public function appendResponse($pXml, array $pResponse){
    28.     foreach ($pResponse as $key => $val){
    29.       if (is_array($val)){
    30.         $child = $pXml->addChild($key);
    31.         $this->appendResponse($child, $val);
    32.       } else
    33.         $pXml->addChild($key, $val);
    34.     }
    35.   }
    36.  
    37.   public function __get($pAtt){
    38.     switch ($pAtt){
    39.     case ‘xml’:
    40.       return $this->_xml;
    41.     }
    42.   }
    43.  
    44.   public static function Generate(array $pResponse, $pStatus=true){
    45.     $response = new Rest_Response($pStatus, $pResponse);
    46.     return $response->xml;
    47.   }
    48. }
    49.  

    The purpose of this class is to generate responses in a uniform and consistent structure. Each response regardless of the context or action should use the same structure which makes a developer implementing your API want to pull out his hair less than if each response required a completely different parser.  This class provides a static method Generate() which takes an associative array as its argument which it converts into xml. Optionally, Generate() takes a status argument which indicates whether the request Succeeded or failed. This is helpful in case the response parser needs to respond differently in the event of a failure.

    So let’s make another REST handler that handles requests for photos.

    1.  
    2. class Rest_Server_Photo extends Rest_Server {
    3.  
    4.   public function upload($userid, $apikey){
    5.     /* Handle file upload */
    6.  
    7.    /* Save data to db */
    8.  
    9.    return Rest_Response::Generate(array(‘photoid’ => $newphotoid), true);
    10.   }
    11.  
    12.   public function get($userid, $apikey, $photoid){
    13.  
    14.     /* Retrieve record from db */
    15.  
    16.    return Rest_Response::Generate(array(‘url’ => $photo->url), true);
    17.   }
    18.  
    19.   public function delete($userid, $apikey, $photoid){
    20.  
    21.     /* Delete record from db */
    22.  
    23.    return Rest_Response::Generate(array(), true);
    24.   }
    25. }
    26.  

    This handler is a little more sophisticated. It responds to requests to upload photos, retrieve them and delete them. Just like the default handler, each method requires credentials and some take additional arguments. The upload method receives the file and generates a database record for the photo which has an id associated with it which can be returned in the response. The get() method takes an id for an argument that it uses to retrieve a database record for a photo and then returns the information associated with it. Similarly, delete() takes an idea and deletes the associated photo from the database and returns a status report.

    So now we have handlers for our REST operations but we haven’t yet seen where the controller fits into all of this. As I said earlier, the controller doesn’t only acts as a broker for the REST requests. In this application we have a single controller RestController with a single method restAction()

    1.  
    2. class RestController extends Zend_Controller_Action
    3. {
    4.   //Rest API action
    5.   public function restAction(){
    6.     $this->_helper->Layout->disableLayout();
    7.     $this->_helper->ViewRenderer->setNoRender();
    8.  
    9.     $params = $this->_request->getParams();
    10.     unset($params[‘controller’]);
    11.     unset($params[‘action’]);
    12.     unset($params[‘module’]);
    13.  
    14.     $server_prefix = ‘Rest_Server_’;
    15.  
    16.     if (array_key_exists(‘context’, $params)){
    17.       switch ($params[‘context’]){
    18.       case ‘photo’:
    19.         $server_class = $server_prefix . ‘Photo’;
    20.         break;
    21.       default:
    22.         throw new Exception(‘Invalid rest context’);
    23.       }
    24.     } else
    25.       $server_class = $server_prefix . ‘Default’;
    26.    
    27.     $server = new Rest_Handler();
    28.     $server->setClass($server_class);
    29.     $server->returnResponse(true);
    30.  
    31.     $responseXML = $server->handle($params);
    32.  
    33.     if (!array_key_exists(‘format’, $params)){
    34.       if ($this->_request->isXmlHttpRequest())
    35.         $format = ‘json’;
    36.       else
    37.         $format = ‘xml’;
    38.     } else
    39.       $format = $params[‘format’];
    40.  
    41.     switch ($format){
    42.     case ‘xml’:
    43.       $this->_response->setHeader(‘Content-Type’, ‘text/plain’)->setBody($responseXML);
    44.       break;
    45.     case ‘json’:
    46.       $this->_response->setHeader(‘Content-Type’, ‘application/json’)->setBody(Zend_Json::fromXML($responseXML));
    47.       break;
    48.     }
    49.   }
    50. }
    51.  

    restAction() looks pretty gnarly at first glance. Believe me when I was figuring out how to do this it started of pretty simple but rapidly evolved to handle various use cases as most code does. I’ll go over it step by step.

    Obviously in order to satisfy all of the requirements of both the controller and our REST handlers we need some workable routes which directs REST requests to this action.

    1.  
    2. $routes = array(
    3.                       ‘default_rest’ => new Zend_Controller_Router_Route(‘rest/:method’, array(‘controller’ => ‘rest’, ‘action’ => ‘rest’)),
    4.                       ‘default_rest_media’ => new Zend_Controller_Router_Route(‘rest/:context/:method’, array(‘controller’ => ‘rest’, ‘action’ => ‘rest’)),
    5.                      );
    6. Zend_Controller_Front::getInstance()->getRouter->addRoutes($routes);
    7.  

    The first route allows us to make requests in the default context. This allows us to call http://webservice.com/rest/getVersion

    The next route allows us to specify a context. For example: http://webservice.com/rest/photo/get?id=1

    So first things first we need to disable the view and layout plugins since all we are interested in getting back from this action is either pure XML or JSON
    . If we don’t disable the layout or view engines they will go looking for non-existant templates, or worse find some and wrap out XML or JSON in HTML.

    1.  
    2.     $this->_helper->Layout->disableLayout();
    3.     $this->_helper->ViewRenderer->setNoRender();
    4.  

    Next we need to grab the request parameters. We strain out the parameters that get automatically appended by the controller infrastructure because they are not needed by the REST handlers.

    1.  
    2.     $params = $this->_request->getParams();
    3.     unset($params[‘controller’]);
    4.     unset($params[‘action’]);
    5.     unset($params[‘module’]);
    6.  

    Next we determine if there is a context in the request. This allows us to determine which REST handler we need to instantiate and invoke.

    1.  
    2.     $server_prefix = ‘Rest_Server_’;
    3.  
    4.     if (array_key_exists(‘context’, $params)){
    5.       switch ($params[‘context’]){
    6.       case ‘photo’:
    7.         $server_class = $server_prefix . ‘Photo’;
    8.         break;
    9.       default:
    10.         throw new Exception(‘Invalid rest context’);
    11.       }
    12.     } else
    13.       $server_class = $server_prefix . ‘Default’;
    14.  

    Now we instantiate a Zend_Rest_Server object and assign our custom handler. I’m sure this can be streamlined to be a little more dynamic.

    1.  
    2.     $server = new Rest_Handler();
    3.     $server->setClass($server_class);
    4.     $server->returnResponse(true);
    5.  

    And now we let the handler do its thing. If we fail to set returnResponse(), the REST server will ignore everything after this line and send its response back to the requester directly. By setting this to true, we ensure the handler will return its response to the controller so we can do some housekeeping.

    1.  
    2.     $responseXML = $server->handle($params);
    3.  

    In order to be truly flexible with the needs of other developers we really should offer a variety of response formats. Some developers prefer JSON while others need XML. It’s a good idea to accept a format parameter that allows the developer to specify the preferred format or assign a default format otherwise. Once we know what format to use, we need to set the Content-Type header and then send the response back to the requester.

    There’s not much more to it than that, although as a footnote, I subclassed the default Zend_Rest_Server object because I wanted to to return custom XML in the event of an uncaught exception in the REST handlers.

    1.  
    2. class Rest_Handler extends Zend_Rest_Server {
    3.  
    4.   public function fault($exception = null, $code = null){
    5.     $xml = simplexml_load_string(‘<?xml version="1.0" encoding="utf-8"?><response></response>’);
    6.  
    7.     $xml->addChild(’status’, ‘fail’);
    8.  
    9.     if ($exception instanceof Exception)
    10.       $xml->addChild(‘error’, $exception->getMessage());
    11.     else
    12.       $xml->addChild(‘error’, ‘Unknown error’);
    13.  
    14.     if (is_null($code) || (404 != $code))
    15.       {
    16.         $this->_headers[] = ‘HTTP/1.0 400 Bad Request’;
    17.       } else {
    18.       $this->_headers[] = ‘HTTP/1.0 404 File Not Found’;
    19.     }
    20.     return $xml;
    21.   }
    22. }
    23.  

    It’s very important that you are diligent about catching exceptions and handling them properly otherwise the response can be ruined with garbage such as error messages outside of the XML or JSON, or worse, they could be blank. To use this simply instantiate this class instead of Zend_Rest_Server. So with this knowledge go forth and create. The web needs more clean and robust web services.

  • End of Life for Internet Explorer 6. Please.

    Posted on April 13th, 2009 brandon 1 comment

    So Microsoft has decided to begin the push of IE8 to all the unsuspecting windows users through their automatic update service and once again has done us a marginal favor out of their own necessity for relevance. With IE6 now two generations antiquated, many of the developers in the community are finding fewer reasons to humor the predominance of the broken application for any reason. Having dealt with huge corporate clients when developing web sites, even in 2009 I still get the excuse that IE6 must still be supported because it litters the workstations of their huge corporate networks, either from stubborn familiarity or a misguided cost savings attempt. I and many other developers have wasted endless hours that we will never get back tracking down bizarre css and javascript errors as well as tedious tactics to try and accommodate IE6 for not doing anything the way the rest of the world expects. With the release of version 8, IE6 has shown its age now entering its eighth year of existence which in regards to software is damn near ancient and given the dynamic nature of the web as it is intended, it really should have faded from mainstream use many years ago. But all of this, I’m certain, is serving to do nothing more than preach to the choir so forgive the rant.

    The intention of this post is more or less to serve as a formal declaration of my intention as a web developer at a major digital agency that I intend to start an initiative to drop support for IE6. My personal strategy is start with the expectation that only standards compliant web browsers will be supported and additional browsers that require extreme hardship to support should be charged for accordingly. If I were running things I would make the costs prohibitive and down right punitive but I have only my opinion to sway others but I feel that it’s important to set the expectations out on the web from the onset.

    I have started engaging in the tactics cited here by a sampling of norwegian web sites, by displaying annoying banners to IE6 users in order to raise awareness of their obsolescence. The problem is most people aren’t aware they are using broken software so it seems unfair to expect people to change when they see no problem. These banners are a perfect tool to participate in this campaign and do your part to rid the world of the scourge that is IE6. All of my blogs utilize this warning mechanism and I encourage everyone else involved with web development to do the same.

  • Asynchronous Image Loading with jQuery

    Posted on April 9th, 2009 brandon 6 comments

    I thought it would make for a good change of pace to switch gears from back end development to front end development. So far I’ve focused primarily on php development which makes sense since that is my job here at eROI. There are plenty of opportunities to get my hands dirty doing javascript development, and the folks here at eROI are not shy about their infatuation with jQuery. It’s an awesome framework for frontend development and I expect I’ll be posting more articles about it in the near future.

    I recently built a media heavy site that involved loading lots of images on a page that were dynamic. Search results were displayed using thumbnails in a tile format that allowed for quick perusing, something that’s not uncommon at all. One of the annoyances I encounter often with sites that load images is that sometimes the load times for image assets can vary resulting in a broken page layout until the images finish loading.

    For my latest project I had a goal to have the entire page layout to render completely and allow the images to flesh out at their leisure without having the page structure move.  The effect is pleasing to the eye with a certain ‘coolness’ factor, and by allowing images to load asynchronously using jquery, we can create callbacks to be executed by images that have finished loading. There are lots of cool things we can do with that. To make the whole process look even ‘cooler’, it would be nice to have some form kind of throbber show so that we know we are waiting for something to appear. But wait! That’s not all. One more feature we should have is to be able handle errors, for example if an image asset is missing or corrupt, rather than rely on the browser to show a broken asset indicator, (or worse: nothing at all), we should be able to show a fallback image that indicates a failure occured.

    Let’s create a file called image-loader.js. At the top we need to specify a couple of variables to indicate where to pull default assets from:

    1.  
    2. gLoadSpinnerUrl = gBaseUrl + ‘/assets/images/ajax-loader.gif’;
    3. gFailImage = gBaseUrl + ‘/assets/images/image_load_error.gif’;
    4.  

    The gLoadSpinnerUrl is the url of an image asset that will be shown while the real image is loading. This can be any manifestation of an ajax-ish throbber, spinner, progressbar, etc. The gFailImage is the url of the image asset to show if something bad happens and the real image becomes unavailable.

    Next we create two functions that make the magic happen. The first function is LoadImage():

    1.  
    2. function LoadImage(pSelector, pCallback){
    3.     var loader = $(pSelector);
    4.     loader.html(‘<img src="’ + gLoadSpinnerUrl + ‘"/>’);
    5.  
    6.     LoadThisImage($(img), loader, pCallback);
    7. }
    8.  

    To this function we pass a jquery selector and an optional callback function. The selector can refer to any type of html element so long as that element has a ’src’ attribute which references the url of the real image we want to load. Typically I use a ‘div’ or an ‘a’ tag which serves as a container element for the img tag that will be dynamically created. Obviously neither of these tags has use for a ’src’ attribute so that leaves it open for us to assign an arbitrary url to it.

    The LoadImage function grabs the specified element and immediately inserts an ‘img’ tag referencing our spinner image so that the end user has something to look at while it does its work. Next LoadThisImage() is called:

    1.  
    2. function LoadThisImage(loader, pCallback){
    3.     image_src = loader.attr(’src’);
    4.     img = new Image();
    5.     img.hide();
    6.  
    7.     img.load(function() {
    8.         cb_js = loader.get(0).getAttribute(‘onload’);              
    9.         onload_cb = function(){
    10.             eval(cb_js);
    11.         }       
    12.  
    13.         loader.html(this);
    14.         loader.removeClass(‘loadable-image’);
    15.         loader.removeAttr(’src’);
    16.         loader.removeAttr(‘onload’);
    17.         $(this).show()
    18.         if (onload_cb){                
    19.             onload_cb($(this));
    20.         }              
    21.         if (pCallback){
    22.             cb = pCallback;
    23.             cb($(this));
    24.         }
    25.     })
    26.     .error(function() { $(this).attr(’src’, gFailImage).show(); })
    27.     .attr(’src’, image_src)
    28.     .show();
    29. }
    30.  

    This function is passed the container element and the optional callback function. LoadThisImage extracts the ’src’ attribute from the container element and creates a brand new Image object in memory. The ’src’ is then assigned to our new Image and we create some callbacks on the Image by called error() load(), passing each an anonymous function to handle a possible error event and the desired load event.

    In the event of an error, we change the ’src’ attribute of the image to show our failure image. If the load is successful, we begin processing any custom callbacks that are present. The first place the function checks in the container element itself. If the container element has an ‘onload’ attribute, it is assumed to have javascript assigned to it which is then evaluated. Next it checks to see if a callback function was passed to the function call manually which is also evaluated. Both callbacks are passed the image object so that they can manipulate the object after it has finished loading. This way we can do things like assign onclick events or alt tags or whatever you can think of.

    For convenience we also provide a function called LoadAllImages():

    1.  
    2. function LoadAllImages(){
    3.     $(‘.loadable-image’).each(function(){       
    4.         var loader = $(this);
    5.         loader.html(‘<img src="’ + gLoadSpinnerUrl + ‘"/>’);
    6.         LoadThisImage(loader);
    7.     });
    8. }
    9.  

    This function takes no arguments. Instead it selects all elements with the class ‘.loadable-image’ and processes them the same way the container is processed in LoadImage. This can be called from a DOM ready function allowing all images on a page to be loaded asynchronously.

    Here’s a real world example:

    1.  
    2. <script type="text/javascript">
    3.   $(document).ready(function(){
    4.      LoadAllImages();
    5.   });
    6. </script>
    7. <div class="loadable-image" src="/images/example.jpg" onload="img = $(arguments[0]); img.bind(’click’, function(){ alert(’Image was clicked’); })"></div>
    8.  

    In this example we set up our container div with an image src and an inline callback. An img with the src ‘/images/example.jpg’ will be inserted into the div. When the image has downloaded, it will be passed to the javascript in the onload attribute. Since there is no function prototype to indicate parameters, we must access the passed in image dynamically with the arguments array. The callback binds a click handler to the image so that an alert appears when it is clicked. See, it’s simple.

    Here’s a screenshot of a site that uses this technique to give you an idea of how it looks:

    imageloaderss

    Here you can see how the page maintains it’s layout while the images are still loading. Spinners are showing for images that have not downloaded yet.

    Loading your images this way adds a really cool ‘modern’ feel to the site and provides a workaround for the ugliness that can ensue as pages are rendered before they have finished downloading all of the page assets.

  • Modular Zend Framework Skeleton 2009

    Posted on April 8th, 2009 brandon 6 comments

    So the application skeleton series proved to be more popular than I was expecting. Unfortunately after a couple of server migrations, the source code examples that were linked to those articles did not survive the trip. I’ve gotten a lot of comments to restore those examples so I decided to write a new article to illustrate the latest version of the application skeleton I’ve been using for my applications.  Since I’ve gone over much of the design philosiphy in the previous articles I won’t go into too much detail about other than to demonstrate a solid way to design MVC applications using the Zend Framework.

    For those who have no clue what I’m talking about, you can review the archives here:

    http://blog.realmofzod.com/2008/03/19/modular-zend-framework-application-skeleton-part-1-folder-structure/

    http://blog.realmofzod.com/2008/03/19/modular-zend-framework-application-skeleton-part-2-crud/

    http://blog.realmofzod.com/2008/06/06/modular-zend-framework-skeleton-part-3-modules/

    The only major deviation I have made from the past versions of the skeleton is the migration from Zend_Db to Doctrine and the use of domain model programming which only affects the module model folders. For more on this, see my recent article about it.

    First of all the folder structure hasn’t changed much from my previous incarnations:

    • app/
      • default/
    • config/config.xml
    • lib/
      • Zend/
      • FP/
      • Doctrine/
      • bootstrap.php
      • cache.php
      • config.php
      • database.php
      • Doctrine.php
      • init.php
      • locale.php
      • logger.php
      • mail.php
      • modules.php
      • view.php
    • log/
    • plugins/
    • public/
      • assets/
      • .htaccess
      • index.php
    • tmp/

    The public folder serves as the document root for the entire application and is the only folder that can be accessed from the web. It’s purpose should be to only house client-side assets, the .htaccess file and the front controller which is the only php file in that folder.

    .htaccess

    DirectoryIndex index.php
    
    RewriteEngine on
    
    RewriteBase /
    
    RewriteCond %{REQUEST_URI} !assets\/.*
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule .* index.php [QSA,NC,L]
    

    All we are doing here is redirecting any requests that do not directly access files in the assets folder to be processed by the front controller in index.php. This will pass along any query strings and paramters to the php.

    index.php

    1.  
    2. -<?php
    3.  
    4. define(‘BASE_DIR’, ‘../’);
    5.  
    6. require_once(BASE_DIR . ‘lib/bootstrap.php’);
    7.  
    8. $main = Zend_Controller_Front::getInstance();
    9. $main->throwExceptions(false);
    10.  
    11. //GO!
    12. $main->dispatch();
    13.  
    14. ?>
    15.  

    The front controller routes all requests to the appropriate controller. This file includes the bootstrap file which initializes the application platform and is then configured to handle all thrown exceptions by the error controller plugin simply by passing false to throwExceptions().

    The core of the platform is still housed in the lib folder which breaks up the bootstrap process into individual files, each handling a particular subsystem (logging, database, locale, etc). Additionally, any third party libraries that are required by your application can be housed here (I usually have Zend, PHPTAL, and Doctrine living here). My general philosiphy regarding lib files is that they are 100% application agnostic and for no reason should they ever be edited to accomodate a particular application (Unless you are performing upgrades which can carry over to your next project). These files are all tied together and included in bootstrap.php. The guts of the platform is designed to be a black box that can be used in any situation.

    bootstrap.php

    1.  
    2. <?php
    3.  
    4. set_include_path(BASE_DIR . ‘lib’ . PATH_SEPARATOR . BASE_DIR . ‘plugins’ . PATH_SEPARATOR . get_include_path());
    5.  
    6. require_once(‘Zend/Loader.php’);
    7.  
    8. Zend_Loader::registerAutoLoad();
    9.  
    10. require_once(‘cache.php’);
    11. require_once(‘config.php’);
    12. require_once(‘locale.php’);
    13. require_once(‘logger.php’);
    14. require_once(‘database.php’);
    15. require_once(‘mail.php’);
    16. require_once(‘view.php’);
    17. require_once(‘modules.php’);
    18. require_once(‘init.php’);
    19.  
    20. ?>
    21.  

    As you can see this file serves only to configure the base include paths, initialize the autoloader and to require all the necessary subsystem files. This allows us to keep the front controller file simple by only having to require one file. I’ve seen alot of examples that extend the front controller in a custom bootstrap class but I find that this approach makes it difficult to break out different functionality into independent files.

    Each file in the lib folder, as I’ve said, manages a particular piece of the platform. As each file is included,  all the necessary Zend classes are created and configured, pulling their configurations from the platform configuration xml file located in config.

    config.xml

    1.  
    2. <?xml version="1.0"?>
    3. <configdata>
    4.   <global>
    5.     <mode>debug</mode>
    6.   </global>
    7.   <production>
    8.     <database>
    9.       <enabled>1</enabled>
    10.       <dsn>mysql://user:pass@localhost/zendapp</dsn>
    11.     </database>
    12.     <error>
    13.       <display>0</display>
    14.     </error>
    15.     <cache>
    16.       <enabled>1</enabled>
    17.     </cache>
    18.     <logger>
    19.       <enabled>1</enabled>
    20.       <file>log/production.log</file>
    21.       <priority>6</priority>
    22.     </logger>
    23.     <locale>
    24.       <default>en_US</default>
    25.       <timezone>America/Los_Angeles</timezone>
    26.       <date>MM.dd.YYYY</date>
    27.     </locale>
    28.     <mail>
    29.       <enabled>1</enabled>
    30.       <mode>sendmail</mode>
    31.     </mail>
    32.     <view>
    33.       <layout>1</layout>
    34.       <engine>FP_View_PhpTal</engine>
    35.       <ext>html</ext>
    36.       <config>
    37.         <compileDir>../tmp</compileDir>
    38.       </config>
    39.     </view>
    40.     <paths>
    41.       <upload>upload</upload>
    42.       <assets>assets</assets>
    43.       <images>assets/images</images>
    44.       <scripts>assets/scripts</scripts>
    45.       <stylesheets>assets/styles</stylesheets>
    46.       <fonts>assets/fonts</fonts>
    47.       <temp>../tmp</temp>
    48.     </paths>
    49.   </production>
    50.   <debug extends="production">
    51.     <cache>
    52.       <enabled>1</enabled>
    53.     </cache>
    54.     <error>
    55.       <display>1</display>
    56.     </error>
    57.     <logger>
    58.       <enabled>1</enabled>
    59.       <file>log/debug.log</file>
    60.       <priority>7</priority>
    61.     </logger>
    62.     <dojo>
    63.       <debug>1</debug>
    64.     </dojo>
    65.   </debug>
    66. </configdata>
    67.  

    The xml is layed out in a way that implements staging, allowing the developer to switch between debug and production modes that allow for subsystems to behave differently in each state simply by changing the value in mode. Within each staging node, subsystem configs are broken out into individual nodes. Most subsystems can be toggled on and off with the ‘enabled’ value. For the sake of performance this file gets cached regardless of the staging mode so changes will only be reflected once the cached files have been cleared.

    cache.php

    1.  
    2. <?php
    3.  
    4. $CACHE = Zend_Cache::factory(‘Core’, ‘File’, array(‘lifetime’ => 900, ‘automatic_serialization’ => true), array(‘cache_dir’ => BASE_DIR . ‘tmp/’));
    5. $HTMLCACHE = Zend_Cache::factory(‘Output’, ‘File’, array(‘lifetime’ => 30, ‘automatic_serialization’ => true), array(‘cache_dir’ => BASE_DIR . ‘tmp/’));
    6.  
    7. Zend_Registry::set(‘cache’, $CACHE);
    8. Zend_Registry::set(‘html-cache’, $HTMLCACHE);
    9.  
    10. function get_cache($pKey){  
    11.   $config = Zend_Registry::get(‘config’);
    12.   if ($config->cache->enabled){
    13.     $cache = Zend_Registry::get(‘cache’);
    14.     $val = $cache->load(md5($pKey));
    15.     return $val;
    16.   } else
    17.     return null;      
    18. }
    19.  
    20. function set_cache($pValue, $pKey){
    21.   if (Zend_Registry::get(‘config’)->cache->enabled){
    22.     $cache = Zend_Registry::get(‘cache’);
    23.     $cache->save($pValue, md5($pKey));
    24.   }
    25. }
    26.  
    27. function start_html_cache($pId){
    28.   if (Zend_Registry::get(‘config’)->cache->enabled){
    29.     $cache = Zend_Registry::get(‘html-cache’);
    30.     return $cache->start(md5($pId));
    31.   } else
    32.     return null;
    33. }
    34.  
    35. function end_html_cache(){
    36.   if (Zend_Registry::get(‘config’)->cache->enabled){
    37.     $cache = Zend_Registry::get(‘html-cache’);
    38.     $cache->end();
    39.   }
    40. }
    41.  
    42. function remove_from_cache($pKey){
    43.   if (Zend_Registry::get(‘config’)->cache->enabled){
    44.     $cache = Zend_Registry::get(‘cache’);
    45.     return $cache->remove(md5($pKey));  
    46.   }
    47.   return false;
    48. }
    49. ?>
    50.  

    This file sets up everything we need to perform caching on objects and html. It defines a number of functions globally which can be used any in our application and will perform automatic serialization and key hashing. Cache files are stored centrally in the tmp folder and can be cleared simply by being deleted.

    config.php

    1.  
    2. <?php
    3.  
    4. $cache = Zend_Registry::get(‘cache’);
    5.  
    6. /* Load from cache if possible */
    7. if ($mode == ‘testing’ || !$CONFIG = $cache->load(md5(‘config’))){
    8.   /* Load config from global xml file */
    9.   $GLOBAL = new Zend_Config_Xml(BASE_DIR . ‘config/config.xml’,‘global’, true);  
    10.  
    11.   if ($mode != ‘testing’) $mode = $GLOBAL->mode;
    12.  
    13.   $CONFIG = new Zend_Config_Xml(BASE_DIR . ‘config/config.xml’, $mode, true);  
    14.   $CONFIG->merge($GLOBAL);
    15.   $cache->save($CONFIG, md5(‘config’));
    16. }
    17.  
    18. Zend_Registry::set(‘config’,$CONFIG);
    19. ?>
    20.  

    This file loads the platform configuration from the xml file and stores the config object in the Registry for other systems to access it. First the mode node is read to determine which staging configuration to load and then the appropriate configuration is loaded.

    locale.php

    1.  
    2. <?php
    3.  
    4. $config = Zend_Registry::get(‘config’);
    5.  
    6. date_default_timezone_set($config->locale->timezone);
    7.  
    8. Zend_Locale_Format::setOptions(array(‘locale’ => Zend_Locale_Format::STANDARD, ‘date_format’ => $config->locale->date));
    9.  
    10. $locale_config = $config->locale;
    11.  
    12. if (array_key_exists(‘languages’, $locale_config->toArray())){    
    13.   $translater = new Zend_Translate(‘gettext’, BASE_DIR . ‘lang’);
    14.  
    15.   foreach ($locale_config->languages as $locale => $language){
    16.     if (file_exists(BASE_DIR . "lang/$locale.mo"))
    17.       $translater->addTranslation(BASE_DIR . "lang/$locale.mo", $locale);
    18.   }
    19.  
    20.   if (isset($_COOKIE[‘locale’])){
    21.     $translater->setLocale($_COOKIE[‘locale’]);
    22.     Zend_Registry::set(‘locale’, $_COOKIE[‘locale’]);
    23.   } else {
    24.     $translater->setLocale($locale_config->default);
    25.     Zend_Registry::set(‘locale’, $locale_config->default);
    26.   }
    27.  
    28.   Zend_Registry::set(‘Zend_Translate’, $translater);
    29. }
    30. ?>
    31.  

    Next the locale settings are applied. This probably deserves a topic all on its own but in a nutshell it sets the application locale default and if the locale config specifies the presence of translation files, a Zend_Translate object is initialized and loaded into the registry for the views to use.

    logger.php

    1.  
    2. <?php
    3. $config = Zend_Registry::get(‘config’);
    4.  
    5. if ($config->logger->enabled){
    6.   $LOGGER = new Zend_Log(new Zend_Log_Writer_Stream(BASE_DIR . $config->logger->file));
    7.   $logfilter = new Zend_Log_Filter_Priority(intval($config->logger->priority));
    8.   $LOGGER->addFilter($logfilter);
    9.  
    10. } else
    11.   $LOGGER = new Zend_Log(new Zend_Log_Writer_Null);
    12.  
    13.  
    14. Zend_Registry::set(‘logger’, $LOGGER);
    15.  
    16. function logobject($pObj, $pEcho=false){
    17.   return htmlspecialchars_decode(Zend_Debug::dump($pObj, null, $pEcho));
    18. }
    19.  
    20. function logerr($pSrc, $pMessage){
    21.   global $LOGGER;
    22.   $LOGGER->err("$pSrc: $pMessage");
    23. }
    24.  
    25. function logwarn($pSrc, $pMessage){
    26.   global $LOGGER;
    27.   $LOGGER->warn("$pSrc: $pMessage");
    28. }
    29.  
    30. function loginfo($pSrc, $pMessage){
    31.   global $LOGGER;
    32.   $LOGGER->info("$pSrc: $pMessage");
    33. }
    34.  
    35. function logdebug($pSrc, $pMessage){
    36.   global $LOGGER;
    37.   $LOGGER->debug("$pSrc: $pMessage");
    38. }
    39. ?>
    40.  

    If logging is enabled, a logger object is instantiated and placed in the registry. The default logger simply writes timestamped messages to a file in the log folder, otherwise a null log writer is created allowing logged messages to go into limbo if logging is not desired or increased performance is needed.

    database.php

    1.  
    2. <?php
    3. $config = Zend_Registry::get(‘config’);
    4.  
    5. if ($config->database->enabled){
    6.   require_once(‘Doctrine.php’);
    7.  
    8.   $conn = Doctrine_Manager::connection($config->database->dsn);
    9.  
    10.   Zend_Registry::set(‘database’, $CONN);
    11. }
    12.  
    13. ?>
    14.  

    This file creates and tests the database connection manager.

    mail.php

    1.  
    2. <?php
    3. $config = Zend_Registry::get(‘config’);
    4.  
    5. if ($config->mail->enabled){
    6.  
    7.   /* If mail setting set to smtp initialize smtp transport */
    8.   if ($config->mail->mode == ’smtp’){
    9.     Zend_Loader::loadClass(‘Zend_Mail_Transport_Smtp’);
    10.     $transport = new Zend_Mail_Transport_Smtp($config->mail->smtp->server, $config->mail->smtp->auth);
    11.     Zend_Mail::setDefaultTransport($transport);
    12.   }
    13. }
    14. ?>
    15.  

    This file configures the default mail transport adapter. By default, it relies on the built in sendmail functionality that Zend_Mail uses to defer to the server’s mail transport. Alternatively, it can be configured to use a configured smtp transport.

    view.php

    1.  
    2. <?php
    3.  
    4. function getViewEngine($pViewConfig){
    5.  
    6.   $ViewPluginClass = $pViewConfig->engine;
    7.   $ViewPluginConfig = $pViewConfig->config->toArray();
    8.  
    9.   if ($pViewConfig->layout){
    10.     Zend_Layout::startMvc();
    11.     Zend_Layout::getMvcInstance()->setViewSuffix($pViewConfig->ext);
    12.   }
    13.  
    14.   //Initialize View Template Engine
    15.  
    16.   $view_engine = new $ViewPluginClass($ViewPluginConfig);
    17.  
    18.   return $view_engine;
    19. }
    20.  
    21. $CONFIG = Zend_Registry::get(‘config’);
    22.  
    23. $view_engine = getViewEngine($CONFIG->view);
    24.  
    25. $vr = new Zend_Controller_Action_Helper_ViewRenderer();
    26. $vr->setView($view_engine);
    27. $vr->setViewSuffix($CONFIG->view->ext);
    28. Zend_Controller_Action_HelperBroker::addHelper($vr);
    29.  
    30. $DocTypeHelper = new Zend_View_Helper_Doctype();
    31. $DocTypeHelper->doctype(‘XHTML1_STRICT’);
    32.  
    33. $view_engine->addHelperPath(BASE_DIR . ‘lib/FP/View/Helpers/’, ‘FP_View_Helper’);
    34. ?>
    35.  

    The view subsystem configures Zend’s view module with the plugin of our choice. Our configuration implements a PHPTAL view plugin but it can easily be swapped out with something like Smarty or even left to the default phtml implemenation. View plugin specific configuration options are passed into the getViewEngine function which is globally accessible, allowing different application modules within our application to utilize different view engines. An example of why this is useful is having our main web application use PHPTAL for displaying templates, however, perhaps we have an enhanced mail module which utilizes Smarty to render email templates. In addition to configuring the view engine, this file also sets various defaults for some of the view helpers such as the default doctype, and additional view helper paths. I store highly reusable view helpers in the lib folder also, so we include that folder in our view helpers path as well to make them available globally.

    modules.php

    1.  
    2. <?php
    3. interface FP_Application_Module
    4. {
    5.   public function getTitle();
    6.   public function getName();
    7.   public function getVersion();
    8.   public function getAuthor();
    9.   public function getEmail();
    10.  
    11.   public function init();
    12.  
    13.   public function getRoutes();
    14. }
    15.  
    16. abstract class ApplicationModuleBroker
    17. {
    18.   protected static $_modules = array();  //key is verbose module name
    19.   protected static $_modules2 = array(); //key is short name
    20.   protected static $_dependencies = array();
    21.   protected static $_configs_loaded = array();
    22.  
    23.   public static function registerModule($pModule, &$pConfig){    
    24.     if (!in_array($pModule, self::$_configs_loaded)){
    25.  
    26.       //Merge module config
    27.       if (!$module_config = get_cache("config_$pModule")){
    28.         $modxml = BASE_DIR . "app/$pModule/config/config.xml";
    29.         if (file_exists($modxml)){
    30.           $module_config = new Zend_Config_Xml($modxml, $pConfig->mode, true);
    31.           set_cache($module_config, "config_$pModule");
    32.         }
    33.       }
    34.       if (isset($module_config)){      
    35.         $pConfig->merge($module_config);
    36.         self::$_configs_loaded[] = $pModule;
    37.         $mconfig = $module_config->toArray();
    38.         if (array_key_exists(‘deps’, $mconfig[$pModule])){
    39.           $defer_load = false;
    40.           foreach ($mconfig[$pModule][‘deps’] as $depname => $depversion){
    41.             if (!array_key_exists($depname, self::$_modules2)){
    42.               self::$_dependencies[] = array(‘dependent’ => $pModule, ‘dependency’ => $depname, ‘version’ => floatval($depversion));    
    43.               $defer_load = true;
    44.             }
    45.           }
    46.           if ($defer_load)
    47.             return;
    48.         }
    49.       }
    50.     }
    51.  
    52.     //Add models to include path
    53.     if (file_exists(BASE_DIR . "app/$pModule/models")){
    54.       set_include_path(get_include_path() . PATH_SEPARATOR . BASE_DIR . "app/$pModule/models");
    55.     }
    56.  
    57.     //Add lib to include path and require all php files inside
    58.     if (file_exists(BASE_DIR . "app/$pModule/lib")){
    59.       set_include_path(get_include_path() . PATH_SEPARATOR . BASE_DIR . "app/$pModule/lib");
    60.      
    61.       Zend_Loader::loadFile(BASE_DIR . "app/$pModule/lib/module.php");
    62.       $ModuleTitle = implodeCase(explode(‘_’, $pModule));   //Takes multi_word_module and converts to MultiWordModule
    63.       $ModuleClass = "{$ModuleTitle}Module";
    64.      
    65.       if (!$module = get_cache("fpmodule_$pModule")){
    66.        
    67.         if (class_exists($ModuleClass)){
    68.           $module = new $ModuleClass();
    69.           if (!is_object($module))
    70.             throw new Exception("Unable to instantiate module $pModule");
    71.           set_cache($module, "fpmodule_$pModule");
    72.         } else
    73.           throw new Exception("$pModule is not a module");
    74.       }
    75.       self::$_modules[$module->getTitle()] = $module;
    76.       self::$_modules2[$pModule] =& self::$_modules[$module->getTitle()];
    77.       $fc = Zend_Controller_Front::getInstance();
    78.       $router = $fc->getRouter();
    79.       $router->addRoutes($module->getRoutes())
    80.       $module->init();
    81.    
    82.     } else
    83.       throw new Exception("$pModule is not a module");
    84.  
    85.     //Add plugins to include path and require all contents
    86.     if (file_exists(BASE_DIR . "app/$pModule/plugins")){
    87.       $plugdir = opendir(BASE_DIR . "app/$pModule/plugins");
    88.       while ($plugin = readdir($plugdir)){
    89.         if ($plugin != ‘..’ && $plugin != ‘.’ && !preg_match(‘/^\..*/’, $plugin))
    90.           require_once(BASE_DIR . "app/$pModule/plugins/$plugin");
    91.       }
    92.     }
    93.  
    94.     foreach (self::$_dependencies as $i => $dep){
    95.       if ($dep[‘dependency’] == $pModule){
    96.         $depmod = self::$_modules2[$pModule];
    97.         if ($depmod->getVersion() >= $dep[$pModule][‘version’]){
    98.           unset(self::$_dependencies[$i]);
    99.           self::registerModule($dep[‘dependent’], $pConfig);
    100.         }
    101.       }
    102.     }
    103.   }
    104.  
    105.   public static function getModules(){
    106.     return self::$_modules;
    107.   }
    108.  
    109.   public static function getModule($pName){
    110.     return self::$_modules[$pName];
    111.   }
    112.  
    113.   public static function hasModule($pName){
    114.     return array_key_exists($pName, self::$_modules);
    115.   }
    116.  
    117.   public static function postInit(){
    118.     $modules = self::getModules();
    119.     foreach ($modules as $module){
    120.       if (method_exists($module, ‘postInit’))
    121.         $module->postInit();
    122.     }
    123.   }
    124. }
    125.  
    126. $CONFIG = Zend_Registry::get(‘config’);
    127. $CACHE = Zend_Registry::get(‘cache’);
    128. /* Scan app modules folder and add model folders to php include path */
    129.  
    130. $moddir = opendir(BASE_DIR . ‘app’);
    131.  
    132. while ($entry = readdir($moddir)){
    133.   if ($entry != ‘..’ && $entry != ‘.’ && !preg_match(‘/^\..*/’, $entry)){
    134.     if ($entry != ‘default’)
    135.       ApplicationModuleBroker::registerModule($entry, $CONFIG);
    136.   }
    137. }
    138.  
    139. ApplicationModuleBroker::registerModule(‘default’, $CONFIG);
    140. ApplicationModuleBroker::postInit();
    141.  
    142. Zend_Registry::set(‘config’, $CONFIG);
    143.  
    144. $main = Zend_Controller_Front::getInstance();
    145. $main->addModuleDirectory(BASE_DIR . ‘app’);
    146. $main->setModuleControllerDirectoryName(‘controllers’);
    147. ?>
    148.  

    This subsystem is the heart of the platform. In a nutshell the purpose of this file is to scan the app folder for application modules and load them. Loading them implies extending our include path to make module lib files available as well as module specific models. The easiest way for me to explain what the module loader is doing is to break it down into steps.

    1. Scan app folder

    2. For each subfolder in the app folder, call registerModule on the module broker, passing in the name of the module folder and pass by reference the global config object.

    3. If the module has not already been loaded, look for the module’s config xml file located in ‘app/$modulefolder/config/config.xml’. Load the appropriate staging config for that module and merge it into the global config object. This allows us to access the configuration for any application module using the same config object.

    4. Determine if this module has dependencies it requires before it can load. This means some app modules might require classes defined by other app modules. If the dependency module has not been loaded yet, the current module load can be deferred until the necessary dependencies are in place. If the load is deferred, registerModule() simply returns. Otherwise, we continue.

    5. Include the module’s models folder in the include path.

    6. Add the module’s lib folder in the include path. Inside that folder, we expect to find a file called module.php which contains the application module class we need to load. Every valid module should have this file and the appropriate class defined inside. The module class name is constructed using the module folder name. If the module folder is ‘blog’, then the module class name is ‘BlogModule’. Alternately, if the module folder is ‘news_feed’, then the module class name is ‘NewsFeedModule’. It will always follow this convention.

    7. Once we have the module class name we then instantiate the class. If all goes well, we should have a module object now that we can query for information we need about the module. Specifically we want to know what routes the module provides. All modules must implement the interface defined in our modules.php file which enforces the implementation of two critical module methods: getRoutes() and init(). The getRoutes() method returns an array of Zend_Controller_Router_Route objects which are registered with the front controller.

    8. Call init() on the module. Since application modules get cached to boost performance, init gets called to initialize transient variables that cannot carry over between requests.

    9. Scan list of deferred modules that are still waiting for dependencies to load. If all dependencies for a deferred moule are in place, call registerModule() again for that module.

    This happens for each folder in the app folder, the only special case being the ‘default’ folder which always gets called last. the default module is our custom application and it gets loaded last because it will always depend on the other modules to be loaded first.  After all the modules have been loaded, we do one more sweep and call postInit() on any modules that offer the method. This is a great way to do processing on all of the modules once they are all loaded. It might help to understand this better once I demonstrate an example of an application module. See below for the example.

    The last file to get parsed in the bootstrap is init.php

    init.php

    1.  
    2. <?php
    3.  
    4.  
    5. $config = Zend_Registry::get(‘config’);
    6. $front = Zend_Controller_Front::getInstance();
    7.  
    8. $baseUrl = $front->getBaseUrl();
    9.  
    10. Zend_Registry::set(‘baseUrl’, preg_replace(‘/\/$/’, , $baseUrl));
    11. Zend_Registry::set(‘docroot’, $_SERVER[‘DOCUMENT_ROOT’]);
    12. Zend_Registry::set(’site’, $config->default->site);
    13. Zend_Registry::set(’server’, ((array_key_exists(‘HTTPS’, $_SERVER) && $_SERVER[‘HTTPS’]) ? ‘https://’ : ‘http://’) . $_SERVER[‘SERVER_NAME’]);
    14. Zend_Registry::set(‘paths’, $config->paths);
    15. Zend_Registry::set(‘assets’, "$baseUrl/{$config->paths->assets}");
    16. Zend_Registry::set(’scripts’, "$baseUrl/{$config->paths->scripts}");
    17. Zend_Registry::set(’stylesheets’, "$baseUrl/{$config->paths->stylesheets}");
    18. Zend_Registry::set(‘images’, "$baseUrl/{$config->paths->images}");
    19. Zend_Registry::set(‘fonts’, "$baseUrl/{$config->paths->fonts}");
    20. Zend_Registry::set(‘upload’, $config->paths->upload);
    21. ?>
    22.  

    This file is more or less a spill over for bootstrap operations that either don’t go any where else or rely on all the subsystems being loaded already. As you can see I use it for assigning a boat load of convenience varialbe to the registry for use throughout the application.

    So now I want to demonstrate the structure of a real world application module. I’ll demonstrate my ACL module I recently wrote to handle security for most of my applications. It’s purpose is to generate Access Control Lists to manage access to secure resources, roles, users, and permissions. Here is it’s folder structure:

    • app/
      • acl/
        • config/config.xml
        • controllers/
        • lib/
          • module.php
        • models/
        • views/
          • helpers/
          • layouts/
          • templates/
      • default/

    config and lib are technically the only folders required for a functional module but usually you will need controllers and views as well. Each module is comprised of it’s module class, a config.xml file, some controllers, some models, and view components which are usually templates, helpers, and layouts. They are arranged heirarchichly like those so they can be simply dumped into the app folder drag’n'drop like. Application modules act very much like plugins for your main web application. They can provide a variety of functionality such as providing forum features, blog features, content management, etc, whatever you can imagine.

    The heart of a module is the module class defined in ‘lib/module.php’

    acl/lib/module.php

    1.  
    2. <?php
    3. class AclModule implements FP_Application_Module, Acl_Client_Interface
    4. {
    5.   const VERSION = 2.0;
    6.   const DATE = ‘03/31/2009′;
    7.   const AUTHOR = ‘Brandon Clark’;
    8.   const EMAIL = ‘brandon.clark@eroi.com’;
    9.   const TITLE = ‘Access Control’;
    10.   const NAME = ‘acl’;
    11.  
    12.   private $_routes = array();
    13.   private $_permissions = array();
    14.  
    15.   public function __construct(){
    16.     $this->_routes = array(
    17.                            ‘acl_admin’ => new Zend_Controller_Router_Route(‘acl/:tab’, array(‘controller’ => ‘index’, ‘action’ => ‘admin’, ‘module’ => ‘acl’, ‘tab’ => null)),
    18.                            ‘acl_synchronize’ => new Zend_Controller_Router_Route(‘acl/synchronize’, array(‘controller’ => ‘index’, ‘action’ => ’synchronize’, ‘module’ => ‘acl’)),
    19.                            ‘acl_acl_admin’ => new Zend_Controller_Router_Route(‘acl/acl/admin/:page’, array(‘controller’ => ‘acl’, ‘action’ => ‘admin’, ‘module’ => ‘acl’, ‘page’ => 1)),
    20.                            ‘acl_acl_permissions’ => new Zend_Controller_Router_Route(‘acl/acl/permissions/:acl_id/:page’, array(‘controller’ => ‘acl’, ‘action’ => ‘aclpermissions’, ‘module’ => ‘acl’, ‘page’ => 1)),
    21.                            ‘acl_acl_create_permission’ => new Zend_Controller_Router_Route(‘acl/acl/create-permission/:acl_id’, array(‘controller’ => ‘acl’, ‘action’ => ‘createpermission’, ‘module’ => ‘acl’)),
    22.                            ‘acl_acl_create’ => new Zend_Controller_Router_Route_Static(‘acl/acl/create’, array(‘controller’ => ‘acl’, ‘action’ => ‘create’, ‘module’ => ‘acl’)),
    23.                            ‘acl_acl_edit’ => new Zend_Controller_Router_Route(‘acl/acl/edit/:id’, array(‘controller’ => ‘acl’, ‘action’ => ‘edit’, ‘module’ => ‘acl’)),
    24.                            ‘acl_acl_delete’ => new Zend_Controller_Router_Route(‘acl/acl/delete/:id’, array(‘controller’ => ‘acl’, ‘action’ => ‘delete’, ‘module’ => ‘acl’)),
    25.                            );    
    26.  
    27.     $this->_permissions = array(
    28.                                 array(‘acl’ => ‘Controller Access Control’, ‘name’ => ‘Synchronize Acl Modules’, ‘resource’ => ‘acl_index’, ‘privilege’ => ’synchronize’),
    29.                                 );
    30.  
    31.   }
    32.  
    33.   //FP_Application_Module Interface=================================================
    34.  
    35.   public function init(){    
    36.     //Initialize ACL Plugins
    37.     $plugins = Zend_Registry::get(‘config’)->acl->plugins->toArray();
    38.     foreach ($plugins as $name => $plugin_config){
    39.       if ($plugin_config[‘enabled’]){
    40.         $plugin_class = "Acl_Plugin_{$name}";   
    41.         call_user_func(array($plugin_class, ‘Register’));
    42.       }
    43.     }
    44.   }
    45.  
    46.   public function getTitle(){
    47.     return self::TITLE;
    48.   }
    49.  
    50.   public function getName(){
    51.     return self::NAME;    
    52.   }
    53.  
    54.   public function getVersion(){
    55.     return self::VERSION;
    56.   }
    57.  
    58.   public function getAuthor(){
    59.     return self::AUTHOR;
    60.   }
    61.  
    62.   public function getEmail(){
    63.     return self::EMAIL;
    64.   }
    65.  
    66.   public function getRoutes(){
    67.     return $this->_routes;
    68.   }
    69.  
    70.   //END FP_Application_Module Interface==============================================
    71.  
    72.   //IACLClient Interface=============================================================
    73.  
    74.   public function getPermissions(){
    75.     return $this->_permissions;
    76.   }
    77.  
    78.   //END IACLClient Interface=========================================================
    79.  
    80. }
    81. ?>
    82.  

    I have snipped out much of the implementation to remain in scope of the article so you can see the methods that comply with the FP_Application_Module interface. These are the minimum methods needed to be loaded by the module loader.

    app/acl/config/config.xml

    1.  
    2. <?xml version="1.0"?>
    3. <configdata>
    4.   <production>
    5.     <acl>
    6.       <enabled>1</enabled>
    7.       <mode>allow</mode>
    8.       <backend>Db</backend>
    9.       <guest>Guest</guest>      
    10.       <list>
    11.         <limits>10,25,50,100</limits>
    12.         <default_limit>50</default_limit>       
    13.       </list>
    14.       <plugins>
    15.         <Controller>
    16.           <enabled>1</enabled>
    17.           <exempt>
    18.             <error>default_error</error>
    19.           </exempt>
    20.         </Controller>
    21.       </plugins>
    22.     </acl>
    23.   </production>
    24.   <debug extends="production">
    25.   </debug>
    26. </configdata>
    27.  

    The specifics of the configuration info specified in this file isn’t so important as it is completely arbitrary and tailored to what the individual module expects to find there. The key is to make sure that the configuration is contained within a node that shares the name of the module folder, ‘acl’ in this case so it can be properly merged into the global config object and be readily accessed by name.

    1.  
    2. <?php
    3.  
    4. $enabled = Zend_Registry::get(‘config’)->acl->enabled;
    5.  
    6. ?>
    7.  

    So there’s a number of classes I covered in the archived post but not in this one and that’s only because of the sheer volume of code and I didn’t want this post to become too overwhelming. The older posts were intended to be an introduction to Zend Framework MVC methodologies whereas this post is mainly a refresher on the latest methods I use and should build on those topics. To fill in the gaps, i’ve made the code available for a skeleton app available here so everyone can review the code and re-use it for their own projects.

  • Object Oriented GD

    Posted on April 6th, 2009 brandon 1 comment

    Sooner or later we all need to use the gd library whether it be for generating thumbnails, scaling images, cropping, or whatever. When compiled with gd support, PHP offers a massive arsenal of functions to accomplish all of the image related tasks you can think of. With the advent of object oriented frameworks such as the Zend Framework and others, it can get a little awkward mixing procedural code with with objects. Additionally code for processing images can get quite ugly with all of the variable juggling.

    After a number of image heavy projects, I got tired of the cut’n'paste headache and decided that it would be much easier to maintain graphical applications that interacted with images in an object-oriented fashion. I developed a small tool chain for encapsulating images and mechanisms for processing them. I start with a simple class definition which allows me to treat an image as an object.

    1.  
    2. <?php
    3.  
    4. class FP_Image
    5. {
    6.   const PNG = ‘png’;
    7.   const JPEG = ‘jpg’;
    8.   const GIF = ‘gif’;
    9.  
    10.   protected $image = null;
    11.   protected $width = null;
    12.   protected $height = null;
    13.  
    14.   public function __construct($pFile, $pMimetype=null){
    15.     $this->image = $this->create($pFile, $pMimetype);
    16.     $this->width = imageSX($this->image);
    17.     $this->height= imageSY($this->image);
    18.   }
    19.  
    20.   protected function create($pFile, $pMimetype=null){        
    21.     if (preg_match(‘/^\w+:\/\/.*/’, $pFile)){                 //Image path is url
    22.       $meta = stream_get_meta_data(fopen($pFile, ‘r’));
    23.      
    24.       foreach ($meta[‘wrapper_data’] as $header){
    25.         if (preg_match(‘/^Location:\ /’, $header)){            //Is this a redirect? Get real url
    26.           $redirect = true;
    27.           $url = preg_replace(‘/^Location:\ /’, , $header);
    28.         }
    29.       }
    30.  
    31.       if (!$redirect)
    32.         $url = $pFile;
    33.  
    34.       $url_path = parse_url($url, PHP_URL_PATH);
    35.       $path_parts = pathinfo($url_path);
    36.       $ext = $path_parts[‘extension’];
    37.     } else {                                                   //Just a file
    38.       $path_parts = pathinfo($pFile);
    39.       $ext = $path_parts[‘extension’];
    40.       $url = $pFile;
    41.     }
    42.  
    43.     if ($pMimetype){
    44.       logdebug(‘FP_Image.create’, "Forcing mimetype $pMimetype");
    45.       switch ($pMimetype){
    46.       case ‘image/jpeg’:
    47.         $res = imagecreatefromjpeg($url);
    48.         break;
    49.       case ‘image/png’:
    50.         $res = imagecreatefrompng($url);
    51.         break;
    52.       case ‘image/gif’:
    53.         $res = imagecreatefromgif($url);
    54.         break;
    55.       default:
    56.         throw new FP_Image_Exception("Unsupported format $url as $pMimetype", FP_Image_Exception::UNSUPPORTED_FORMAT);
    57.       }
    58.     } else {
    59.       switch(strtolower($ext)){
    60.       case ‘jpg’:
    61.       case ‘jpeg’:
    62.         $res = imagecreatefromjpeg($url);  
    63.         break;
    64.       case ‘png’:
    65.         $res = imagecreatefrompng($url);
    66.         break;
    67.       case ‘gif’:
    68.         $res = imagecreatefromgif($url);
    69.         break;
    70.       default:
    71.         throw new FP_Image_Exception("Unsupported format $url", FP_Image_Exception::UNSUPPORTED_FORMAT);
    72.       }
    73.     }
    74.     if (!is_resource($res))
    75.       throw new FP_Image_Exception("An error occured while loading $url. Please verify that the image exists and is not corrupt", FP_Image_Exception::LOAD_ERROR);
    76.    
    77.     return $res;
    78.   }
    79.  
    80.   public function __get($pAtt){
    81.     switch (strtolower($pAtt)){
    82.     case ‘width’:
    83.       return $this->width;
    84.     case ‘height’:
    85.       return $this->height;
    86.     case ‘image’:
    87.       return $this->image;
    88.     }
    89.   }
    90.  
    91.   public function save($pDestination){
    92.     $path_parts = pathinfo($pDestination);
    93.     $ext = $path_parts[‘extension’];
    94.  
    95.     switch (strtolower($ext)){
    96.     case ‘jpg’:
    97.     case ‘jpeg’:
    98.       return imagejpeg($this->image,$pDestination, 100);    
    99.     case ‘png’:
    100.       return imagepng($this->image,$pDestination, 9);      
    101.     case ‘gif’:
    102.       return imagegif($this->image,$pDestination);
    103.     default:
    104.       return false;
    105.     }
    106.   }
    107.  
    108.   public function stream($pFormat=self::PNG){  
    109.     switch ($pFormat){
    110.     case self::PNG:
    111.       return imagepng($this->image);      
    112.     case self::JPEG:
    113.       return imagejpeg($this->image);        
    114.     case self::GIF:
    115.       return imagegif($this->image);    
    116.     default:
    117.       throw new FP_Image_Exception("Unsupported format $pFormat", FP_Image_Exception::UNSUPPORTED_FORMAT);      
    118.     }
    119.   }
    120.  
    121.   public function __destroy(){
    122.     imagedestroy($this->image);
    123.   }
    124. }
    125. ?>
    126.  

    This class takes a file path or url as its constructor argument. Using some tricky http header sniffing we are able to determine if the provided url is actually a redirect and in turn we are able to get the real url of the image. This is a handy feature as it allows us to process remotely hosted images on the fly which gives us the option of using CDNs or clouds for image storage. Next a gd resource is created and stored internally.

    The object exposes 3 properties (width, height, and image). The first two are obvious while the image property is a reference to the gd resource should we ever need to deal with it directly. The save method allows the image to be saved to a file in the format specified by the extension of the destination path. The stream function is useful for streaming the raw image data to a web browser when you need to link to a dynamically generated image. The object destructor frees the gd resource from memory.

    So at first glance this class isn’t terribly useful aside from the fact that it handles the image format automatically. It’s true utility comes by extending it to encapsulate additional functionality. We can use this class as a rich base class that provides the mundane common functionality that all of the subclasses need. The simplest example would be probably the most common use of gd on the web: the generation of thumbnails

    Generate Thumbnails

    1.  
    2. <?php
    3.  
    4. class FP_Image_Thumbnail extends FP_Image
    5. {
    6.  
    7.   public function __construct($pFile, $pWidth, $pHeight, $pDestination=null, $pMimetype=null){
    8.     if (is_resource($pFile))
    9.       $img = $pFile;
    10.     else if (is_object($pFile) && $pFile instanceof FP_Image)
    11.       $img = $pFile->image;
    12.     else
    13.       $img = $this->create($pFile, $pMimetype);
    14.  
    15.     $owidth = imageSX($img);
    16.     $oheight= imageSY($img);
    17.  
    18.     if ($pWidth < $owidth)
    19.       $nwidth = floor(($pWidth/$owidth) * $owidth);
    20.     else
    21.       $nwidth = $owidth;
    22.  
    23.     if ($pHeight < $oheight)
    24.       $nheight = floor($pHeight/$oheight * $oheight);
    25.     else
    26.       $nheight = $oheight;
    27.  
    28.     $this->image = ImageCreateTrueColor($nwidth, $nheight);
    29.     imagecopyresampled($this->image, $img, 0, 0, 0, 0, $nwidth, $nheight, $owidth, $oheight);
    30.  
    31.     imagedestroy($img);
    32.  
    33.     if ($pDestination)
    34.       $this->save($pDestination);
    35.   }
    36. }
    37. ?>
    38.  

    Here all we overload is the constructor. This class has the added bonus of being able to accept other objects that extend the FP_Image class as well as raw gd resources. The constructor takes this agnostic image as well as a width and height desired for the thumbnail. The class does all of the processing it needs and then stores the created thumbnail internally which we can then access using the methods provided by FP_Image.

    Here is a quick example of how to generate dynamic thumbnails from a Zend Framework controller action using this class:

    1.  
    2. <?php
    3. class ImageController extend Zend_Controller_Action {
    4.  
    5.   public function thumbnailAction(){
    6.     $this->_response->setHeader(‘Content-Type’, ‘image/png’, true)
    7.                             ->setHeader(‘Content-Transfer-Encoding’, ‘binary’, true);
    8.  
    9.     $thumbnail = new FP_Image_Thumbnail(‘/var/www/test/images/avatar.jpg’, 100, 100);    
    10.     $thumbnail->stream();
    11.   }
    12.  
    13. }
    14. ?>
    15.  

    Scale Images

    1.  
    2. <?php
    3.  
    4. class FP_Image_Scale extends FP_Image
    5. {
    6.   const HORIZONTAL = ‘horizontal’;
    7.   const VERTICAL = ‘vertical’;
    8.  
    9.   public function __construct($pFile, $pMaxSize, $pScaleMode=self::HORIZONTAL, $pDestination=null, $pMimetype=null){
    10.     logdebug(‘FP_Image_Scale.__construct’, "Scaling ". logobject($pFile) ." to $pScaleMode of $pMaxSize");
    11.  
    12.     if (is_resource($pFile))
    13.       $img = $pFile;
    14.     else if (is_object($pFile) && $pFile instanceof FP_Image)
    15.       $img = $pFile->image;
    16.     else
    17.       $img = $this->create($pFile, $pMimetype);
    18.  
    19.     $owidth = imageSX($img);
    20.     $oheight= imageSY($img);
    21.  
    22.     switch ($pScaleMode){
    23.     case self::HORIZONTAL:      
    24.       $width = $pMaxSize;
    25.       $height = round(($oheight * $width) / $owidth);
    26.       break;
    27.     case self::VERTICAL:
    28.       $height = $pMaxSize;
    29.       $width = round(($owidth * $height) / $oheight);
    30.     default:
    31.       throw new Exception(‘Unhandled scale mode ‘.$pScaleMode);
    32.     }
    33.  
    34.     $this->image = ImageCreateTrueColor($width, $height);
    35.     imagecopyresampled($this->image, $img, 0, 0, 0, 0, $width, $height, $owidth, $oheight);
    36.    
    37.     $this->width = $width;
    38.     $this->height = $height;
    39.  
    40.     imagedestroy($img);
    41.  
    42.     if ($pDestination)
    43.       $this->save($pDestination);
    44.   }
    45. }
    46. ?>
    47.  

    This class isn’t all that different, instead of scaling down both dimensions it scales either vertically or horizontally while preserving the aspect ratio.

    Crop Images

    1.  
    2. <?php
    3.  
    4. class FP_Image_Crop extends FP_Image
    5. {
    6.   public function __construct($pFile, $pX, $pY, $pWidth, $pHeight, $pDestination=null){
    7.     //logdebug(’FP_Image_Crop.__construct’, "Cropping $pFile at $pX,$pY ($pWidth x $pHeight) destination=$pDestination");
    8.  
    9.     if (is_resource($pFile))
    10.       $original = $pFile;
    11.     else if (is_object($pFile) && $pFile instanceof FP_Image)
    12.       $original = $pFile->image;
    13.     else
    14.       $original = $this->create($pFile);
    15.  
    16.     $this->image = imagecreatetruecolor($pWidth, $pHeight);
    17.     imagecopyresampled($this->image, $original, 0, 0, $pX, $pY, $pWidth, $pHeight, $pWidth, $pHeight);  
    18.  
    19.     imagedestroy($original);
    20.  
    21.     if ($pDestination){
    22.       $this->save($pDestination);
    23.     }
    24.   }
    25. }
    26. ?>
    27.  

    This class takes another image and crops it to the specifications passed into the constructor.

    Watermark an Image

    1.  
    2. <?php
    3.  
    4.   //Overlay image ontop of another
    5. class FP_Image_Watermark extends FP_Image
    6. {
    7.   public function __construct($pBaseFile, $pOverlayFile, $pDX, $pDY, $pSX, $pSY, $pSWidth, $pSHeight, $pAlphaBlending=true, $pDestination=null, $pMimetype=null){
    8.     if (is_resource($pBaseFile))
    9.       $base = $pBaseFile;
    10.     else if (is_object($pBaseFile) && $pBaseFile instanceof FP_Image)
    11.       $base = $pBaseFile->image;
    12.     else
    13.       $base = $this->create($pBaseFile, $pMimetype);
    14.  
    15.     if (is_resource($pOverlayFile))
    16.       $overlay = $pOverlayFile;
    17.     else if (is_object($pOverlayFile) && $pOverlayFile instanceof FP_Image)
    18.       $overlay = $pOverlayFile->image;
    19.     else
    20.       $overlay = $this->create($pOverlayFile);
    21.  
    22.     imagealphablending($overlay, $pAlphaBlending);
    23.  
    24.     imagecopy($base, $overlay, $pDX, $pDY, $pSX, $pSY, $pSWidth, $pSHeight);
    25.     $this->image = $base;
    26.  
    27.     $this->width = imageSX($this->image);
    28.     $this->height = imageSY($this->image);
    29.  
    30.  
    31.     if ($pDestination)
    32.       $this->save($pDestination);
    33.   }
    34. }
    35. ?>
    36.  

    This is a handy class for embedding a watermark into an image. This takes a base image and a secondary image and overlays it with alpha blending allowing for subtle semi-transparent watermarks.

    Overlay True Type Text over Image

    1.  
    2. <?php
    3.  
    4. class FP_Image_MultiLineOverlay extends FP_Image {
    5.  
    6.   const LEFT = ‘left’;
    7.   const CENTER = ‘center’;
    8.   const RIGHT = ‘right’;
    9.  
    10.   protected $text = null;
    11.   protected $angle = null;
    12.   protected $font_url = null;
    13.   protected $font_size = 10;
    14.   protected $leading = 0;
    15.   protected $justify = self::LEFT;
    16.   protected $foreground_color = array(255,255,255);
    17.   protected $text_x = 0;
    18.   protected $text_y = 0;
    19.   protected $padding = array(‘t’ => 0, ‘r’ => 0, ‘b’ => 0, ‘l’ => 0);
    20.   protected $truncate_at = 0;
    21.  
    22.   public function __construct($pBaseFile, $pText, $pFontUrl, array $pOptions=array(), $pDestination=null, $pMimetype=null){
    23.     if (is_resource($pBaseFile))
    24.       $this->image = $pBaseFile;
    25.     else if (is_object($pBaseFile) && $pBaseFile instanceof FP_Image)
    26.       $this->image = $pBaseFile->image;
    27.     else
    28.       $this->image = $this->create($pBaseFile, $pMimetype);
    29.  
    30.     $this->width = imageSX($this->image);
    31.     $this->height= imageSY($this->image);
    32.  
    33.     $this->text = $pText;
    34.     $this->font_url = $pFontUrl;
    35.  
    36.     if (array_key_exists(‘font_size’, $pOptions))
    37.       $this->font_size = $pOptions[‘font_size’];
    38.  
    39.     if (array_key_exists(‘justify’, $pOptions))
    40.       $this->justify = $pOptions[‘justify’];
    41.  
    42.     if (array_key_exists(‘leading’, $pOptions))
    43.       $this->leading = $pOptions[‘leading’];
    44.  
    45.     if (array_key_exists(‘font_size’, $pOptions))
    46.       $this->font_size = $pOptions[‘font_size’];
    47.  
    48.     if (array_key_exists(‘foreground_color’, $pOptions))
    49.       $this->foreground_color = $pOptions[‘foreground_color’];
    50.  
    51.     if (array_key_exists(‘truncate’, $pOptions))
    52.       $this->truncate_at = $pOptions[‘truncate’];
    53.  
    54.  
    55.     if (array_key_exists(‘padding’, $pOptions)){
    56.       $padding = $pOptions[‘padding’];
    57.       if (array_key_exists(‘t’, $padding))
    58.         $this->padding[‘t’] = $padding[‘t’];
    59.       if (array_key_exists(‘r’, $padding))
    60.         $this->padding[‘r’] = $padding[‘r’];
    61.       if (array_key_exists(‘b’, $padding))
    62.         $this->padding[‘b’] = $padding[‘b’];
    63.       if (array_key_exists(‘l’, $padding))
    64.         $this->padding[‘l’] = $padding[‘l’];      
    65.     }
    66.  
    67.     if (array_key_exists(‘angle’, $pOptions))
    68.       $this->angle = $pOptions[‘angle’];
    69.  
    70.     $test_text = ‘abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_’;
    71.     $_globalbox = imageTTFBbox($this->font_size, 0, $this->font_url, $test_text);
    72.     $globalbox = convertBoundingBox($_globalbox);
    73.  
    74.     $width = $this->width;
    75.     $height = $this->height;
    76.  
    77.     $text_color = imagecolorallocate($this->image, $this->foreground_color[0],$this->foreground_color[1],$this->foreground_color[2]);
    78.  
    79.     if ($this->justify == self::LEFT){
    80.       if ($this->truncate_at > 0)
    81.         $text = truncate($this->text, $this->truncate_at);
    82.       else
    83.         $text = $this->text;
    84.  
    85.       $text_x = $this->text_x + $this->padding[‘l’];
    86.       imagettftext($this->image, $this->font_size, $this->angle, $text_x, $this->font_size, $text_color, $this->font_url, $text);
    87.     } else {
    88.       $strings = preg_split(‘/\n/’, $this->text);
    89.       $__H = $this->padding[‘t’];
    90.  
    91.       if ($this->justify == self::RIGHT) {
    92.  
    93.         foreach ($strings as $key => $val){
    94.           if ($this->truncate_at > 0)
    95.             $text = truncate($val, $this->truncate_at);
    96.           else
    97.             $text = $val;
    98.          
    99.           $_b = imageTTFBbox($this->font_size, 0, $this->font_url, $text);
    100.           $box = convertBoundingBox($_b);
    101.  
    102.           $_W = $box[‘width’];
    103.  
    104.           $_X = $width - $_W - $this->padding[‘r’];
    105.          
    106.           $_H = $globalbox[‘height’];
    107.           $__H += $_H + $this->leading;     
    108.  
    109.           imagettftext($this->image, $this->font_size, $this->angle, $_X, $__H, $text_color, $this->font_url, $text);
    110.         }
    111.       } else if ($this->justify == self::CENTER){
    112.  
    113.         foreach ($strings as $key => $val){
    114.           if ($this->truncate_at > 0)
    115.             $text = truncate($val, $this->truncate_at);
    116.           else
    117.             $text = $val;
    118.  
    119.           $_b = imageTTFBbox($this->font_size, 0, $this->font_url, $text);
    120.           $box = convertBoundingBox($_b);
    121.          
    122.           $_W = $box[‘width’];
    123.  
    124.           $_X = abs($width / 2) - abs($_W / 2);
    125.  
    126.           $_H = $globalbox[‘height’];
    127.  
    128.           $__H += $_H;
    129.           imagettftext($this->image, $this->font_size, $this->angle, $_X, $__H, $text_color, $this->font_url, $text);
    130.           $__H += $this->leading;
    131.         }
    132.       }
    133.     }
    134.  
    135.     if ($pDestination)
    136.       $this->save($pDestination);
    137.   }
    138. }
    139. ?>
    140.  

    This one is fairly sophisticated compared to the others. In a nutshell it allows multiple lines of true type text to be overlayed ontop of an image. It takes a wide array of configuration option to specify things like font color, padding, justification, angle, etc. This class probably deserves a post all in itself so trust me when I say it excels in utility.

    Because all of these classes all extend the same base class they can be used in elaborate combinations by overlaying text over a watermarked image that is then in turn scaled down. Also in this structure, these classes are completely generalized and are portable to any application that needs image processing.

  • Domain Model Programming With the Zend Framework

    Posted on April 3rd, 2009 brandon 3 comments

    Each application I write tends to be an evolutionary successor to the previous and I suspect this is the case for most developers. With long term projects one tends to find things that work better than others, and methods that end up being a real pain in the ass and not as scalable as hoped once the code line count breaks a couple thousand or so. When I first got hired on at eROI I’ll admit my php was a bit rusty. I was fairly comfortable with it as I had developed a grab bag of small sites using procedural php and mysql, and I had a good understanding of the language as a language junky. It was fortunate that prior to working at eROI that I had delved head first into the world of Ruby on Rails and various python frameworks all of which allowed me to bask in the goodness that is MVC (Model-View-Controller architecture). As a result when I came on board with the charter of deploying large scale applications in php, my first goal was to figure out how to make php less painful and how to deploy the design patterns I had grown accustomed to.

    The Zend Framework was the perfect toolkit for these ends as it provided all the abstracted functionality I could want. The first site I built using the framework was pretty cool but I’ll be the first to admit I’d sooner have a foot amputated than have someone look at the code. While I had experience with MVC, I was still pretty green and my controllers were embarrassingly bloated with logic and manual manipulation of model objects. Controllers had an unmaintainable knowledge of model objects and even minor edits proved to be disastrous as a fix in one block of code would break another somewhere else. Learning is fun. Fortunately, that website never saw the light of day. When the next major project landed on my desk, I was chomping at the bit to apply all of the lessons I had learned from the previous abortion of code. Like a good little programmer I took the logic out of the controllers and pushed it into the models resulting in a much more maintainable codebase. That website did get deployed and has been extended multiple times without much ado and has proved to be a delight to work with.

    The following few projects, naturally, were built on evolutions of that same codebase, each one more abstracted and generalized than the one before. I’m a big fan of reusable code and have this vision of doubling my productivity every six months or so by applying the right technologies and re-purposing black box code whenever possible. I can’t say for certain if I’ve stayed true to the road map, but I have noticed that the projects get larger and more complex but development time is remaining the same so I must be doing something right. (I know I’m rambling on but bare with me, I’ll get to the topic in a moment). The last project I did was enormous. I started to see my coding methods strain under the pressure of the demands of the application and maintainability started to slip a bit. It was time for a major evolution.

    Enter: Domain Model Programming. I’m a total newb to this design pattern so I won’t comment much on it and instead refer you to wikipedia’s description. In a nutshell the idea is to separate out the true business logic from the persistance layer implementation. I first encountered the concept here on the blog of a huge Zend Framework contributor, during a search for some annoying abstraction problem I was having and when I read Matthew’s article a light bulb went on. Introducing this method to my coding has had the effect of allowing me to break the sound barrier and accomplish the true abstraction that I have needed for real re-usable objects. So now I am going to introduce how I have implemented it using the Zend Framework.

    So recently I decided to try out the Doctrine ORM library in place of the included Zend_Db module and in short I haven’t looked back. This made for a perfect opportunity to show how the database implementation can be made to be completely interchangable without having to recode the models or change any of the application specific logic. The point is the models, controllers and views have no concept of the persistance layer so it should be a breeze to swap out Zend_Db for Doctrine, and should that approach go south, it’s no effort to switch back, or replace both with something else entirely.

    So first things first. Let me introduce everyone to a series of base classes that can be re-used in any project. These classes provide the base functionality that this design pattern needs leaving the models themselves free to only concern themselves with the application itself.

    1.  
    2. <?php
    3.  
    4. abstract class FP_Model {
    5.   protected $_data = array();
    6.   protected $_gateway = null;
    7.  
    8.   public function __construct(array $pData, $pGateway){
    9.     $this->setGateway($pGateway);
    10.     $this->import($pData);
    11.   }
    12.  
    13.   public function __set($pAtt, $pVal){
    14.     if (array_key_exists($pAtt, $this->_data))
    15.       $this->_data[$pAtt] = $pVal;
    16.   }
    17.  
    18.   public function __get($pAtt){
    19.     switch ($pAtt){
    20.     case ‘gateway’:
    21.       return $this->getGateway();
    22.     default:
    23.       if (array_key_exists($pAtt, $this->_data))
    24.         return $this->_data[$pAtt];
    25.       else
    26.         throw new Exception("Invalid attribute - $pAtt");
    27.     }
    28.   }
    29.  
    30.   public function __isset($pAtt){
    31.     if (array_key_exists($pAtt, $this->_data))
    32.       return isset($this->_data[$pAtt]);
    33.     else
    34.       return false;
    35.   }
    36.  
    37.   public function __unset($pAtt){
    38.     if (array_key_exists($pAtt, $this->_data))
    39.       unset($this->_data[$pAtt]);
    40.     else
    41.       return null;
    42.   }  
    43.  
    44.   public function validate($pData){
    45.     $pData = $this->_convert($pData);
    46.  
    47.     return true;
    48.   }
    49.  
    50.   public function import($pData){
    51.     foreach ($pData as $key => $val)
    52.       $this->__set($key, $val);
    53.  
    54.     return $this;
    55.   }
    56.  
    57.   public function setGateway($pGateway){
    58.     $this->_gateway = $pGateway;
    59.     return $this;
    60.   }
    61.  
    62.   public function getGateway(){
    63.     return $this->_gateway;
    64.   }
    65.  
    66.   public function toArray(){
    67.     return $this->_data;
    68.   }
    69.  
    70.   protected function _convert($pData){
    71.     if (is_array($pData))
    72.       return $pData;
    73.     else if (is_object($pData))
    74.       return (array) $pData;           
    75.     else
    76.       throw new Exception("Data must be an datasource, array or object");
    77.   }
    78. }
    79. ?>
    80.  

    This is the class that all models extend. It’s purpose is to provide general CRUD functionality. The idea is that a model represents an object that has attributes and an interface to save itself. With that in mind, the base class manages an attribute array and an object that we refer to as a gateway. The gateway is the object that talks to the persistence layer so it acts as a broker for the model whenever the model needs to be saved or needs additional data from outside of itself.

    1.  
    2. <?php
    3.  
    4. class FP_Model_Collection implements Iterator, Countable
    5. {
    6.   protected $_model_class = ‘FP_Model’;
    7.   protected $_gateway = null;
    8.   protected $_collection = null;
    9.   protected $_count = null;
    10.  
    11.   public function __construct($pItems,` $pGateway){    
    12.     $this->setGateway($pGateway);
    13.     $this->setCollection($pItems);
    14.   }
    15.  
    16.   public function setGateway($pGateway){
    17.     $this->_gateway = $pGateway;
    18.     return $this;
    19.   }
    20.  
    21.   public function getGateway(){
    22.     return $this->_gateway;
    23.   }
    24.  
    25.   public function setCollection($pItems){
    26.     $this->_collection = $pItems;
    27.   }
    28.  
    29.   public function getCollection(){
    30.     return $this->_collection;
    31.   }
    32.  
    33.   public function count(){
    34.     if ($this->_count === null)
    35.       $this->_count = count($this->_collection);
    36.     return $this->_count;
    37.   }
    38.  
    39.   public function current(){
    40.     if ($this->_collection instanceof Iterator)
    41.       $key = $this->_collection->key();
    42.     else
    43.       $key = key($this->_collection);
    44.  
    45.     if ($key === null)
    46.       return false;
    47.  
    48.     $item = $this->_collection[$key];
    49.  
    50.     if ((get_class($item) != $this->_model_class) && !is_subclass_of($item, $this->_model_class)){
    51.       $ModelClass = $this->_model_class;
    52.       $item = new $ModelClass($item, $this->_gateway);
    53.       $this->_collection[$key] = $item;
    54.     }
    55.  
    56.     return $item;
    57.   }
    58.  
    59.   public function key(){
    60.     return key($this->_collection);
    61.   }
    62.  
    63.   public function next(){
    64.     return next($this->_collection);
    65.   }
    66.  
    67.   public function rewind(){
    68.     return reset($this->_collection);
    69.   }
    70.  
    71.   public function valid(){
    72.     return $this->current() !== false;
    73.   }
    74.  
    75.   public function append($pItem){
    76.     $this->_collection[] = $pItem;
    77.   }
    78.  
    79.   public function _toArray($pCollection){
    80.     $collection = array();
    81.     foreach ($pCollection as $item) {
    82.       if ($item instanceof FP_Model || $item instanceof FP_Model_Collection)
    83.         $collection[] = $item->toArray();
    84.       else
    85.         $collection[] = $item;      
    86.     }
    87.     return $collection;
    88.    
    89.   }
    90.  
    91.   public function toArray(){
    92.     return $this->_toArray($this->_collection);
    93.   }
    94. }
    95. ?>
    96.  

    This class exists solely for the purpose of acting as a collection of models. It is designed in a way that allows for lazy loading of classes. As you can see, the collection class implements some interfaces that allow it to be iterated through so we can use a standard foreach loop to step through the collection. The constructor of this class can take an array of models that have already been instantiated or an array of associative arrays describing a particular object. For example if we have a model representing a user that looks something like this:

    1.  
    2. <?php
    3.  
    4. class User extends FP_Model {
    5.   protected $_data = array(
    6.                                      ‘id’ => null,
    7.                                      ‘username’ => null,
    8.                                      ‘password’ => null
    9.                                     );
    10. }
    11.  
    12. ?>
    13.  

    We would be able to loop through a user collection like any other:

    1.  
    2.  
    3. $users = $usergateway->fetch();
    4. foreach ($users as $user){
    5.   //do something
    6. }
    7.  
    8.  

    You may have noticed the $_model_class property which contains the name of a class (specifically a model class). This is how the collection knows which class to use to instantiate models lazily as it steps through its internal array and finds descriptive arrays. When we define our Users collection class:

    1.  
    2. <?php
    3.  
    4. class Users extends FP_Model_Collection {
    5.    protected $_model_class = ‘User’;
    6. }
    7.  
    8. ?>
    9.  

    We override that property with the name of our own model class allowing the magic to happen internally. Some people may prefer to not do it that way. Another alternative is to always provide a common interface on the gateway to return the proper type of object when passed the descriptive array. In that situation, the current() function would instead look something like this:

    1.  
    2.  
    3.   public function current(){
    4.     if ($this->_collection instanceof Iterator)
    5.       $key = $this->_collection->key();
    6.     else
    7.       $key = key($this->_collection);
    8.  
    9.     if ($key === null)
    10.       return false;
    11.  
    12.     $item = $this->_collection[$key];
    13.  
    14.     if (!is_object($item)){
    15.       $item = $this->_gateway->create($item);
    16.       $this->_collection[$key] = $item;
    17.     }
    18.  
    19.     return $item;
    20.   }
    21.  
    22.  

    This method is probably better but either way works. So by now you are probably wondering what the hell is a gateway? Well as I said before it is the object that talks to the persistance layer (ie database, webservice, file, etc). The gateway has intimate knowledge about how to create models, how to manipulate them, and how to persist them. They can be thought of almost as drivers for web applications. It is ideal to have these adhere to an interface for a number of reasons which I will go into. So my interface looks like this:

    1.  
    2. <?php
    3.  
    4. interface FP_Model_Gateway_Interface {
    5.  
    6.   public function fetch($pCriteria=null, $pOffset=null, $pLimit=null);
    7.   public function count($pCriteria=null);
    8. }
    9.  
    10. ?>
    11.  

    Generally the application will talk to the gateway and ask for what it needs. The gateway will instantiate models and collections on demand and return them to the application which can be used for views. Additionally, because fetch() optionally takes an offset and limit, we can utilize the gateway in a paginator with the help of an Zend_Paginator adapter:

    1.  
    2. <?php
    3.  
    4. class FP_Model_Paginator_Adapter implements Zend_Paginator_Adapter_Interface {
    5.  
    6.   protected $_criteria = null;
    7.   protected $_gateway = null;
    8.   protected $_count = null;
    9.  
    10.   public function __construct($pCriteria, FP_Model_Gateway_Interface $pGateway){
    11.     $this->_criteria = $pCriteria;
    12.     $this->_gateway = $pGateway;
    13.   }
    14.  
    15.   public function getItems($pOffset, $pLimit){
    16.     return $this->_gateway->fetch($this->_criteria, $pOffset, $pLimit);
    17.   }
    18.  
    19.   public function count(){
    20.     return $this->_gateway->count($this->_criteria);
    21.   }
    22. }
    23.  
    24. ?>
    25.  

    Application logic not dependent on persistence can be processed inside of the models and when the application asks the model object to save itself, the model will in turn ask the gateway to make it happen. As a result we have all implementation specifics housed inside of the gateway allowing for any major backend changes to be implemented by swapping out that component alone. So lets take our User example further by demonstrating what a User gatway might look like:

    1.  
    2. <?php
    3.  
    4. class Doctrine_Users extends FP_Model_Gateway {
    5.  
    6.   public function create(array $pData=array()){
    7.     return new User($pData, $this);
    8.   }
    9.  
    10.   //Update existing user
    11.   public function replace($pId, array $pData){
    12.     $user = $this->fetch($pId);  
    13.     if ($user){
    14.       $user->import($pData);
    15.       return $user;
    16.     } else
    17.       throw new Exception(‘Invalid user id’);
    18.   }
    19.  
    20.   public function save(array $pData){
    21.     if (!is_null($pData[‘id’])){
    22.       $_user = Doctrine_Query::create()
    23.         ->select(‘u.*’)
    24.         ->from(‘Doctrine_Dao_User u’)
    25.         ->where(‘u.id = ?’, $pData[‘id’])
    26.         ->fetchOne();
    27.     } else
    28.       $_user = new Doctrine_Dao_User();
    29.      
    30.     if ($_user){    
    31.       //If password was changed, encrypt it
    32.       if ($_user->password != $pData[‘password’])
    33.         $pData[‘password’] = md5($pData[‘password’]);
    34.  
    35.       $_user->fromArray($pData);
    36.       $_user->save();
    37.     } else
    38.       throw new Exception(‘Invalid user id’);
    39.   }
    40.  
    41.   public function delete($pId){
    42.     return Doctrine_Query::create()
    43.       ->delete(‘Doctrine_Dao_User u’)
    44.       ->where("u.id = $pId")
    45.       ->execute();  
    46.   }
    47.  
    48.   public function count($pCriteria=null){
    49.     if (is_array($pCriteria)){
    50.       $q = Doctrine_Query::create()
    51.         ->select(‘COUNT(u.id)’)
    52.         ->from(‘Doctrine_Dao_User u’);
    53.      
    54.       $first = true;
    55.       foreach ($pCriteria as $key => $val){
    56.         if ($first){
    57.           $q = $q->where("$key = ?", $val);
    58.           $first = false;
    59.         } else
    60.           $q = $q->andWhere("$key = ?", $val);
    61.       }
    62.       return $q->execute(array(), Doctrine::HYDRATE_SINGLE_SCALAR);
    63.     } else {
    64.       $q = Doctrine_Query::create()
    65.         ->select(‘COUNT(u.id)’)
    66.         ->from(‘Doctrine_Dao_User u’);
    67.       return $q->execute(array(), Doctrine::HYDRATE_SINGLE_SCALAR);
    68.     }
    69.   }
    70.  
    71.   public function fetch($pCriteria=null, $pOffset=null, $pLimit=null){
    72.     if (is_numeric($pCriteria)){
    73.       $_user = Doctrine_Query::create()
    74.         ->select(‘u.*’)
    75.         ->from(‘Doctrine_Dao_User u’)
    76.         ->where("u.id = $pCriteria")
    77.         ->fetchOne();
    78.       if ($_user)
    79.         return new User($_user->toArray(), $this);
    80.     } else if (is_array($pCriteria)){
    81.       $q = Doctrine_Query::create()
    82.         ->select(‘u.*’)
    83.         ->from(‘Doctrine_Dao_User u’);
    84.      
    85.       $first = true;
    86.       foreach ($pCriteria as $key => $val){
    87.         if ($first){
    88.           $q = $q->where("$key = ?", $val);
    89.           $first = false;
    90.         } else
    91.           $q = $q->andWhere("$key = ?", $val);
    92.       }
    93.  
    94.       if ($pOffset)
    95.         $q = $q->offset($pOffset);
    96.  
    97.       if ($pLimit)
    98.         $q = $q->limit($pLimit);
    99.  
    100.       $_users = $q->execute();
    101.       return new Users($_users->toArray(), $this);
    102.     } else {
    103.       $q = Doctrine_Query::create()
    104.         ->select(‘u.*’)
    105.         ->from(‘Doctrine_Dao_User u’)
    106.  
    107.       if ($pOffset)
    108.         $q = $q->offset($pOffset);
    109.  
    110.       if ($pLimit)
    111.         $q = $q->limit($pLimit);
    112.  
    113.       $_users = $q->execute();
    114.       return new Users($_users->toArray(), $this);
    115.     }      
    116.   }
    117. }
    118. ?>
    119.  

    As you can see, the gateway implements CRUD operations intelligently. It takes data in the form of associative arrays and happily serializes the information into databases, files, etc. This particular gateway is able to determine whether an object requires an INSERT operation or an UPDATE operation by inspecting the data for the presence of an ‘id’ attribute. It is here where we implement functionality provided by the Doctrine library. So for those of you who are observant, you may be wondering, where are Doctrine’s table abstraction classes? At some point we have to extend Doctrine_Record in order to manipulate those tables. Those are stored in a subfolder tied to the persistence implementation. For this example the folder structure of the models looks something like this:

    • models/
      • Doctrine/
        • Dao/
          • User.php
        • Users.php
      • User.php
      • Users.php

    Here I have created a folder named ‘Doctrine’ since the implementation uses a database for persistence. This folder houses the gateway classes as well as another folder named ‘Dao’ which houses the Doctrine table models. The Gateway classes rely on the database models in the Dao folder in order to manipulate the database. Here is the doctrine User model

    1.  
    2. <?php
    3.  
    4. class Doctrine_Dao_User extends Doctrine_Record
    5. {
    6.     public function setTableDefinition()
    7.     {
    8.         $this->setTableName(‘users’);
    9.         $this->hasColumn(‘id’, ‘integer’, 4, array(‘type’ => ‘integer’, ‘length’ => 4, ‘primary’ => true, ‘autoincrement’ => true));
    10.         $this->hasColumn(‘username’, ’string’, 255, array(‘type’ => ’string’, ‘length’ => 255, ‘default’ => , ‘notnull’ => true));
    11.         $this->hasColumn(‘password’, ’string’, 255, array(‘type’ => ’string’, ‘length’ => 255, ‘default’ => , ‘notnull’ => true));
    12.     }
    13. }
    14.  

    This class implements all of the functionality needed to manipulate the user table in the database by extending the Doctrine_Record base class. Details on how these are implemented can be found here in the Doctrine guide.

    Alternately, we could also have a ZendDb folder containing the gateway classes that work with Zend_Db instead of doctrine. In this scenario our folders would look like this:

    • models/
      • Doctrine/
        • Dao/
          • User.php
        • Users.php
      • ZendDb/
        • Dao/
          • User.php
        • Users.php
    • User.php
    • Users.php

    And inside the Dao folder for that implementation we would have our Zend_Db table model.

    1.  
    2. <?php
    3.  
    4. class ZendDb_Dao_User extends Zend_Db_Table {
    5.   protected $_name = ‘users’;
    6. }
    7. ?>
    8.  

    With a little imagination we can make switching between implemenations dynamic. For those who read my earlier article on application skeletons, this can be accomplished by extending our application modules with getters for gateway objects. We can implement our module as such:

    1.  
    2. <?php
    3.  
    4. class AppModule implements FP_Application_Module {
    5.  
    6.   /* …..Snipped ….*/
    7.  
    8.  public function getUserGateway(){
    9.    return new Doctrine_Users();
    10.  }
    11.  
    12. }
    13.  
    14. ?>
    15.  

    Controllers and helpers can then grab an instance of this module and retrieve the intended gateway to be used and not have to be concerned about which persistence implementation they should be using. When making the switch we can simply alter our application module to return a different gateway object and be assured it will propogate through the entire application. Now that’s easy.

    So now we have a pair of classes User and Users which are 100% portable to any other application with little or no modification. Chances are the Gateway classes will be portable too most of the time. Whether we are storing them in databases, xml files, or serializing them over a REST call, we can be assured that our class will behave consistently in each scenario. This architecture also lends itself heavily to optimal unit testing which is a boon in itself.

  • Multi-Lingual Support with Zend_Translate and PHPTAL

    Posted on March 23rd, 2009 brandon 4 comments

    I recently had to implement multi lingual support in a Zend Framework application to accomodate 4 languages in a social networking site: English, Spanish, Porteguese, and French Canadian. The site was fairly large comprising of a large number of heavily dynamic templates with a lot of user generated content. At first it seemed daunting and there were some worries that a lot of work needed to be done before the translations would be available. I’ll share some of the trials I went through figuring out how to leverage Zend_Translate and the even more annoying process of getting it conveniently usable with PHPTAL.

    Translation tends to be a bit intimidating for some developers as it has an air of black magic around it. A lot of this can be blamed on a lack of understanding about how translations are handled. A common misconception was that the software behaved like a universal translator of sorts, the kind you would see in Star Trek. As super cool as that is, unfortunately technology available to us mere mortals has not reached that level of sophistication to a point where it’s practical or accurate to do that. Instead we must rely on predetermined translations on controlled static content provided by hired translators or our limited knowledge from our highschool language courses.

    The Zend_Translate module, available with the Zend Framework, gives us a multitude of options for implementing web site translations. It supports adapters for most known translation mechanisms/formats and even some that I had never heard of until I had need for this service (array, csv, ini, gettext, tbx, tmf, qt, etc). The only real difference between these standards is the format in which the translations are stored and as a result how their translation stores are created. Some are binary based, others are code based, while others are xml based. Needless to say some are human readable and others require additional tools to manage so the needs of most developers and their clients should be met by one of these solutions. The details of the individual formats aren’t terribly important for the scope of this article since Zend_Translate provides a normalized interface to all of them. In this article I will demonstrate the use of the time tested gettext adapter.

    Gettext relies on the use of .po and .mo files, .po files being a human readable structured text file mapping the translations, and .mo files being their compiled binary target format. The adapter reads the translations from the .po files. Each .po file contains the translations for a different language and is named in a way that allows the adapter to quickly reference it. I prefer to use locale strings to identify the different languages as the adapter requires no modifications to reference them (ie. en_US.po, es_US.po, pt_BR.po, and fr_CA.po). For languages that utilize non-ascii characters, it’s mandatory that the .mo file containing the translations be in UTF format. These files can be created by hand but you can save a lot of time using a utility called Poedit. This program if properly configured can scan your source code directly and identify strings that require translations and automatically generate your .mo/.po files for you. Depending on what language you code in and how you implement gettext mileage will vary on automating this process. I didn’t have much luck at this since I used a convention that was geared towards PHPTAL and unfortunately Poedit could not identify my translations by default. I am certain with enough tinkering I can find a devise a pattern that the tool can use but time is valuable so perhaps someone else can contribute their suggestions. I had to create my .po files by hand.

    Here is an excerpt from my fr_CA.po:

    msgid “”
    msgstr “”
    “Project-Id-Version: Super Duper Social Networking Site”
    “POT-Creation-Date: ”
    “PO-Revision-Date: 2009-02-26 15:02-0800″
    “Last-Translator: Brandon <______@____.com>”
    “Language-Team: eROI <_____@___.com>”
    “MIME-Version: 1.0″
    “Content-Type: text/plain; charset=utf-8″
    “Content-Transfer-Encoding: 8bit”
    “X-Poedit-Language: French”
    “X-Poedit-Country: CANADA”

    msgid “Favorite this artist”
    msgstr “Cet artiste dans mes Favoris”

    msgid “Join this group”
    msgstr “Joindre ce groupe”

    msgid “Message the group”
    msgstr “Envoyer un message au groupe”

    msgid “Post a bulletin”
    msgstr “Afficher un bulletin”

    msgid “Post a new bulletin”
    msgstr “Afficher un nouveau bulletin”

    Regardless of how this file is created, it must be compiled by Poedit into the .mo file . The binary file is then placed in a folder where the Zend_Translate adapter can find it. My directory structure is as follows:

    • /app
    • /config
    • /lang
      • en_US.po
      • es_US.po
      • fr_CA.po
      • pt_BR.po
    • /lib
    • /log
    • /plugins
    • /public
    • /tmp

    Typically I handle application wide locale in one of my bootstrap subsystem files located in the lib folder. I have one such file called locale.php which looks something like this:

    1.  
    2. <?php
    3.  
    4.   //Call this from any where in app to change locale
    5. function set_application_locale($pLocale, $pSave=true){
    6.   $config = Zend_Registry::get(‘config’);
    7.   $locale_config = $config->locale;
    8.   $locale = new Zend_Locale($pLocale);
    9.   Zend_Locale::setDefault($pLocale);
    10.   Zend_Registry::set(‘locale’, $locale);
    11.  
    12.   //Initialize translater, set to scan lang/ folder for .mo files
    13.   $translater = new Zend_Translate(‘gettext’, BASE_DIR . ‘lang’, $locale_config->default, array(’scan’ => Zend_Translate::LOCALE_FILENAME));
    14.   $translater->setLocale($pLocale);
    15.   Zend_Registry::set(‘Zend_Translate’, $translater);
    16.  
    17.   if ($pSave)
    18.     save_application_locale($pLocale);
    19.  
    20.   return $locale;
    21. }
    22.  
    23. function save_application_locale($pLocale){
    24.   setCookie(‘locale’, $pLocale, time()+60*60*24*30, ‘/’);
    25. }
    26.  
    27. $config = Zend_Registry::get(‘config’);
    28.  
    29. //Turn off user notices for zend 1.7
    30. Zend_Locale::$compatibilityMode = false;
    31.  
    32. $locale_config = $config->locale;
    33.  
    34. //Restore Locale if present in cookie otherwise set default from config.xml
    35. if (isset($_COOKIE[‘locale’]))
    36.   $locale = set_application_locale($_COOKIE[‘locale’], false);
    37. else
    38.   $locale = set_application_locale($locale_config->default);
    39.  
    40. date_default_timezone_set($config->locale->timezone);
    41. Zend_Locale_Format::setOptions(array(‘date_format’ => $config->locale->date));
    42.  
    43. ?>
    44.  

    This file checks for a cookie named ‘locale’ for a locale string or a config xml file for the default value. Once it has this value we are able to set some other locale settings and instantiate our default translater. In the constructor for our Zend_Translate object we specify we would like to use the gettext adapter and some options that assist the adapter in locating the necessary .po files. The second parameter tells it to search for the .po files in the lang folder, and the scan parameter tells it to identify the .po files automatically by filename. By setting the Zend_Registry variable ‘Zend_Translate’, the translate() view helper used later can automatically retrieve the translater and use it. An added bonus here from creating a set_application_locale function is that we can switch languages on the fly from some facility such as a select box or link.

    Next you must get familiar with the translate() view helper which is used in the template to  define placeholders for strings that need to be translated. Using phtml, a demonstration of it’s use would look like this:

    1.  
    2.  
    3. <h1><?php echo $this->translate(‘Go Home’); ?></h1>
    4.  
    5.  

    It’s important to note that the string we pass into this helper is not necessarily the string that will appear in the rendered html. It will be if no translation is available, however what is supposed to happen is the string is nothing more than an identifier that is mapped to a real translated string in the .po file. If you reference the example .mo file above, you will use the strings identified in the msgid rows when calling the helper. Again these can be a verbose human readable string in your (the developer) native language (’Go Home’) or it can be a more cryptic identifier (’header_menu_home_label’). As long as it matches the msgid identifier it doesn’t matter. Each .po file will contain this mapping with a different translation of the string.

    So moving on, we’re not using phtml, we are using PHPTAL. If you are not familiar with how to implement PHPTAL with the Zend_View, refer to my older post on this subject (I am aware the examples are currently broken, I will fix them when time permits). With the stock implementation we can use the tal:content attribute with the php expression syntax like such:

    1.  
    2.  
    3. <h1 tal:content="php:this.translate(’Go Home’)"/>
    4.  
    5.  

    Pretty clunky… there’s got to be a better way. There is fortunately a much more ‘TAL’ way of doing it. PHPTAL allows you to define your own expression prefixes that allow for some neat customization. Wouldn’t you rather do it this way instead:

    1.  
    2.  
    3. <h1 tal:content="translate:Go Hom"/>
    4.  
    5.  

    All we have to do to make this happen is define a TALES function somewhere in the bootstrap that is available in the templates:

    1.  
    2.  
    3. function phptal_tales_translate($pSrc, $pNoThrow){
    4.   $src = trim($pSrc);
    5.   return ‘$ctx->this->translate(’.PHPTAL_TalesInternal::string($src, $pNoThrow).‘)’;
    6. }
    7.  
    8.  

    I define this in a file called PHPTAL_custom.php which is required automatically by my PHPTAL view plugin class.

    Now when we produce our html we can sprinkle this attributes throughout the templates on tags that will require translations such as p tags, h tags, or anything you want. Without the translations ahead of time these modified tags simply serve as placeholders that will show whatever string you pass in by default. When the translations become available, they will be diligently swapped out with the real thing when the locale is set. The only downside to this approach as I mentioned above is that I have not found a pattern that Poedit can use to spot these placeholders but it’s just a minor detail.

    That’s all there is to it.

  • The Demise of Firefox

    Posted on March 20th, 2009 brandon 3 comments

    I stumbled across this article today which in words manifested a gut feeling all of us web developers have been feeling for the last year. The sensational title: ‘Firefox may already be dead’, rings true if the systematic revolt against Internet Explorer is any lesson. To understand why Firefox may be on the death march, one must understand why IE is so reviled by developers and security experts alike. All of the myriad symptomatic technical issues all boil down to one simple problem which is that IE failed to evolve. Firefox rose to popular heights because it satisfied a terrible itch that IE refused to, that of standards compliance, attention to security and intuitive interfaces. History repeats itself again as much like IE was the atomic bomb dropped on netscape, Firefox was the community response to IE. Just as IE did not continue to improve after its sole competition was decimated, the folks at mozilla labs have either become equally complacent or they have forgotten their mission. Firefox, which was once a sleek and slim browser and the poster child for innovation has been seen as slipping in it’s roadmap (3.1 fell to the status of vaporware), and discussion forums are clogged with complaints about memory usage, sluggish performance and more frequent stability issues.

    I’m not just citing the anecdotal evidence of other people as justification for this flaming of everyone’s favorite browser, to be fair, I push the browser very hard personally probably beyond its intended design. Being a web developer, I abuse firefox with a library of plugins which no doubt contribute to the collective unstability of the program. As full disclosure here is a list of extensions i currently have installed:

    • ColorZilla
    • Download Statusbar
    • DownloadHelper
    • Firebug
    • FlashGot
    • Foxytunes
    • GBookmarks
    • Google Gears
    • Html Validator
    • It’s All Text!
    • JSView
    • keyconfig
    • Novell Moonlight
    • S3 Firefox Organizer
    • SDBizo
    • Stop Autoplay
    • Tab Mix Plus
    • Tamper Data
    • Ubiquity
    • Url Link
    • Web Developer

    It’s quite an obscene list, I admit but not all that uncommon among my ilk. My thinking is that if a software proclaims superior extensibility it should assume the worst that extension developers aren’t the brightest knives in the drawer and won’t pay alot of attention to memory management therefore the host program should be diligent in cleaning up after it’s children. My experience has been that by the end of the day, Firefox memory footprint has bloated to 700+ Mb and it has crashed atleast twice (On Linux). Regardless of Mozilla’s justification of the memory usage, I would rather take a performance hit than sacrifice all of my ram.

    When Chromium comes out for linux in a usable form, I’ve already decided to make the switch and I suspect I’m not alone. I read the lengthy comic that google put out  and even if the browser doesn’t live up to the high expectations in real world performance, I am already persuaded by the architecture of the browser and expect that I will not experience most of the issues I experience with Firefox. As long as Chromium or plugin developers provide the same love to web developers there will be little reason to stay loyal to Mozilla. I agree whole-heartedly with the parent article that Firefox will most likely fail because it has too much momentum to stop, not that it’s developers have the desire to alter course. I for one welcome our new Google overlords.

  • Back from the dead

    Posted on March 20th, 2009 brandon No comments

    So late last year I started my other blog over at http://www.unintelligent-design.com, and i’ve been pretty involved in that for some time. As a result, this blog got terribly neglected as I dealt with some poignant albeit non-technical topics such as the presidential elections, some critical pieces on religion and financial crises and whatnot. I apologize to people who have been waiting for new content and I plan on making it up by resuming posting here with renewed vigor. I couldn’t believe that when i came back I discovered this site was still running wordpress 2.5. After some upgrades and a renewed theme which I will attempt to customize, I didn’t have that dirty feeling anymore like I was working on an old beater car that wasn’t worth the upkeep. I realize that some of the code examples were lost as the site got migrated from one server to another and the accompanying code subdomain did not survive the trip. On that note, I would appreciate any recommendations for some good source code viewing plugins for wordpress. The last plugin I used required a lot of effort to render code so I would like something a little more seemless.

    The company I work for (eROI) has gotten developing what are affectionately dubbed “slogs” (site-blogs) down to a science. This is essentially the art of using wordpress as a pseudo cms platform where entire sites are constructed that do not resemble blogs. I have not had the opportunity to construct one of these sites personally so I hope to continue to use this particular site as an excuse to explore wordpress experimentation.

    For those who are eagerly awaiting the arrival of Silk CMS, my Zend Framework powered cms solution I again apologize and I promise that it’s not vaporware. I had hoped to resume development in February but I’ve been busy working on a very large social networking site for a big client which should be going live pretty soon. My plan was to go back and do a codebase refresh using the latest version of the framework as well as incorporate some better coding techniques i’ve picked up along the way. In the mean time I am considering releasing the current version in it’s current unfinished state as version 0.1 and pave the way for the rewrite and dub it 0.5 . All of this really hinges on how time permits obviously.