Вы находитесь на странице: 1из 229

Table Of Contents - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Zend Framework: Surviving The Deep End


Table of Contents 1. Introduction 1.1. The Zend Framework 1.2. About This Book 1.2.1. Obtaining The Source Code For Chapters 1.3. Me, Me, Me! 1.4. You, You, You! 2. The Architecture of Zend Framework Applications 2.1. Introduction 2.2. The Model-View-Controller 2.2.1. The View

http://www.survivethedeepend.com/zendframeworkbook/en/1.0 (1 de 6)28/10/2011 12:51:12

Table Of Contents - Zend Framework Book: Surviving The Deep End

2.2.2. The Controller 2.2.3. The Model 2.3. In Review 2.4. Conclusion 3. The Model 3.1. Introduction 3.2. Clarifying The Model 3.3. In Programming, Fat Models Are Preferable To Size Zero Models 3.4. The Fat Stupid Ugly Controller 3.5. Controllers Are Not The Data Police 3.6. Conclusion 4. Installing The Zend Framework 4.1. Introduction 4.2. Before You Install The Framework 4.3. Getting The Zend Framework 4.3.1. Download As Compressed Archive File 4.3.2. Checkout From Subversion 4.3.3. Download As A Linux Distribution Package 4.3.4. Nightly Build Download 4.4. Post Installation 5. A Not So Simple Hello World Tutorial 5.1. Introduction 5.2. Step 1: Creating A New Local Domain 5.3. Step 2: Creating A Project Directory Structure 5.4. Step 3: Implement Application Bootstrapping 5.5. Step 4: The Only Way In, The Index File 5.6. Step 5: Adding A Default Controller and View 5.7. Conclusion 6. Standardise The Bootstrap Class With Zend_Application

http://www.survivethedeepend.com/zendframeworkbook/en/1.0 (2 de 6)28/10/2011 12:51:12

Table Of Contents - Zend Framework Book: Surviving The Deep End

6.1. Introduction 6.2. Step 1: Editing the ZFExt_Bootstrap Class 6.3. Step 2: Editing The Index and htaccess Files 6.4. Step 3: Adding The Application Configuration File 6.5. Step 4: Handling Setting Of Standard Component Defaults 6.6. Step 5: Fixing ZFExt_Bootstrap 6.7. Step 6: Integrating Application Configuration Into Resource Methods 6.8. Step 7: Optimising Autoloading Code 6.9. Allowing Zend_Loader_Autoload Load Namespaced Classes 6.10. Conclusion 7. Handling Application Errors Gracefully 7.1. Introduction 7.2. The ErrorController and Error View 7.3. Well, That Didn't Work... 7.4. Not All Errors Are Equal 7.5. Conclusion 8. Developing A Blogging Application 8.1. Introduction 8.2. Planning 8.3. Incremental Development and YAGNI 8.4. Checking Our Toolbox 8.5. This Is Not The Reference Guide 9. Implementing The Domain Model: Entries and Authors 9.1. Introduction 9.2. The Domain Model and Database Access Patterns 9.3. Exploring The Domain Objects 9.4. Exploring The Entry Data Mapper 9.5. Assessing Implementation Tools 9.5.1. Domain Objects

http://www.survivethedeepend.com/zendframeworkbook/en/1.0 (3 de 6)28/10/2011 12:51:12

Table Of Contents - Zend Framework Book: Surviving The Deep End

9.5.2. Validation And Filtering Rules 9.5.3. Database Access 9.6. Implementation 9.6.1. Adding Unit Tests For Execution 9.6.2. The Domain Objects 9.6.3. The Data Mappers 9.6.4. Lazy Loading Domain Objects 9.6.5. Preventing Duplicate Entities With An Identity Map 9.7. Conclusion 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 and Yahoo! User Interface Library 10.1. Introduction 10.2. Zend_View: Object Oriented Templating 10.2.1. Layouts 10.2.2. Partials 10.2.3. View Helpers 10.2.4. Placeholders 10.2.5. Short Tags vs Full Tags 10.3. The ZFBlog Application Setup 10.4. Creating An Index Page With HTML 5 10.5. Extracting Static Markup Into A Layout 10.6. Replacing Changeable Elements With Placeholders 10.7. Improving HTML 5 Support With Custom View Helpers 10.8. Adding A Link To A Custom Stylesheet 10.9. Customising The Style 10.10. Conclusion A. Creating A Local Domain Using Apache Virtual Hosts A.1. Introduction A.2. Configuring Apache With Virtual Hosts

http://www.survivethedeepend.com/zendframeworkbook/en/1.0 (4 de 6)28/10/2011 12:51:12

Table Of Contents - Zend Framework Book: Surviving The Deep End

A.3. Configuring Local HOSTS File A.4. Conclusion B. Performance Optimisation For Zend Framework Applications B.1. Introduction B.2. Avoid Premature Optimisation B.3. Measuring Performance B.3.1. Memory and CPU Measurement B.3.2. Requests Per Second B.4. Pinpointing The Cause Of Poor Performance B.4.1. Code Profiling B.4.2. Database Operations Analysis B.5. General PHP Optimisation B.5.1. Opcode Caching B.5.2. Realpath Cache B.6. General Zend Framework Optimisation B.6.1. Class Loading Optimisation B.6.2. Configuring Default Caches B.6.3. Cache At The Right Level B.6.4. Minimising Include Paths B.7. HTTP Server Optimisation B.7.1. Optimising Apache's Configuration B.7.2. Avoiding Apache Completely B.8. Conclusion C. Copyright Information C.1. Copyright C.2. Licensing Next Chapter 1. Introduction

http://www.survivethedeepend.com/zendframeworkbook/en/1.0 (5 de 6)28/10/2011 12:51:12

Table Of Contents - Zend Framework Book: Surviving The Deep End

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0 (6 de 6)28/10/2011 12:51:12

Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Survive The Deep End!


Welcome to Surviving The Deep End, a free book about Zend Framework for the PHP programming language. The book was written to guide readers through the metaphorical "Deep End". It's the place you find yourself in when you complete a few tutorials and scan through the Reference Guide, where you are buried in knowledge up to your neck but without a clue about how to bind it all together effectively into an application. This take on the Zend Framework offers a survival guide, boosting your understanding of the framework and how it all fits together by following the development of a single application from start to finish. I'll even throw in a few bad jokes for free. The book is a work in progress and will soon be available to read online or download and print as PDF. In fact, every individual chapter can be downloaded individually or read online as soon as it's

http://www.survivethedeepend.com/ (1 de 2)28/10/2011 12:51:22

Zend Framework Book: Surviving The Deep End

published. There will be no final version of the book - it's not like Zend will cease releasing new Zend Framework versions tomorrow! There will be a print edition in the near future when I figure out how that should work. Surviving The Deep End is a free book. I rely on guilt, extortion, bad jokes, and teary eyed pleas for assistance to raise donations. That and Google ads on every page. Donations will be used to pay for the server (we're hosted on Slicehost) and the next Macbook Pro I intend purchasing later this year. To keep up to date on the book's progress please visit or subscribe to my blog or follow TheDeepEnd on Twitter.

Enter The Book


Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/ (2 de 2)28/10/2011 12:51:22

Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Comment Help
Zend Framework: Surviving The Deep End was designed for online community review. This means I extort the public into correcting my mistakes so I don't need to go hire lots of proof readers and technical reviewers. Clever tactic, eh? The comment system is built into the book as an inline system, i.e. you can write comments specific to any paragraph of the entire text by popping open a comment form using the provided links after each paragraph (you can also read existing comments the same way!). The link text itself shows the current number of comment for the preceding paragraph or code block.

http://www.survivethedeepend.com/index/comments (1 de 2)28/10/2011 12:51:28

Zend Framework Book: Surviving The Deep End

General comments will be possible soon, but for now comment to my blog for those. The general end-of-page comments will be implemented soon. The entire system uses Zend Framework in the backend, with the frontend powered by jQuery. In case you want to credit me for its creation, I should note I stole the idea lock, stock and barrel from two other online books: The Django Book and Real World Haskell. They use similar systems so I merely built the idea along those lines to fit my needs. Kudos to the kids who did this first! Comments are linked to the specific id of each paragraph which is generated in the Docbook sources I edit and forever immortalised in the XML so that subsequent editing does not alter the id of a paragraph once its set. The id is basically the MD5 hash of the original paragraph text when it was first added. I hope the system proves useful in adding more atomic comments so everyone can pick apart the whole text and force me to improve on it. Feel free to comment with a vengeance!

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/index/comments (2 de 2)28/10/2011 12:51:28

Appendix C. Copyright Information - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Appendix C. Copyright Information


Table of Contents C.1. Copyright C.2. Licensing

C.1. Copyright
Copyright 2008-2009 Pdraic Brady. All rights reserved.

C.2. Licensing
This book, "Zend Framework: Surviving The Deep End", by Pdraic Brady is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/3.0/ or send a letter

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/apc (1 de 3)28/10/2011 12:51:36

Appendix C. Copyright Information - Zend Framework Book: Surviving The Deep End

to Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. No action outside the scope of this license is permitted without the author's consent. All source code made available in this book, by external download, or as amended in separately published errata, is licensed under a New BSD License as follows.

Copyright (c) 2008, Pdraic Brady All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Pdraic Brady nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/apc (2 de 3)28/10/2011 12:51:36

Appendix C. Copyright Information - Zend Framework Book: Surviving The Deep End

INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Prev Appendix B. Performance Optimisation For Zend Framework Applications Home

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/apc (3 de 3)28/10/2011 12:51:36

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Appendix B. Performance Optimisation For Zend Framework Applications


Table of Contents B.1. Introduction B.2. Avoid Premature Optimisation B.3. Measuring Performance B.3.1. Memory and CPU Measurement B.3.2. Requests Per Second B.4. Pinpointing The Cause Of Poor Performance B.4.1. Code Profiling B.4.2. Database Operations Analysis B.5. General PHP Optimisation B.5.1. Opcode Caching
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (1 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

B.5.2. Realpath Cache B.6. General Zend Framework Optimisation B.6.1. Class Loading Optimisation B.6.2. Configuring Default Caches B.6.3. Cache At The Right Level B.6.4. Minimising Include Paths B.7. HTTP Server Optimisation B.7.1. Optimising Apache's Configuration B.7.2. Avoiding Apache Completely B.8. Conclusion

B.1. Introduction
Performance Optimisation is not a matter of replacing all your double quotes with single quotes and praying a few saved nanoseconds will prevent Apache from biting into swap space, and setting fire to your server, when you're listed on Digg. Rather it's a deliberate process of setting performance goals, benchmarking to measure current performance, identifying inefficiencies and bottlenecks, purchasing appropriate server hardware and being agressive in trying new tactics that never occured to you. In other words, it's a lot of fun! In this appendix, I discuss performance optimisation in terms of the various approaches and tactics commonly used in the real world - where we don't really worry about stuff like double quotes (unless it has a perceptible effect). Most of these should become familiar friends if they are not already. Since detailing performance options is a large task, this is a large Appendix but hopefully it will come in handy. I am not going to limit this appendix to just the Zend Framework and a lot of this appendix applies to any PHP application.

B.2. Avoid Premature Optimisation


Before we do anything, let's be very clear that you shouldn't sacrifice a sound application design on the altar of premature optimisation. Premature optimisation is, as Sr. Tony Hoare originally put it, the

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (2 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

root of all evil. Let's not take it out of context however! To forever put off optimisation, is to never obtain an efficient application. Eventually, the temptation must become overwhelming but timing is everything. Optimising too early can lead to a restrictive design that only causes more problems, so it's worth waiting until the application is in a state fit to be optimised. Optimisation is a game of eliminating measurable inefficiencies. The keyword is "measurable". If you can't reliably measure or predict the impact of an optimisation, then how do you know it's a worthwhile optimisation? A silly replacement of a few double quotes with single quotes taking one developer hour to complete might gain you a completely unnoticeable performance boost. You just wasted an hour and gained little or nothing worth bragging about! Meanwhile there's a slow SQL statement which, with caching applied from a few minutes work using Zend_Cache, gains you a whole second. Obviously the second option is far more worthy of attention. Focus your efforts on optimisations that achieve the greatest benefit. There are three approaches to optimisation: following your blind beliefs, following your experienced intuition, and employing software tools to analyse applications for optimisation opportunities. The third is the only real valid option. The others are uncertain and you risk leaving valuable optimisations undiscovered, or wasting your time on pointless ones. That doesn't invalidate the first two entirely but they are often the category of optimisations any reasonable programmer should apply throughout development - not areas that should need hours of attention after the fact. An experienced programmer will rarely create a hideously inefficient application littered with obvious source code failings. Some element of optimised performance will already be present in the existing design so what remains is to push things further in a deliberate considered fashion. A lot of optimisations are common sense, since once you understand the problem there are only so many solutions to choose from, and most of those will have been blogged by half the planet.

B.3. Measuring Performance


Optimisation is only working if you can create significant measurable results. Significance is relative of course, one programmer's saved millisecond can be another's shaved off second. As you optimise the most obvious performance issues, lesser issues will begin coming to the fore until you reach a

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (3 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

state where optimising further carries a higher cost than the end benefit or the alternative of simply throwing in more hardware. In any case, you should have a target performance in mind and to reach that target requires measuring current performance to set a baseline, and future performance to measure if you've accomplished any worthwhile improvements over that baseline. Performance measurement is generally concerned with a few metrics. Memory and CPU usage are the obvious ones, especially on a single server where resources are limited. But even in an environment where you are scaling your hardware either vertically or horizontally, maximising performance can save you from needlessly splashing out on expensive servers to cover up the holes in sub-optimal code. Another common measure alongside these is requests per second, a measurement of how many requests some part of your application is capable of serving per second given the current hardware it's hosted on.

B.3.1. Memory and CPU Measurement


Measuring memory consumption can be done at both the server and PHP levels. PHP supports two useful functions which measure how much memory PHP is consuming. memory_get_usage() and memory_get_peak_usage() can be called to determine how many bytes are consumed either at a specific point in the PHP workflow, or at it's peak. These are typically used to get some rough estimates of the memory profile of an application - determining where in the application memory is peaking beyond an acceptable tolerance level. Determining that tolerance level is no easy task since some features naturally need more memory than others. There's a reason your php.ini file has fairly high memory limits! One useful tactic is a nod to the futility of attempting manual measurement. Instead of clicking every possible link and submitting every possible form by hand, simply set up one or more points in the source code where measurement can take place. Whether you do this using software tools to automatically attack all routes (functional testing might be a place to start if employed), or by slapping a beta sticker on a production site and letting it run live for a short period is up to you. One possible rough solution is to write a small Controller Plugin which implements the Zend_Controller_Plugin_Abstract::dispatchLoopShutdown() method.

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (4 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q

<?php class ZFExt_Controller_Plugin_MemoryPeakUsageLog extends Zend_Controller_Plugin_Abstract { protected $_log = null; public function __construct(Zend_Log $log) { $this->_log = $log; } public function dispatchLoopShutdown() { $peakUsage = memory_get_peak_usage(true); $url = $this->getRequest()->getRequestUri(); $this->_log->info($peakUsage . ' bytes ' . $url); } }

You can register this from your Bootstrap when you want to use it.
q q q q q q q q q q q q q

<?php class ZFExt_Bootstrap { // ... public function enableMemoryUsageLogging() { $writer = new Zend_Log_Writer_Stream( self::$root . '/logs/memory_usage'); $log = new Zend_Log($writer); $plugin = new ZFExt_Controller_Plugin_MemoryPeakUsageLog ($log); /** * Use a high stack index to delay execution until other * plugins are finished, and their memory can also be accounted * for. */ self::$frontController->registerPlugin($plugin, 100); } // ... }

q q q

q q q q q q q q

This is a fairly basic example, and you could easily adapt something similar to log additional information about the original request, so you have enough data to reperform the request in a more controlled environment to pinpoint the reason for any memory sinks. Here's some output from the log setup above.
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (5 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

2009-01-09T15:41:46+00:00 INFO (6): 4102728 bytes / 2009-01-09T15:42:57+00:00 INFO (6): 4103608 bytes /index/ comments
If you prefer to watch memory usage on the fly, you can use a different PHP stream for the Writer strategy or even use the Firebug Writer to watch the live results when using Firefox with FirePHP. Once you have a rough memory profile, you might then use a systematic approach like Code Profiling to gather detail on the root causes of any memory usage. Code Profiling is also a good way to see where execution time is being spent. Apart from solutions built into the application, you can also monitor memory and CPU usage on the server an application is running on using a variety of tools. Linux systems offer top, free, vmstat and a variety of others. A personal favourite is htop which displays a continually update summary of memory, CPU and per-process data with a few interactive features for ordering processes by various statistics. These are of particular use in tuning servers to cater to your application, but obviously they also can assist in seeing how your application performs on a pre-optimised test server during load testing how much memory are the Apache processes consuming, can I run more or fewer Apache clients, which application areas are maxing out the CPU rather than RAM?

B.3.2. Requests Per Second


Load testing is another useful performance measurement tool. It is influenced to a significant degree by the hardware tests are performed on, so to maintain comparable results over time you'll need a test system whose specifications and general load profile remain constant. Its purpose is to measure how many requests your application (a specific URL, or set of URLs) can complete per second on average. The time taken is of lesser concern (changes with the hardware) so it's the relative drift in the measure over time that indicates whether your application is becoming more or less performant. You'll notice that most framework benchmarks tend to obsess over this value.

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (6 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

The approach, simply enough, is to simulate the effect of being hit by a specific number of requests spread across a given number of concurrent users. Sometimes the base element is a period of time instead of a fixed number of requests. The total number of requests served is then divided by the time in seconds taken to complete this number of requests. It's extremely effective in noting how application performance changes as you optimise the application and/or the server it's hosted on. Two commonly used tools for this purpose are ApacheBench (ab) and Siege. The ApacheBench tool is normally installed along with the HTTP server binaries. If it's missing, sometimes you might need to install an Apache Utils package. Under Ubuntu it's usually located at / usr/sbin/ab which may not be listed in Ubuntu's PATH by default, which means you should either add /usr/sbin to your current user's PATH or call it using its absolute path. Here's the ApacheBench command to run 10,000 requests across 100 simulated concurrent users (the ending slash is important in naked URIs):

ab -n 10000 -c 100 http://www.survivethedeepend.com/


The resulting output will look similar to this:

Server Software: Server Hostname: Server Port: Document Path: Document Length: Concurrency Level: Time taken for tests: Complete requests: Failed requests: Write errors: Total transferred: HTML transferred: Requests per second: Time per request: Time per request: concurrent requests) Transfer rate:

apache2 www.survivethedeepend.com 80 / 9929 bytes 100 341.355 seconds 10000 0 0 101160000 bytes 99290000 bytes 29.29 [#/sec] (mean) 3413.555 [ms] (mean) 34.136 [ms] (mean, across all 289.40 [Kbytes/sec] received

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (7 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

Connection Times (ms) min mean[+/-sd] Connect: 0 0 1.7 Processing: 124 3409 831.1 Waiting: 120 3267 818.5 Total: 124 3409 831.1

median max 0 52 3312 10745 3168 10745 3312 10745

Percentage of the requests served within a certain time (ms) 50% 3312 66% 3592 75% 3784 80% 3924 90% 4344 95% 4828 98% 5576 99% 6284 100% 10745 (longest request)
Here's a similar example for Siege where you set total requests by setting a number of concurrent users with a repeat factor for each one. So 100 concurrent users repeating requests 10 times gives you 1,000 total requests. The additional option sets a delay of 1 second between concurrent users which is good enough for performance testing.

siege -c 100 -r 10 -d 1 http://www.survivethedeepend.com


The results are a bit shorter but still show the requests per second as "Transaction rate":

Transactions: Availability: Elapsed time: Data transferred: Response time: Transaction rate: Throughput: Concurrency: Successful transactions: Failed transactions: Longest transaction: Shortest transaction:

1000 100.00 43.77 3.06 3.20 22.85 0.07 73.08 1000 0 8.78 0.05

hits % secs MB secs trans/sec MB/sec

Both load testing options come with a variety of other options worth investigating. When you're

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (8 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

looking at the server come deployment time, mixing similar tests with the monitoring of your server's resources like memory and CPU can be pretty informative and useful when optimising Apache's configuration. And yes, the performance of the book website when I ran those load tests was abysmal! If I was caching the pages properly at the time the request times would have significantly higher. It should be noted that load testing takes both software and hardware into account and therefore it's a useful tool for server optimisation in addition to application performance measurement.

B.4. Pinpointing The Cause Of Poor Performance


Rather than randomly picking pieces of source code to optimise and keeping your fingers crossed for good luck, here are a few methods of actually identifying specific performance problems.

B.4.1. Code Profiling


Looking at the bigger picture by performing load testing and resource monitoring will get you some rudimentary data on which parts of the application perform worse or better than average. However, if you find a performance issue, the only definitive way you can locate the root cause is to break down that part of the application's performance across the entire source code execution stack. In a simple application, reading the source might work but a complex application could be spinning through database operations, file processing, or countless other possible tasks which effect performance. Bearing in mind your application may also be relying on third party libraries, reading the source isn't as easy as it sounds. Code Profiling or Method Timing is a means of breaking down a single request so you can examine the execution time and memory usage of every function or method call performed for that request. It's invaluable in diagnosing performance problems and identifying targets for optimisation. One common PHP profiler is the Xdebug extension maintained by Derick Rethans. Xdebug provides features for debugging, profiling and code coverage analysis. The profiling feature generates cachegrind compatible files which can be analysed into readable data by any cachegrind compatible application such as KCacheGrind (KDE), WinCacheGrind (Win32) or the web application, WebGrind.
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (9 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

Depending on the cachegrind application you choose, it will provide a few methods of seeing how much time was spent within every function, method and keyword statement. Obviously those with the greatest time or memory cost bear the closest examination to see if they can be optimised to gain worthwhile improvements. The current version is available from PECL, and you can install it with a simple:

pecl install xdebug


The command will download and compile Xdebug. If you are on Windows, precompiled binaries are available for download from the Xdebug website's download page. To finish installation we can configure Xdebug for profiling by adding the following to your php.ini file, and taking care to replace or comment out any previous line using the zend_extension or zend_extension_ts configuration option. Here's an example configuration added to a Ubuntu system at /etc/php/conf.d/xdebug.ini (Ubuntu allows for extension specific ini files outside of /etc/php5/ apache2/php.ini).

; Xdebug zend_extension = /usr/lib/php5/20060613/xdebug.so ;xdebug.profiler_enable = 1 xdebug.profiler_output_dir = "/tmp" xdebug.profiler_output_name = "cachegrind.out.%t.%p"


Whenever you want to start profiling, remove the leading semi-colon from the xdebug.profiler_enable configuration line. Put it back when you're finished unless you like having gigantic cachegrind files taking over your hard disk. Here's some example output showing the percentage of execution time per function/method from Webgrind on a Windows system.

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (10 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

Cachgrind analysis output from Webgrind

Installing Webgrind just requires copying the download to your document root for Apache and editing the config.php file to remove the $storageDir and $storageProfiler options (the above xdebug ini configuration is enough to let Webgrind detect these automatically). Using the above configuration with xdebug.profiler_output_name should be sufficient to let to find any cachegrind output from Xdebug. By employing code profiling, and generating cachegrind files for a variety of application URIs, we can now peer into the source code to find out who's being naughty and spending the most time to execute. As you'll notice from above, Zend_Loader seems to be having a ball making up almost 25% of the index page's execution time. Obviously, 25% is significant enough to warrant attention!

B.4.2. Database Operations Analysis


Xdebug will break down code execution but there are additional alternatives or complementary practices to assist in locating optimisation opportunities. One of the most obvious targets is the database. Connecting to a database carries a few issues. First and foremost, database operations are innately expensive. Your application will need to open a connection to the database server, send the query, wait for the database to process the query, read back the result, do some more exciting tasks, and really...it's just messy! In any web application, the database will be a major bottleneck tying up a lot of your execution time. Optimisation benefits can be gained from examining the method of utilising the database which in the
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (11 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

case of Zend Framework will include Zend_Db_Table and your Models, along with the complexity and processing needs of SQL statements and the size of result sets being retrieved from the database. In almost all three cases, where these are obvious and measured optimisation targets, caching is one strategy likely to have a benefit where the retrieved data changes infrequently and where the data itself is a large result set or is the result of a known slow query. Why continually bombard the database for data which only changes every few hours, days or even weeks? Cache it! Caching in memory using APC or memcached would obviously be preferably, but a file cache will do if memory resources are tight. Set an appropriate expiry period or manually clear the cached data whenever it changes. Zend_Cache makes organising this a walk in the park. Here's an example using a simple Comments Model where the cache configuration is managed from the main config.ini file of the application.

[general] ; SQL Query Cache cache.sql.frontend.name=Core cache.sql.frontend.options.lifetime=7200 cache.sql.frontend.options.automatic_serialization=true cache.sql.backend.name=File cache.sql.backend.options.cache_dir=/cache/sql


q q q q q q q q q q q q q

<?php class Comments extends Zend_Db_Table { protected $_name = 'comments'; protected $_cache = null; public function init() { /** * config.ini general sections stored into Zend_Registry * Ideally the static calls would be replaced with dependency * injection */ $options = Zend_Registry::get('configuration')->cache>sql; $options->backend->options->cache_dir = ZFExt_Bootstrap::$root . $options->backend->options>cache_dir;
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (12 de 30)28/10/2011 12:51:47

q q q

q q

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q

$this->_cache = Zend_Cache::factory( $options->frontend->name, $options->backend->name, $options->frontend->options->toArray(), $options->backend->options->toArray() ); } public function getComments($id) { // pull data from cache, or else hit database and cache results if (!$result = $this->_cache->load('ENTRY_COMMENTS_' . $id)) { $select = $this->select() ->where('entry_id = ?',$id) ->where('status = ?','approved') ->where('type = ?','comment') ->order('date ASC'); $result = $this->fetchAll($select); $this->_cache->save($result, 'ENTRY_COMMENTS_' . $id); } return $result; } public function insert($data) { // invalidate existing cache when saving new comments $this->_cache->remove('ENTRY_COMMENTS_' . $data ['entry_id']); return parent::insert($data); } }

q q q q q q q

q q q q q q q

q q q q

Where data changes all the time, a common situation where content is dynamic, user generated or based on user preferences, caching will have little benefit. The cache may become invalid even before Zend_Cache finishes writing it! In this scenario you will need to examine whether replacing any use of Zend_Db_Table with more direct SQL, or whether redesigning the SQL itself to increase its performance, has a perceptible effect. Where this conspires with a large result set, be careful that the only data being retrieved is required data. There's no point requesting additional fields if they are never used since all they'll do is consume memory needlessly and contribute to starving your server of precious RAM. One area you can pay particular attention to, as an example, is the MySQL Slow Query Log. This feature let's you define the number of execution seconds, using the configuration option long_query_time, above which a query can be defined as being slow. All slow queries can then be logged for further examination. Since slow queries are measured in real time, it's worth noting that
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (13 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

load testing an application will increase the detection rate as CPU and memory resources are strained. Use a lower long_query_time value if you are relying on a log generated on a development machine. The Zend Framework offers its own logging solution for all SQL queries to profile what is run, and how long it takes to execute, using Zend_Db_Profiler. The profiler can be enabled by passing an option called "profiler" with a boolean TRUE value with the options when constructing a database adapter. Thereafter you can instantiate the Zend_Db_Profiler class and use a variety of methods to examine how many queries were profiled, how long they took in total to execute, and retrieve an array of query profiles on which to perform a more in-depth analysis using the accompanying filters. There's even a special Zend_Db_Profiler_Firebug class so you can view profiling data on the fly through the Firebug console in Firefox.

B.5. General PHP Optimisation


PHP has a number of well known, and not so well known, optimisations that are now regarded as standard practice. A few of these have become more prominent during the lifetime of PHP 5.2.

B.5.1. Opcode Caching


Using an opcode cache, like the Alternative PHP Cache (APC) extension, can have a significant impact on the performance of any PHP application by caching the intermediate code that results from parsing PHP source code. Skipping this level of processing and reusing the optimised cache in subsequent requests saves on memory and improves execution times. It should be standard practice to get this installed unless you are stuck on a limited shared host. Installing APC is a relatively simple process using pecl. Under Linux, use:

pecl install apc


Windows users can download a precompiled DLL from http://pecl4win.php.net. The final step is adding the APC configuration to your php.ini file, or by creating a new apc.ini file for

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (14 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

APC under the typical Ubuntu setup in /etc/php5/conf.d/apc.ini.

;APC extension=apc.so apc.shm_size = 50


The configuration you use can have additional effects on performance and I suggest reading the documentation at http://php.net/manual/en/apc.configuration.php. Pay particular attention to apc. shm_size, apc.slam_defense (or apc.write_lock), apc.stat and apc.include_once_override. You should ensure apc.shm_size is, at a minimum, set to a sufficient level to cache all the classes used by your application (and whatever else you're hosting!). Also bear in mind anything you're putting in there via Zend_Cache or the apc functions.

B.5.2. Realpath Cache


One of the more recent innovations entered into the php.ini file for configuration is a realpath cache that debuted with PHP 5.2.0. This was designed to cache the realpath values of relative paths used in include_once and require_once statements. In the bad old days, every time you called require_once with a relative path sparked a set of file operations to find the file being referred to. Now it's done once, and cached using the configured TTL for future lookups. The relevant settings are realpath_cache_size and realpath_cache_ttl. The first sets the size of the cache and defaults to 16K. Obviously this is not a huge number and should be increased somewhat to cater to applications loading up lots of files. The TTL value depends on how often files location are changing. If rarely, consider increasing this value from the default of 120 seconds. I've pushed this up a lot more than a few minutes without ill effect, but I'd be wary of it when moving lots of files around.

B.6. General Zend Framework Optimisation


We've covered a lot of ground so far in general terms. The Zend Framework, as a target for optimisation, does have room for improvement. Being a backwards compatible framework, some optimisations are omitted in the current version to maintain backwards compatibility and support a minimum PHP version.

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (15 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

That doesn't mean we should walk away and do nothing!

B.6.1. Class Loading Optimisation


Due to the way the Zend Framework is structured, the source code is littered with countless require_once statements. While this might not seem like a huge deal, given the size of the framework it usually results in having lots of unnecessary files being loaded up, parsed and readied for action but they are never utilised. Streamlining the number of classes being loaded may offer a modest benefit. This is achieved by making use of PHP's autoload capability which dynamically loads classes on demand (basically it's somewhat akin to lazy loading). Skipping the unnecessary classes from being included reduces PHP's workload. With the Zend Framework, there is a slight complication in that the most commonly used autoload feature is provided by Zend_Loader. Zend_Loader has its own mysterious hidden agenda which requires that it performs independent file checks and other operations on all autoloads. These are, outside of any marginal edge cases, completely extraneous and pointless. As of Zend Framework 1.8.0, Zend_Loader::autoload() has become a deprecated feature meaning you should not use it in new projects and seriously consider replacing it with the new Zend_loader_Autoloader solution before the release of Zend Framework 2.0. Using Zend_Loader is useful in some sense, but since the Zend Framework follows the infinitely predictable PEAR convention, the following works just as well and skips Zend_Loader's more expensive sanity operations.
q q q q

function __autoload($path) { include str_replace('_','/',$path) . '.php'; return $path; }

You could also create this as a static method for your Bootstrap class.
q q q q

class ZFExt_Bootstrap { // ...


http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (16 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q

public static function autoload($path) { include str_replace('_','/',$path) . '.php'; return $path; } // ... }

A four line function versus a class. Who won between David and Goliath again? Notably, some third party libraries will need special treatment if not following the PEAR convention. With Zend Framework 1.8 and later, there is a new solution in Zend_Loader_Autoloader whose features are worth adopting even if it appears more complex than the simple autoload replacement I just described. Despite the new class' complexity, you can still have your performance cake and eat by applying a simple change to how Zend_Loader_Autoload::autoload() operates. This is possible since the new class allows developers to add replace the default autoloader, which by default is Zend_Loader::loadClass(), with a lighter version to avoid all the often unnecessary file checks. You should note that if you are using Zend_Application, this must be done prior to bootstrapping which means in index.php.
q q

$autoloader = Zend_Loader_Autoloader::getInstance(); $autoloader->setDefaultAutoloader(array('ZFExt_Bootstrap', 'autoload'));

Using autoloading will not prevent the impact of all those require_once statements in the Zend Framework source code by itself. To maximise the benefit of autoloading, it's not a bad idea to simply delete the require_once references from all the class files. You can accomplish this in a Phing task, or via the command line, or any old PHP script you can run on a new Zend Framework library copy. From the command line you can comment out those references using:

cd path/to/ZendFramework/library find . -name '*.php' -not -wholename '*/Loader/ Autoloader.php' -print0 | \ xargs -0 sed --regexp-extended --in-place 's/ (require_once)/\/\/ \1/g'

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (17 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

B.6.2. Configuring Default Caches


Within the framework there are a few classes which indulge in expensive operations. For example Zend_Db_Table runs a DESCRIBE TABLE query in the background everytime you create a new instance of its type. Considering you could be using at least a few of these in every request, these additional queries mount up. Another example is Zend_Translate which will continually reparse translation sources when utilised. Luckily both of these components allow you to define a cache, so to avoid feeling very foolish in the future be sure to set them up. For Zend_Db_Table, you can set a default cache by passing an instance of Zend_Cache to Zend_Db_Table_Abstract::setDefaultMetadataCache(). Zend_Translate offers the same feature using Zend_Translate::setCache(), as does Zend_Locale using Zend_Locale::setCache(). These can be configured and entered into your bootstrap so they are available for all requests.

B.6.3. Cache At The Right Level


In performance optimisation, caching is a big deal since it can mean deliver substantial performance gains by caching the results of expensive operations for a period of time. However, knowing when to cache also raises the question of where to cache. Take the example of a dymanic page where the dynamic elements (perhaps an hourly updated list of the most recent news) change infrequently. The most obvious target is to cache the dynamic data for the most recent news (within a Model or View Helper perhaps). Since it's hourly, the cache TTL will be around 3,600 seconds. Now, the dynamic elements are only updated from new data on the hour. However, to utilise this cache we need to call the application to trigger the View generation which utilises the cache - every request still hits the application as normal. Is this really necessary? If the only dynamic elements on the page update hourly, then why not cache the entire page for the full hour? You can perform page caching from your Bootstrap using Zend_Cache.
q

<?php

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (18 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

class ZFExt_Bootstrap { // ... public function run() { $this->setupEnvironment(); /** * Implement Page Caching At Bootstrap Level */ $this->usePageCache(); $this->prepare(); $response = self::$frontController->dispatch(); $this->sendResponse($response); } public function usePageCache() { $frontendOptions = array( 'lifetime' => 3600, 'default_options' => array( // disable default caching for all requests 'cache' => false ), // cache routes to Index and News controllers 'regexps' => array( '^/$' => array('cache' => true), '^/news/' => array('cache' => true) ) ); $cache = Zend_Cache::factory( 'Page', 'Apc', $frontendOptions ); // serve cached page (if it exists) and exit $cache->start(); } // ... }

But is even this level of caching sufficient? We still need to call PHP for the Bootstrap and perform at least a little work, and it likely uses an Apache process as a result. In fact, the fastest caching strategy is to cache these dynamic pages for up to an hour as static HTML files which don't need PHP and which, in a reverse proxy setup, can bypass Apache altogether and be served by a lighter alternative like lighttpd or nginx. Of course, since requests would never touches the application or the bootstrap - how to expire that cache gets a bit complex! With or without physical static caching, a complementary practice would be to delegate caching to the
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (19 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

client through the use of Etag and Last-Modifed headers. While helpful when you have a lot of repeat visitors, it's not as powerful as static HTML caching when unique or infrequent regular visitors are the norm. This discussion illustrates that as we deploy caching across an application, it's important to identify where caching can be located to offer the greatest benefit. Remember that caching is all about avoiding unnecessary processing and memory usage - the closer caching moves to the outside layers of an application, the more processing it's likely to avoid.

B.6.4. Minimising Include Paths


In a typical Zend Framework application you can expect to find classes stuck all over place. It's not unusual to be pulling classes from so many locations that your final include_path looks a mess. This impacts performance since any time you include a class (whether directly or via an autoload function) using a relative path, PHP needs to hunt down a matching file by iterating over every include_path registered. Since this is undesirable there are two simple rules to apply. First, minimise your include paths. Where possible install libraries and even the Zend Framework into shared directories. Since many PHP5 libraries have long since adopted the PEAR Convention, this won't pose much difficulty. The exception will be pulling down library updates using, for example, svn: externals under Subversion, when the external library has files in it's parent location. An example of one such exception is HTMLPurifier which maintains files parallel with the HTMLPurifier directory. But beyond these limited exceptions, keep libraries in one shared location where possible. Then instead of having ten million include path entries, you can keep a more efficient list of 2-4 locations at a maximum. The second rule is to ensure that the most frequently required paths are at the front of the include path list. This ensures they are found quickly and iterating across the other possible locations is limited to the more rarely used classes.

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (20 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

B.7. HTTP Server Optimisation


No matter how aggressively you optimise your application, eventually your server will experience a limit as to how much traffic it can handle. One of the most important limiting factors on a server is the amount of memory it has available. As traffic to your application increases, more and more memory will be consumed by Apache processes and MySQL until finally the server is left with no alternative but to start using disk based swap space. Swap space is incredibly slow (indeed all disk operations are snails compared to utilising RAM) and avoiding it as much as possible is desirable. Having Apache processes relying on swap space or worse, having the Apache parent process spawn children from swap, can bring your application stumbling to a crawl which will not result in a particularly great user experience. This is especially true of Ajax applications where a responsive UI is extremely important. The most obvious solution to these server woes is simply to throw more hardware at the problem, a process called scaling. You could increase RAM or obtain a higher spec replacement server (vertical scaling) or employ multiple servers to handle the load (horizontal scaling). However, before you prematurely scale in either direction its still important to make the most efficient use of your available server resources to minimise expenditure on new hardware. Yes, optimisation does save you money as well as making your users happier. In examining Apache, our HTTP server, there are a few areas that can be looked at. Apache's configuration file, a long standing mystery to many developers, is where a lot of memory management problems end up being created. The problem is that Apache quite likes its diet of RAM and isn't about to let any escape its grasp without a fight. The other problem is that Apache, while being a super successful fast server, has even nimbler competitors that require far less memory (heresy!). A strategy where we both configure Apache to be as optimal as possible given the available server resources, and bypass it entirely when it is absolutely not needed, can lead to some dramatic improvements in performance. There are also two other factors, one which relates to letting clients cache content and another which is a side effect of the continuing march towards 64bit operating

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (21 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

systems. Let's start with configuration before I find myself tied to a stake surrounded by burning timber and stern faced people with pitchforks.

B.7.1. Optimising Apache's Configuration


The simple truth of Apache is that your server can only efficiently support the number of child processes your memory can accommodate. The classic misconfigured server allows for too many (or conversely too few) Apache children to run around uncontrolled, using so much memory that an encounter with swap space is inevitable. Reigning in the number of Apache child processes and keeping a lid on ever increasing memory consumption will keep your server running at maximum efficiency without completely digging itself into an early grave when the Digg Effect comes knocking. Unfortunately, there is no one perfect Apache configuration. Some servers have more memory than others, or spend more time serving static files instead of dynamic content. Your configuration will need to be adjusted and tested (we mentioned Apache Bench and Siege earlier) depending on your own application's profile and server software. The pointers below are broad suggestions on where to look when starting out. For memory management, the most important configuration options are StartServers, MinSpareServers, MaxSpareServers, MaxClients, and MaxRequestsPerChild. Since PHP likes prefork Apache, every request to the server requires a child process. Luckily, child processes are allowed to stick around and service multiple requests (less waiting around for new ones to spawn). Each child therefore spends time sitting around occupying a pile of memory. So in a nutshell, if your server memory (after accounting for other processes like MySQL, ssh, etc) has enough memory to let 40 Apache processes exist without hitting swap space (see MaxClients), then you should make sure you never have many more than that. Calculating how many clients to allow is no easy proposition. The simple calculation is:
Dedicated Apache Memory / Average Apache Process Memory = MaxClients

It may be a three second calculation, but Apache consumes memory differently for different scenarios. A simple Zend Framework request might consume 17-20MB per process (say 40

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (22 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

processes for a server with 1GB RAM, 800 of which is considered free for Apache). Serving a static or cached page would consume considerably less, 2-4MB per process. Adjusting Apache's configuration means having some judgement on how users are utilising the application, how often cached pages vs fully dynamic pages are requested, and what level of caching is employed (full or partial). While monitoring memory usage (perhaps using a mix of top, free or the very nice htop) and doing some pounding with ApacheBench and siege, configure the above settings starting with some modest values (usually the default after installation). Based on the resulting memory usage level, you can start tweaking to allow for more or less total child processes (MaxClients) with varying starting positions (StartServers, MinSpareServers). MaxRequestsPerChild usually starts with quite a high value but you should be wary if child processes start growing in memory over time - keep the setting to a level that kills off child processes if they start inflating too much - its intent after all is to combat potential memory leaks. Make sure the ServerLimit configuration value stays ahead of MaxClients. Here's a snapshot showing htop at work (a lot prettier than top with lots of interactive features).

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (23 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

Server resource monitoring with htop

Other targets you should look at include KeepAlive and KeepAliveTimeout. Make sure they are enabled since they let clients reuse connections for multiple HTTP requests. Of course, if your client doesn't need to make a lot more requests, KeepAliveTimeout should move to a level that prevents connections staying alive for too long when they could be serving the other clients clamouring for attention. Starting with a KeepAliveTimeout of 2 is usually sufficient. Some websites with optimised HTML even do better when this is disabled or set to 1. Next up is everyone's constant - .htaccess files. If you have control over your own Virtual Hosts, consider moving .htaccess directives into the appropriate Directory container of http.conf or the external vhosts file. That will cut out the continual .htaccess parsing Apache needs to do. My final words are reserved for Apache's mod_expire module which controls Expire and CacheControl HTTP headers. These headers tells clients to cache static content and not to bother requesting items until they have...expired! It's useful for images, CSS, javascript and other static content. Apache's configuration options for these can be set on a server, Virtual Host or Directory level making fine tuning possible. It's usefulness lies in cutting down the number of requests clients

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (24 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

make - less requests means leaving memory and CPU cycles freer to serve others. Here's an example influencing CSS files, it adds HTTP headers to expire files served from the given directory 30 days (2592000 seconds) after each of the files' modification (M) times.

<Directory /home/mrweb/public_html/example.com/css> ExpiresActive On ExpiresByType text/css M2592000 </Directory>


After optimisation, you might notice you still seem to be getting 35-40 requests per second with as many Apache processes as can fit in memory, but htop or free insist you have free RAM sitting idle even with Apache Bench ripping along. Unfortunately, sometimes your memory capacity far exceeds the ability of your CPU to keep up thus creating a bottleneck. There's really not much you can do in this scenario without investing in more CPU capacity except perhaps ensuring your application is optimised against over usage of the CPU. Caching obviously plays a part here. In similar situations, I do tend to start migrating from disk caches to memory caches (for example using APC or Memcached backends for Zend_Cache). Why not if there's free RAM to take advantage of? Memory caches are very fast, and can be used to cache the results of CPU intensive operations (even if the result is something tiny) to get that little extra out of the CPU.

B.7.2. Avoiding Apache Completely


Sometimes, you just need to go around Apache. Apache is fast but it uses a lot more memory, and can be slower, than a number of alternatives like Lighttpd and Nginx. In this section we'll take a look at using Nginx as a reverse proxy to Apache. This means that Nginx serves as the front line HTTP server proxying all requests for dynamic content to Apache in the background, but handling all static content itself. It also means that Apache sends any content to Nginx to serve, letting Apache move on to another request faster. This approach offers a number of advantages. Nginx has a tiny memory footprint compared to Apache, so replacing any use of Apache with Nginx offers an immediate memory saving. This comes into the light when serving static content where Nginx is faster and lighter than using Apache. It's also

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (25 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

of use where slow clients are common. Slow clients are those who take a longer than usual time to complete downloading requests. While the client is working, an Apache process is sitting on the server for any number of seconds waiting for the client to finish up. Would you prefer to have a 15MB + process waiting for ages, or instead have Nginx with it's minimal footprint doing the waiting leaving the Apache process free to move to another dynamic content request? The answer should be obvious! Staying with the Ubuntu theme of this book, installing Nginx is a simple:

sudo aptitude install nginx


You can omit sudo if running as root, but seriously why would you even have root accessible? In configuring a reverse proxy setup, it becomes important to consider the role of both Nginx and Apache. Apache will no longer be listening on port 80 since Nginx will take over as the front end, so we should change Apache's configuration to instead listen on something like port 8080 by changing the main configuration file or, where used, ports.conf.

NameVirtualHost *:8080 Listen 8080 <IfModule mod_ssl.c> Listen 443 </IfModule>


We should also change any Virtual Host configurations to use port 8080 also.

<VirtualHost *:8080> ServerAdmin webmaster@example.com ServerName example.com.com ServerAlias www.example.com DocumentRoot /var/www </VirtualHost>
Moving on to Nginx, you employ a Ubuntu style configuration with a main configuration file, and subconfigurations for Virtual Hosts (Nginx servers). There's an element of duplication here since Nginx
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (26 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

will often need a matching "server" configuration container for each Apache Virtual Host we want to use the reverse proxy setup. Here's a sample main configuration file stored at /etc/nginx/nginx.conf under Ubuntu.

# Ubuntu Intrepid user worker_processes error_log pid www-data www-data; 2; /var/log/nginx/error.log warn; /var/run/nginx.pid; 1024;

events { worker_connections use epoll; }

http { server_names_hash_bucket_size 64; include default_type /etc/nginx/mime.types; application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log 2; client_max_body_size 32m; client_body_buffer_size server_tokens sendfile tcp_nopush tcp_nodelay keepalive_timeout off; on; on; off; 2; 128k; /var/log/nginx/access.log;

client_body_temp_path /var/spool/nginx-client-body 1

# include per-server (Virtual Host) configurations for enabled sites include /etc/nginx/sites-enabled/*; }

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (27 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

Nginx tends to have a reputation for obscure configuration documentation since the original texts are all written in Russian. English translations are maintained at http://wiki.codemongers.com/. You won't find a MaxClients here, rather it's a factor of the values of worker_processes and worker_connections. The documentation covers the other options noted here and some will look familiar coming from Apache. At the end of this configuration, or in a separate file at the specified /etc/nginx/sites-enabled/ directory you can create per-server configurations. These are akin to Apache Virtual Hosts configurations.

server { listen server_name

80; example.com www.example.com;

# Default Gzip Configuration (Set Exceptions Per Location) gzip on; gzip_comp_level 2; gzip_proxied any; gzip_types text/plain text/html text/css text/xml application/xml application/xml+rss \ application/xml+atom text/javascript application/ x-javascript application/javascript; # Handle Static Content Here location ~* ^.+\.(jpg|jpeg|gif|png|ico)$ { root /var/www; access_log off; gzip off; expires 30d; } location ~* ^.+\.(css|js)$ { root /var/www; access_log off; expires 1d; } location ~* ^.+\.(pdf|gz|bz2|exe|rar|zip|7z)$ { root /var/www; gzip off; } # Proxy Non-Static Requests to Apache location / { # Proxy Configuration proxy_pass http://your-servers-ip:8080/; proxy_redirect off;
http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (28 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

proxy_set_header Host proxy_set_header X-Real-IP proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_max_temp_file_size 0; client_max_body_size client_body_buffer_size proxy_connect_timeout proxy_send_timeout proxy_read_timeout proxy_buffer_size proxy_buffers proxy_busy_buffers_size proxy_temp_file_write_size } }
Caution

$host; $remote_addr;

10m; 128k; 90; 90; 90; 4k; 4 32k; 64k; 64k;

Lines ending with \ indicate a single line that is wrapped here for readability and should be a single line in the actual configuration file.

This server configuration is pointed to the same document root as out earlier Apache Virtual Host. Think of it as a filter and gateway. The very first part of the server configuration sets up content compression using gzip as default. I've added three location containers which apply a regular expression to intercept requests for specific file types. When detected, these will be served directly by Nginx without bothering Apache. Depending on the file type I've also told Nginx whether or not to gzip the content, and what Expires header to set (if any). These three containers ensure Apache need never spend expensive memory on static content Nginx can serve. The final fourth location container effects everything the previous containers don't catch, which will be dynamic content and any static files Nginx's regular expressions don't match. Here we configure Nginx to forward the request to port 8080 of the local server IP where Apache is listening. Once Apache has finished the request, it's immediately sent back to Nginx (leaving Apache free for something else) to handle.

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (29 de 30)28/10/2011 12:51:47

Appendix B. Performance Optimisation For Zend Framework Applications - Zend Framework Book: Surviving The Deep End

B.8. Conclusion
We've run the gamut of optimisation tips for Zend Framework applications and even touched on a few hardware concerns to boot. There are doubtless many other optimisation tips not mentioned here so it is, as you can see, a broad area. The primary message threaded across the appendix is that optimisation requires measuring progress against a baseline performance benchmark and other metrics. It's only by applying a robust methodology that real opportunities for optimisation are identified and their potential gain judged. Wasting your time on random optimisations with minimal gain is something well worth avoiding.

Prev Appendix A. Creating A Local Domain Using Apache Virtual Hosts Home

Next Appendix C. Copyright Information

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/...rformance.optimisation.for.zend.framework.applications (30 de 30)28/10/2011 12:51:47

Appendix A. Creating A Local Domain Using Apache Virtual Hosts - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Appendix A. Creating A Local Domain Using Apache Virtual Hosts


Table of Contents A.1. Introduction A.2. Configuring Apache With Virtual Hosts A.3. Configuring Local HOSTS File A.4. Conclusion

A.1. Introduction
During your programming career, or hobby, you'll eventually need to learn how to use Virtual Hosts. The concept refers to hosting multiple domain names at the same local IP. These domains are configured for your web server, and although the name may differ, we'll use the Apache terminology

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/creating.a.local.domain.using.apache.virtual.hosts (1 de 8)28/10/2011 12:51:53

Appendix A. Creating A Local Domain Using Apache Virtual Hosts - Zend Framework Book: Surviving The Deep End

for consistency. In this appendix, I'll describe how to create a local domain called "helloworld.tld" whose URL will be http://helloworld.tld. The principle is almost identical for public domains, the main difference being public domains rely on configuring your DNS records to host the new domain name. For local domains, however, rather than using DNS records we can simply add the new domain to your system's hosts file which is available for nearly all systems, including Windows. The reason we use local domains in development is fairly simple. It lets us create new domains for each project and removes the need to maintain everything as a set of subdirectories under localhost. It's also a really simple task once you run through it once since it doesn't vary from domain to domain other than the server name and the server specific Directory or Location configurations you may wish to employ.

A.2. Configuring Apache With Virtual Hosts


To create a new local domain, you need to start by configuring Apache. How this is accomplished for Virtual Hosts varies between systems, so the following assumes a Ubuntu system with some alternative notes including Windows instructions. Ubuntu employs a managed setup for Apache Virtual Hosts and modules which is quite a handy way of configuration. Virtual Hosts are defined in files located in /etc/apache2/sites-available using any filename (though obviously a name related to the domain to host is preferable - I just use the domain name itself most of the time). While splitting out configuration among numerous files allows for better atomic control, many other systems will generally just use either the main http.conf or apache2.conf file, or some Virtual Host specific file such as http-vhosts.conf. The basic Virtual Host configuration we're seeking is as follows:

# Setup Listening Port NameVirtualHost *:80 # Ensure "localhost" is preserved unchanged pointed
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/creating.a.local.domain.using.apache.virtual.hosts (2 de 8)28/10/2011 12:51:53

Appendix A. Creating A Local Domain Using Apache Virtual Hosts - Zend Framework Book: Surviving The Deep End

# to the default document root for our system. <VirtualHost *:80> ServerName localhost DocumentRoot /var/www </VirtualHost> # Setup "helloworld.tld" Virtual Host <VirtualHost *:80> ServerName helloworld.tld DocumentRoot /home/padraic/www/helloworld/public <Directory /home/padraic/www/helloworld/public> Options Indexes FollowSymLinks Includes AllowOverride All Order allow,deny Allow from all </Directory> </VirtualHost>
With Ubuntu, we would implement this configuration by first editing /etc/apache2/ports.conf to contain:

# Setup Listening Port NameVirtualHost *:80


Since Ubuntu has localhost predefined with a Document Root of /var/www in a configuration at /etc/ apache2/sites-available/default, we only need to create a new file to contain the helloworld.tld configuration at /etc/apache2/sites-available/helloworld.tld containing:

# Setup "helloworld.tld" Virtual Host <VirtualHost *:80> ServerName helloworld.tld DocumentRoot /home/padraic/www/helloworld/public <Directory /home/padraic/www/helloworld/public> Options Indexes FollowSymLinks Includes AllowOverride All Order allow,deny Allow from all </Directory> </VirtualHost>
In Windows, under XAMPP for example, you would edit C:/xampp/apache/conf/extra/http-vhosts.
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/creating.a.local.domain.using.apache.virtual.hosts (3 de 8)28/10/2011 12:51:53

Appendix A. Creating A Local Domain Using Apache Virtual Hosts - Zend Framework Book: Surviving The Deep End

conf since there are no separate ports or domain specific conf files. To ensure the vhosts file is loaded as a configuration, the main XAMPP http.conf file should include a line containing:

# Virtual hosts Include conf/extra/httpd-vhosts.conf


Here's a sample configuration under XAMPP for Windows:

# Setup Listening Port NameVirtualHost *:80 # Ensure "localhost" is preserved <VirtualHost *:80> ServerName localhost DocumentRoot "C:\xampp\htdocs" </VirtualHost> # Setup "helloworld" Virtual Host <VirtualHost *:80> ServerName helloworld.tld DocumentRoot "C:\projects\helloworld\public" <Directory "C:\projects\helloworld\public"> Options Indexes FollowSymLinks Includes AllowOverride All Order allow,deny Allow from all </Directory> </VirtualHost>
Any other Windows variation will follow a similar pattern. If you get stuck, simply add the configuration at the end of the main Apache configuration file. The overall configuration above creates two domain names: localhost and helloworld.tld. Recreating localhost seems a bit silly, but it's a necessity to ensure localhost remains intact and usable as a unique domain name recognised by Apache when employing Virtual Hosts. The section after localhost configuration creates a new Virtual Host with a server name of "helloworld.tld" which should point Apache to the new Document Root whenever we enter http://helloworld.tld into a browser. Since we are using the Zend Framework, the Document Root is always the /public

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/creating.a.local.domain.using.apache.virtual.hosts (4 de 8)28/10/2011 12:51:53

Appendix A. Creating A Local Domain Using Apache Virtual Hosts - Zend Framework Book: Surviving The Deep End

directory of the application. Also note, the application can be stored almost anywhere which leaves you a lot more flexibility on where to store application files. You could just use a /www or / public_html directory in your home directory on Linux, or a /www directory in your Windows C drive. Actually, this directory's name is unimportant - merely a handy convention so you know what it is. Feel free to call it wheelsoffire if you want. The final part is to add a Directory configuration for the new Virtual Host's Document Root. This ensures Apache can serve requests from this directory by supplying appropriate permissions. Apache will not serve files from a directory without the correct access permissions. By default, options are severely limited to prevent unauthorised access from the web, so be sure to add this to allow files to be served from the Document Root. The options I'm using here are the usual ones Apache applies to localhost's Document Root which are fairly liberal and allow for the use of . htaccess files so users can access Apache configuration options at runtime. To activate this Virtual Host under Ubuntu we need only run the following commands to create a symlink for the new configuration file at /etc/apache2/sites-enabled/helloworld.tld:

sudo a2ensite helloworld.tld sudo /etc/init.d/apache2 reload


If you are on Windows or another Linux variant, reloading or restarting Apache has the same effect since you likely won't be using the Ubuntu site layout for these systems. The Ubuntu method ensures you can take a domain offline using the opposite a2dissite command. There are similar commands like a2enmod and a2dismod for enabling/disabling modules more easily than manually editing configuration files.

A.3. Configuring Local HOSTS File


We're not done quite yet however. There is also the matter of mapping the new domain "helloworld" to the correct local IP. This is easily resolved by adding the new domain name to the local HOSTS file which recognises and maps local domains to IP addresses.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/creating.a.local.domain.using.apache.virtual.hosts (5 de 8)28/10/2011 12:51:53

Appendix A. Creating A Local Domain Using Apache Virtual Hosts - Zend Framework Book: Surviving The Deep End

Under Ubuntu, the file is located at /etc/hosts and you can edit it using something like:

sudo nano /etc/hosts


We can ensure our system recognises the local domain we want to use for the new Virtual Host configuration by editing this to something like:

127.0.0.1 127.0.0.1 127.0.1.1

localhost helloworld.tld padraic-desktop

# The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts
Nothing else is needed - just the association of our local domain to the localhost IP address. Under Windows, you should be aware this file is generally only editable by an Administrator user, so on Windows Vista you may need to adjust permissions temporarily or open an editor with Administrator privileges in order to edit this. Otherwise the editing is almost identical and you can find the HOSTS file at C:\Windows\System32\drivers\etc\hosts:

# Copyright (c) 1993-2006 Microsoft Corp. # # This is a sample HOSTS file used by Microsoft TCP/IP for Windows. # # This file contains the mappings of IP addresses to host names. Each # entry should be kept on an individual line. The IP address should # be placed in the first column followed by the corresponding host name. # The IP address and the host name should be separated by at least one
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/creating.a.local.domain.using.apache.virtual.hosts (6 de 8)28/10/2011 12:51:53

Appendix A. Creating A Local Domain Using Apache Virtual Hosts - Zend Framework Book: Surviving The Deep End

# space. # # Additionally, comments (such as these) may be inserted on individual # lines or following the machine name denoted by a '#' symbol. # # For example: # # 102.54.94.97 rhino.acme.com # source server # 38.25.63.10 x.acme.com # x client host 127.0.0.1 127.0.0.1 ::1 localhost helloworld.tld localhost

In both variants, we see that helloworld.tld maps to 127.0.0.1 (the same IP as localhost but Apache will note the difference and use the correct Virtual Host we've configured). Don't worry about the ::1 reference found here, it's an IPv6 pattern we don't need to touch at this time. I suggest reading more about the HOSTS file to understand any other options fully like using differing local IPs.

A.4. Conclusion
In this appendix we have met how to create and enable Virtual Hosts for our local domains. There's nothing to be afraid of in here! Creating new hosts is a common and simple practice once you graduate away from trying to bundle all your projects under the default Apache document root. With a little practice you can replicate not only full domains, but also subdomains - perfectly replicating the actual hosting conditions you expect to see when the project is deployed. This enables testing of such performance enhancing tactics like hosting images, css and javascript on a collection of subdomains to get around modern browsers' simultaneous domain connections (a common bottleneck in downloading web pages) and gives you a root domain to work with rather than a more troublesome subdirectory where a root path must be prefixed to all loaded files. I'm sure you can invent any number of useful uses, and what you learn here is directly applicable on production servers where creating Virtual Hosts for your registered and hosted domains is not only common, but necessary unless you are using an intermediary management tool.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/creating.a.local.domain.using.apache.virtual.hosts (7 de 8)28/10/2011 12:51:53

Appendix A. Creating A Local Domain Using Apache Virtual Hosts - Zend Framework Book: Surviving The Deep End

Throughout the book, we'll refer to other configuration options you can add to your Virtual Host settings since I only touched the absolute essentials here.

Prev Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 and Yahoo! User Interface Library Home

Next Appendix B. Performance Optimisation For Zend Framework Applications

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/creating.a.local.domain.using.apache.virtual.hosts (8 de 8)28/10/2011 12:51:53

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 and Yahoo! User Interface Library
Table of Contents 10.1. Introduction 10.2. Zend_View: Object Oriented Templating 10.2.1. Layouts 10.2.2. Partials 10.2.3. View Helpers 10.2.4. Placeholders 10.2.5. Short Tags vs Full Tags 10.3. The ZFBlog Application Setup 10.4. Creating An Index Page With HTML 5 10.5. Extracting Static Markup Into A Layout 10.6. Replacing Changeable Elements With Placeholders 10.7. Improving HTML 5 Support With Custom View Helpers 10.8. Adding A Link To A Custom Stylesheet 10.9. Customising The Style 10.10. Conclusion

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (1 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Important This is a draft chapter. Content may change and new sections be added in the coming days. Your feedback is, as always, welcome in the paragraph or chapter level comments including any criticism (more than welcome!) or suggestions for future updates to this chapter.

10.1. Introduction
In this chapter we will be creating a simple web design for our blog. Obviously this falls into the View layer of the ModelView-Controller (MVC) architectural design pattern. On a more basic level, it's primarily the HTML markup and CSS styling the blog will be using. Like many frameworks, all markup will be stored as templates in our application which are rendered at the end of any request and sent in a response to the user. It gets more interesting when you consider that a complex HTML page can be broken down into reusable or conditionally reusable elements. For example, every HTML page likely has reusable elements like a header, a footer, a navigation menu, a breadcrumb trail, etc. Alongside these elements are dynamic content and some other elements that are needed only for particular pages. In other places you need to apply special formatting rules, or insert formatted data from your database. Leveraging Zend Framework's templating via
Zend_View can reduce duplicating all this markup and template processing from template to template by using View

Helpers, Partials, Placeholders and Zend_Layout for the outermost common markup like the <head>, <title>, <html> and other elements including any CSS stylesheet links or Javascript files that need to be included. I'm quite a bad web designer (sorry!) so for the our blog application I'll keep to a relatively simple design that someone else can supercharge later. The blog's markup will use HTML 5. HTML 5 is the next major revision of HTML so it seemed fitting to do some experimentation with the new standard. Since the XHTML 2.0 project has been shut down (at least for now) it's also the next major pseudo-replacement for XHTML. HTML 5, for the record, can technically be delivered as either an HTML or XML based serialisation (i.e. either text/html or application/xml+xhtml along with the syntax rules each implies). Given browser support (IE does not accept the XML content type), only the HTML serialisation is really viable though I will use XHTML conventions such as closing all tags and using lowercase attribute and tag names as much as possible since I am most familiar with those. My use of HTML 5 should be considered experimental. It's not a final recommendation yet and not all browsers support it. You should be considering Firefox 3.5, Internet Explorer 8 or any other more recent browser version for use with HTML 5 anything older will have patchy support. HTML 5 is, however, backwards compatible to a degree with HTML 4. It does need some coaxing with Internet Explorer when styling (IE doesn't recognise the new HTML 5 elements and can't style them unlike other browsers) but we'll solve that later using a very neat and small javascript library, html5shiv. To assist in styling, I will also be using the Yahoo User Interface (YUI) Library's CSS framework. I'll use this to normalise behaviour across all major browsers (i.e. font types, sizes, element spacing and margins, etc.). I will also use its grid implementation to setup page sections more easily. YUI CSS is only one of many CSS Frameworks. In the past I've used Elastic and Blueprint CSS. These frameworks are used primarily to save development time and write (hopefully) less

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (2 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

CSS - an important factor for me since my design skills leave much to be desired and I could spend hours playing with CSS settings! I really don't like editing too much CSS when I can entice a monkey with vast bags of peanuts to do a better job later on. That or a professional web designer (probably better than the monkey). For the moment I just need a nice default design which looks kind of okay in a pinch. In this article we're going to take a stab at setting up a default blog style, using some filler content, and finally capturing the design with Zend_View templates.

10.2. Zend_View: Object Oriented Templating


We PHP programmers have been using template engines since the dawn of PHP. In fact, PHP itself is a template engine and that was the original raison d'tre for its entire existence - you can mix PHP into your HTML or other markup very easily. One thing we have not been great at is making a distinction between presentation (markup/formatting) and presentation logic (encapsulates logic for generating markup, formatting elements, or even accessing the Model). Mostly, the distinction has been to separate PHP from HTML which completely misses the point since you are only replacing PHP with a custom tag language. This strategy's sole redeeming factor is that it often prevents web designers from using PHP itself - a valid concern no doubt. PHP is a template engine so there is nothing wrong with mixing PHP and HTML. It's part and parcel of how PHP works. Doing it in a more deliberate manner as part of a templating system simply pushes us into doing it right.
Zend_View is the Zend Framework's template rendering solution which formalises the concept of separating presentation

from presentation logic. It's an object oriented solution which does not use a tag based system (e.g. like that used by Smarty or Java's JavaServer Pages (JSP)). Templates rendered by Zend_View use PHP directly instead. At a higher level,
Zend_View encapsulates an application's View, from MVC, which accepts user input and outputs a presentation of the

application, whether it be HTML, XML, JSON, etc., based on any data it is either provided with or designed to independently retrieve. Technically Zend_View doesn't accept user input since this is handled in the Controller layer - there is some leakage between Views and Controllers in this respect once you use MVC for web applications rather than desktop applications given the nature of HTTP, and besides, the Controller is itself a presentation mechanic. It's strictly related to the current request and manages response construction and output. The most important aspect of Zend_View templating is that it is object oriented. You may use absolutely any value type in a template: arrays, scalars, objects and even PHP resources. There is no intermediary tag system between you and the full power of PHP. Part of this OOP approach is that all templates are effectively executed within the variable scope of the current Zend_View instance. To explain this consider the following template.
q q q q q q

<html> <head><title>My Page</title></head> <body> <?php echo strtolower($this->myName) ?> </body> </html>

In this template we see that PHP is used directly. We also see a reference to $this - representing the current object instance. But the template is not an object! Where does this come from?

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (3 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Templates are included into the Zend_View::_run() protected method when being rendered and output buffering is used to capture the evaluated content. So any template content is effectively treated as evaluated code within Zend_View::_run(), i.e. you can access class methods, properties (like the public Zend_View::$myName) and other features as if you were writing a method on Zend_View itself (which effectively you are!). How does this influence our thinking about the View? Since all templates are essentially method calls (in one form or another) it stands to reason that templates are indirectly amenable to all the advantages offered by object oriented programming. Templates can be nested, repeated, refactored, and encapsulated in other objects (View Helpers) through composition. This becomes more evident once you meet several concepts offered by Zend_View: View Helpers, Placeholders and Layouts. It is also evident from our discussions so far regarding the role of the Model. As I noted in
Chapter 3: The Model, Views can directly access and read from Models using View Helpers, without the need for any

Controller code to manage that interaction.

10.2.1. Layouts
The concept of Layouts is implemented by Zend_Layout. A Layout is that part of your presentation which remains relatively static across many (or all) pages. In a standard HTML document, this would likely include the header, footer, navigation menu, and any other section common to many pages. Since these parts remain largely static, it's pointless duplicating it across every single page level template. It would be impossible to maintain, requiring countless other templates to be altered for any change. For this reason, we can use Zend_Layout to create a number of Layout templates, into which all other template output is injected. This ensures templates in the rest of the application need not concern themselves with a common layout - it's offloaded to Zend_Layout. A simple Layout template could be:
q q q q q q q q

<html> <head> <title>My Page</title> </head> <body> <?php echo $this->layout()->content ?> </body> </html>

As we discussed in previous chapters, the Controller implements View rendering by default using the ViewHelper Action Helper. Zend_Layout registers a new Action Helper and a FrontController Plugin,
Zend_Layout_Controller_Action_Helper_Layout and Zend_Layout_Controller_Plugin_Layout, which allow

rendering of a layout and access to the Layout instance from Controllers respectively. We'll cover Helpers and Plugins in more detail later but they may implement a hook method which is called automatically by the FrontController after certain events occur, in this case a postDispatch() method which is called when a request is dispatched to a Controller. The output from the Controller matched View gets assigned to the Layout template's public $content property which we may in turn echo at any point in the Layout template, such as between the <body> tags in our example.
Zend_Layout also allows you to selectively disable layout rendering from Controllers, using the registered Action Helper.

This is important when outputting something other than HTML, like JSON for example. Wrapping JSON or XML output in a

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (4 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

HTML layout probably won't work in your favour. You can also swap layouts or set different layout variables from Controllers, so it's a very flexible system. In terms of design patterns, Zend_Layout implements a loose form of Two Step View as described by Martin Fowler in Patterns Of Enterprise Application Architecture (POEAA). Fowler's definition of the Two Step View states:

Turns domain data into HTML in two steps: first by forming some kind of logical page, then rendering the logical page into HTML.

Obviously, in our case, the domain data is rendered immediately into HTML before being inserted into a specific point in the layout's HTML. The use of a Two Step View approach to layouts complements Zend_View's template composition approach as described below when using Partials. You should also be aware that while Zend_Layout handles all the mechanics of layout assignments, the Layout template itself is rendered by an instance of Zend_View. So your Layouts may make full use of the typical Zend_View goodies like Partials, Placeholders and View Helpers. This can be something of a confusing point and may encourage a less than optimal solution I'll mention now. One aspect of Zend_Layout to be careful with is that the Reference Guide mentions that Layouts and named response segments can be used in conjunction with the ActionStack Action Helper to create widgetised pages, i.e. each Action renders a View which is inserted into the template at its assigned point. There is an extremely important proviso to consider here - dispatching an action is an expensive task. As the name ActionStack suggests, this Action Helper creates a stack of Controller Actions to execute in turn requiring multiple dispatches of nearly the entire MVC framework. This should be avoided unless absolutely necessary. There is almost no reason to use the ActionStack since anything it achieves can be just as easily achieved using View Helpers at a fraction of the performance cost. Here's a blog post from Ryan Mauger explaining this in a bit more detail in Why the Zend Framework Actionstack is Evil. Note that this does not mean you should never use this aspect of Zend Framework but that you should have a very concrete need and have a tolerance for the inevitable performance hit.

10.2.2. Partials
Partials have a very simple use case. They are fragments of a higher level template which can be rendered, either once or repeatedly, at a specific point in the parent template. Partials may also be called "template fragments" to reflect this. A simple use case would be our blog's index page which may display a number of articles. The markup for each article is almost identical, differing only by the article text included. We could easily split this common article markup into a single partial, and loop through a collection of articles rendering each in turn. Besides reducing the amount of markup in a template, and offering a simple mechanism for looping through a data collection, they also afford some reusability. We could have article markup in the index page, the entry specific page, and several other locations. Using a partial isolates this
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (5 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

repeated markup ensuring we can make global changes from a single location. These template fragments, along with any other markup generating mechanism, form an implementation of the Composite
View design pattern defined in the book, Core J2EE Patterns.

Use composite views that are composed of multiple atomic subviews. Each component of the template may be included dynamically into the whole and the layout of the page may be managed independently of the content.

An observation from our earlier examination of Layouts that I would make is that the Two Step View is itself a sub-pattern of the Composite View. In POEAA, Fowler skipped over a number of View related design patterns now in common use such as Composite View and View Helper which were instead formally defined in the Core J2EE Patterns book some time later.
Zend_View partials are implemented by the Zend_View_Helper_Partial and Zend_View_Helper_PartialLoop View

Helpers. We cover View Helpers in the next section. The first helper renders a partial template once on every call and can be used either as a once off rendering or be embedded in a looping mechanism within the parent template. The second class specifically supports looping internally so that looping need not be embedded in the parent template. The parameter passed to a partial may not be arbitrary, it must be an associative array or an object implementing a toArray
() method. Otherwise it will be reduced to an array of object properties using get_object_vars(). From the resulting

associative array, the keys are assigned as local class property names with their matching values assigned. Consider the following example passing some minor data to a typical non-looping partial.
q q q q

<?php echo $this->partial('article.phtml', array( 'title'=>'Title', 'content'=>'My Content!' )) ?>

The article.phtml file is just like any other template except that it is only called as a partial. It would access the passed data as local class properties from it's own host Zend_View instance.
q q q q q q

<article> <h3><?php echo $this->escape($this->title) ?></h3> <div class="content"> <?php echo $this->content ?> </div <article>

When using the partial loop helper, the parameter should be an array of items to loop through (each item following the same rules as a normal partial parameter) or any iterable object returning a valid partial parameter. As an example, the
Zend_Db_Table_Rowset class is an iterable collection of objects of type Zend_Db_Table_Row. Each instance of Zend_Db_Table_Row implements a toArray() method. This means a rowset is a valid partial loop parameter.

For example, we could use our earlier partial by passing in an array of articles using the partial loop helper.
q q q q

<?php echo $this->partialLoop('article.phtml', array( array('title'=>'Title', 'content'=>'My Content!'), array('title'=>'Title2', 'content'=>'More Content!') )) ?>
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (6 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Besides arrays, you can pass in objects directly and bypass the attempted casting of the object to an array. This is accomplished by setting an object key before calling the Zend_View::partial() or Zend_View::partialLoop() methods with any parameters. Remember that a parameterless call to a View Helper's primary method will usually just return the View Helper object for method chaining. For example:
q q

<?php echo $this->partialLoop()->setObjectKey('article') ->partialLoop($articleCollection) ?>

In the above example, all objects in the iterable Collection object (something we might implement in our domain model) are added to the partial template's host class as a public $article property. Within the partial we could do something like:
q q q q q q

<article> <h3><?php echo $this->escape($this->article->title) ?></h3> <div class="content"> <?php echo $this->article->content ?> </div> </article>

This might not be the most brief example, afterall, using objects means the partial references are even longer. An array might be simpler but there are cases where using the object itself has benefits since a domain object may have lazy loaded references to other domain objects, like the Author of our Article in Chapter 9. Partials have two other facets of importance. First, they operate in a separate variable scope from the parent template. This effectively means they are only aware of data specifically passed to them when calling the Zend_View::partial() or
Zend_View::partialLoop() methods (these are mapped to Zend_View_Helper_Partial::partial() and Zend_View_Helper_Partial::partialLoop() respectively as that View Helper's primary method). They are not aware of

any data set on the parent View from a controller or, if a second level nested fragment, the parent partial. This imposed restriction encourages a more object oriented approach to templating and prevents the leakage of data across multiple templates and template fragments (i.e. the problem which besets OOP designs employing global variables). This is simply good practice - we don't allow globals (hopefully) to contaminate the variable scope of our objects, so why should we allow the same for templates? All templates should be insulated from each other to prevent unwanted dependencies emerging that would make testing, maintenance and change more costly. On a final note, you can nest a partial into another partial. You could have a whole tree of them if needed. It really depends on what your partial represents. A small slice of markup or a much larger part of a page template or layout. The nature of the template game is to compose a single view out of many constituent parts. How those parts are constructed or divided to prevent duplication of markup and encapsulate reusable functionality will depend on your needs.

10.2.3. View Helpers


If partials are the means of separating markup into reusable parcels, their presentation logic counterparts are View Helpers. A View Helper is used to create a reusable operation applied to a template. For example, imagine a partial has been passed a domain object representing a user comment. It contains a name, email, URL, some text and a date. The first four are easy, but how do you format the date for presentation? Your partial could include some PHP code to parse the date (depending on the date standard used), and recompose it into a desired readable form. But entries have published dates

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (7 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

and even modified dates, so do comments. Will you repeat this PHP code everywhere? This is obviously not a very maintainable solution. If something changes, and you need to reconfigure the parsing logic used, you could have lots of templates to edit. It would be far easier to encapsulate this PHP functionality as a View Helper, a reusable class that can be called from any template at any time. You could implement this as a
ZFExt_View_Helper_FormatDate class (a custom helper), containing a formatDate() method to accept an arbitrary date

and return a formatted form according to a specified pattern, like "YYYY-MM". This produces an easy to reuse class, which can benefit from unit testing and also be ported to other projects for reuse. As I also discussed in Chapter 3: The Model, View Helpers can also be used to encapsulate a Model using, for example, the last chapter's implementation of a simple Data Mapper. Your helper can now query the domain model for some data, format it and add markup, and return the final result for immediate insertion at a template point. The primary concern with this approach is to always remember that a View Helper should never alter the domain model it interacts with - that is the concern of whatever Controller is handling user input. View Helpers are therefore a really useful feature. You can offload special formatting, menu generation, model querying and decoration with markup and a lot more to View Helpers. View Helpers are also defined as a distinct pattern, as the View Helper design pattern, in the Core J2EE Patterns book. All View Helpers follow a similar design. They implement a method with a name matching the class name, so
ZFExt_View_Helper_FormatDate would define the method ZFExt_View_Helper_FormatDate::formatDate(). This

"primary" method will usually accept parameters for the helper to operate on and return the results. For more complex helpers with many public methods, this primary method may also return the object itself. Since
ZFExt_View_Helper_FormatDate would be a custom View Helper, you would also need to tell Zend_View where to find it,

and what its class namespace is. Let's consider a simple example. We've decided at the prompting of some analysis of our web page's loading performance to ensure that any javascript or css files can be cached by a client for extended periods. This is covered by the well documented Yahoo Performance Best Practices in the section Add an Expires or a Cache-Control Header. We therefore run off and configure Apache to use a far in the future Expires header when serving static files likes CSS or Javascript. We now find another problem - if the client caches these forever, how can we roll out changes and updates? A common strategy is to append a URI query string to the static file URIs which is updated in our markup whenever the referenced file changes. The cached resource uses the original URI (you can cache URIs with query strings if an explicit Expires header is sent), but when it changes the client will load from the new URI since the query string has changed, i.e. it's a new resource. This would create a URI of the form: http://zfblog.tld/css/style.css?1185131970. The final query string is, very obviously, a timestamp representing the file modification date. You could just as easily use a version number. Let's write a View Helper which can automatically assign these query strings so that our updates alter their URIs on the fly without any manual intervention. Your custom helpers may extend from Zend_View_Helper_Abstract which offers some standard functionality if needed (we don't here).

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (8 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

q q q q q q q q q q q q q

<?php class ZFExt_View_Helper_AppendModifiedDate extends Zend_View_Helper_Abstract { public function appendModifiedDate($file) { $root = getcwd(); $filepath = $root . $file; $mtime = filemtime($filepath); return $file . '?' . $mtime; } }

The new View Helper is short and to the point. It accepts the relative path to a file on our server as normally used in markup, checks the file modified date, and appends that date as a timestamp to the path. The resulting path can then be passed to any other helper (e.g. the HeadLink or HeadScript View Helpers) or outputted directly into the relevant markup. Here's a template fragment using the new custom helper.
q

<link rel="stylesheet" href="<?php echo $this->appendModifiedDate('/css/style. css') ?>" type="text/css" media="screen" />

Besides writing custom helpers, the Zend Framework offers some standard View Helpers for common tasks like managing markup generation (e.g. form and head elements). We'll cover several of these soon which use one more feature of
Zend_View called Placeholders.

10.2.4. Placeholders
Placeholders address a specific need in templating with Zend Framework. They allow templates to access a shared registry of data, regardless of their variable scope. This is very close to the Registry Pattern, which in Zend Framework is often implemented using Zend_Registry, but has a more specific use case here. Consider a scenario where you discover that a web page needs to use some specific piece of javascript loaded as early as possible. Your layout does not contain it since it's just not needed for everything. Placeholders can resolve this dilemma by allowing templates and partials to add values to a Placeholder, a key in this shared registry. When a parent template or layout is rendered, it can then render the Placeholder data at any point it specifies. For Javascript, this is handled by a specialised Placeholder implemented by
Zend_View_Helper_HeadScript with which you can append, prepend, overwrite or set at a specific index any javascript

you need from any sub-template before it is finally rendered in a layout. Aside from offering a selection of specialised Placeholders like HeadLink, HeadMeta, etc, you can utilise the base Placeholder View Helper for any other data needing to be communicated across templates. Let's look at an example where we have a layout that can insert the output from a Placeholder. The layout offers a render point allowing any templates to change the text attached to a standard header.
q q q q q q q q q

<html> <head> <title>My Page</title> </head> <body> <h1><?php echo $this->placeholder('pageHeader') ?></h1> <?php echo $this->layout()->content ?> </body> </html>

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (9 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

We can now set this value from any other template (including partials).
q q

<?php $this->placeholder('pageHeader')->set('About') ?> <p>My name is Joe. Joe Bloggs.</p>

Unlike the more specific Placeholder implementations, the generic Placeholder helper can be used for any value. If it's a specific value following specific formatting rules you can subclass the general helper to also add this formatting when it's echoed into a template. For example, let's implement a custom View Helper to format a page header using HTML 5.
q q q q q q q q q q q q q q q q q q q q q

<?php class ZFExt_View_Helper_PageHeader extends Zend_View_Helper_Placeholder { public function pageHeader() { return $this; } public function set($value) { parent::placeholder('pageHeader')->set($value); } public function __string() { return "<h1>" . (string) parent::placeholder('pageHeader') . "</h1>"; } }

This is a very simple example which merely acts to create a concrete wrapper around the Placeholder container matching the key "pageHeader". It also encloses the page title value in <h1> tags. Here is our layout and page template using the new concrete Placeholder.
q q q q q q q q q q q

<html> <head> <title>My Page</title> </head> <body> <?php echo $this->pageHeader() ?> <?php echo $this->layout()->content ?> </body> </html> <?php $this->pageHeader()->set('About') ?> <p>My name is Joe. Joe Bloggs.</p>

10.2.5. Short Tags vs Full Tags


You may notice that throughout the book I show a preference for using full PHP tags, i.e. <?php (echo) ... ?>, in preference to short tags, <?(=) ... ?>. Short tags are an optional PHP setting. While many servers have them enabled to increase compatibility with applications utilising them, many stock servers will not since they are disabled in PHP's recommended configuration file, php.ini.recommended. In addition, we know that PHP 6 may deprecate short tags completely to resolve a conflict with XML declarations which use a similar form, <?xml ... ?>. Traditionally, XML declarations have to be echoed if generating XML from a template using short tags. Personally, I consider the probable PHP6 deprecation a nuisance. Writing <?php echo ... ?> more than a few times is plain annoying to me, then again since it is an optional setting, distributed applications should never use it anyway. In any case, it's better to stick with what

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (10 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

definitely works now rather than risk it breaking later. The Zend Framework has allowed short tags to be used, regardless of configuration, using a stream wrapper. This is enabled if the short tags option is disabled in your php.ini configuration file, i.e. the short_open_tag flag, and you have set the useStreamWrapper option to true. You can do this using the useStreamWrapper option key in your application.ini file or by calling Zend_View::setUseStreamWrapper() in your bootstrap. Using the stream wrapper will likely have a performance cost attached so if you really need short tags, it's better to just enable them in your PHP configuration.

10.3. The ZFBlog Application Setup


Setting up the new blog application is not horrendously difficult, just copy over the Hello World example to the blog application's project directory where you currently have the beginning of our application's domain model. It will make an excellent base to start with. You may wish to add a new local domain and Virtual Host specifically for the blog following the instructions from that chapter and the related appendix for setting up Apache Virtual Hosts. For the purposes of the book I'll be using the domain http://zfblog.tld. There are no other changes required. The rest of the chapter will detail where to go from there.

10.4. Creating An Index Page With HTML 5


The index page of our blog will predictably be the page displaying a list of entries. Using HTML 5, let's start by looking at the opening HTML tags. Later we'll add styling and worry about adding more elements and information to the page. For the moment, we'll be editing /application/views/scripts/index/index.phtml.
q q q q q q q

<!DOCTYPE html> <html> <head> <meta name="language" content="en" /> <meta charset="utf-8"/> <title>Pdraic Bradys Blog</title> <link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/resetfonts-grids.css" media="screen" rel="stylesheet" type="text/css" /> <link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" /> <!--[if IE]><script type="text/javascript" src="http://html5shiv.googlecode. com/svn/trunk/html5.js"></script><![endif]--> </head>

HTML 5 uses a simplified Doctype which pretty much simply states this is HTML. There is no versioning or other information attached or needed. As we are aware that we can set the Doctype used by Zend_View and any dependent components, we can print this in our template using the Zend_View_Helper_Doctype View Helper. The Zend Framework's support for HTML 5 extends as far as the Doctype which tells any dependent View Helpers to use HTML 4 style tags. In fact, HTML 5 can also be written in a similar way to XHTML, i.e. you can use lowercase tag and attribute names, enclose attribute values in quotes, close all tags, etc. This is my preferred method of writing HTML 5 since it feels cleaner and I don't need to depart too far from the XHTML I'm familiar with. Zend Framework therefore poses a minor problem - it doesn't distinguish between HTML 5 and XHTML 5 (HTML 5 written with XML rules of wellformedness). We'll solve this by writing a custom View Helper

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (11 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

to add support for a XHTML5 Doctype. As with any HTML/XHTML, the root element is <html>. It is immediately followed by a <head> element which contains any
<link>, <meta>, <title> or <style> elements we need. These can be written in any typical style though, as I explained, I

prefer the XHTML practice. HTML 5 does add one new feature here - a new attribute for a <meta> element called charset. This new attribute is intended as a simplified means of informing a parser what character encoding is used by the document, and therefore it's a valid replacement for the common use Content-Type <meta> element. Again, the Zend Framework does not support this as yet, so we will add a second custom View Helper to implement it. You may notice that even though we can write all these tags outright into our templates, I keep referring to implementing them as View Helpers. This is good practice, since using the View Helpers allows other templates to modify what appears in
<head> by appending, prepending or even overwriting what stylesheets, scripts, and meta information are initially set. You

might foresee this as advantageous in situations where a particular page needs additional styling or a different set of javascript code not required for all pages. I have also added two CSS stylesheets from the Yahoo User Interface framework. These are loaded directly from a Yahoo server so we don't need to store anything locally. The first of these is a minified version of three merged stylesheets: reset, fonts and grid. The first two ensure all major browsers are on the same page, eradicating various rendering differences each implements by default. There's nothing quite like trying to deal with the inconsistencies across browsers on simple default styling. The grid styles will be used later to create a column layout more easily. The second included stylesheet, base, imposes a standard foundation styling for all major elements out of the box. At this rate, I should have relatively little CSS to write other than what applies specifically to my preferred style for the blog. Finally, we have included a Javascript file from the html5shiv project. This is a relatively simple piece of Javascript used to ensure Internet Explorer can recognise the new HTML 5 elements. Otherwise we would not be able to style them. This is only needed for Internet Explorer so we have wrapped this in a set of conditionals matching all versions of Internet Explorer but excluding any other browser. Let's now add a Header section representing the page title and the upper navigation area.
q q q q q q q

q q q q q q q q

<!DOCTYPE html> <html> <head> <meta name="language" content="en" /> <meta charset="utf-8"/> <title>Pdraic Brady's Blog</title> <link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/resetfonts-grids.css" media="screen" rel="stylesheet" type="text/css" /> <link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" /> <!--[if IE]><script type="text/javascript" src="http://html5shiv.googlecode. com/svn/trunk/html5.js"></script><![endif]--> </head> <body> <header> <h1><a href="/">Pdraic Brady's Blog</a></h1> <nav> <ul>
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (12 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q

<li><a <li><a <li><a <li><a </ul> </nav> </header>

href="/">Home</a></li> href="#">About</a></li> href="#">Projects</a></li> href="#">Contact</a></li>

With HTML 5, we can already see some of the new elements. Our Header section is now wrapped by the <header> tag which encloses our usual <h1> header title and also another new member, a <nav> element which encloses our upper navigation menu. Probably the nicest thing here, aside from the semantic meaning, is that we can do away with a ton of
<div> elements using class attributes to tell us what they represent.

HTML 5 provides some definitions for these new elements: header The header element represents a group of introductory or navigational aids. A header element is intended to usually contain the section's heading (an h1h6 element or an hgroup element), but this is not required. The header element can also be used to wrap a section's table of contents, a search form, or any relevant logos. nav The nav element represents a section of a page that links to other pages or to parts within the page: a section with navigation links. Not all groups of links on a page need to be in a nav element only sections that consist of major navigation blocks are appropriate for the nav element. In particular, it is common for footers to have a list of links to various key parts of a site, but the footer element is more appropriate in such cases, and no nav element is necessary for those links. Next up, we need the main body of the markup showing where we would render our blog entries, followed by the final footer section containing a copyright and credits where necessary.
q q q q q q q

q q q q q q q q q q q q q

<!DOCTYPE html> <html> <head> <meta name="language" content="en" /> <meta charset="utf-8"/> <title>Pdraic Brady's Blog</title> <link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/resetfonts-grids.css" media="screen" rel="stylesheet" type="text/css" /> <link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" /> <!--[if IE]><script type="text/javascript" src="http://html5shiv.googlecode. com/svn/trunk/html5.js"></script><![endif]--> </head> <body> <header> <h1><a href="/">Pdraic Brady's Blog</a></h1> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Projects</a></li> <li><a href="#">Contact</a></li> </ul>

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (13 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q q

</nav> </header> <section> <article> <header> <h2>One Day...Revisited</h2> <p>Posted on <time datetime="2009-09-07">Tuesday, 08 September 2009</time></p> </header> <div> <p>I forgot!</p> <p>I already am writing a book!</p> </div> <footer> <address>By Pdraic</address> </footer> </article> <article> <header> <h2>One Day...</h2> <p>Posted on <time datetime="2009-09-07">Monday, 07 September 2009</time></p> </header> <div> <p>I will write a book</p> </div> <footer> <address>By Pdraic</address> </footer> </article> </section> <footer> <p>Copyright &copy; 2009 Pdraic Brady</p> <p>Powered by <a href="http://www.survivethedeepend.com">ZFBlog </a></p> </footer> </body> </html>

q q q q q q q q q q q q q q

q q q q q q q q q q q q q q q q q q

Well, this is new! HTML 5 has added more new elements and this is startlingly obvious now. Many slot into places where we would expect to place <div> tags with a class attribute referring to the purpose of the <div>. In a typical barebones page, you might expect to simple enclose the content in <article> tags since the content is a single item. In our blog, however, we know each entry rendered on the page is a self-contained article so we use multiple <article> elements and wrap all of them in a single <section> element (there can also be many <section> elements if the page is divided further, and they can be nested - not unlike Docbook XML). Each article will also have a header and footer, and so we use the HTML 5 tags relevant for these cases. Finally, any reference to an author's details is enclosed within <address> tags. A bit of a confusing point, the <address> tag does not relate to a physical address - it's more relevant for a person's other details like their name, website, email, description, etc. Let's look at the definitions from the HTML 5 specification for these new elements in HTML 5. section The section element represents a generic document or application section. A section, in this context, is a thematic grouping of content, typically with a heading, possibly with a footer.

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (14 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

article The article element represents a section of a page that consists of a composition that forms an independent part of a document, page, application, or site. This could be a forum post, a magazine or newspaper article, a Web log entry, a user-submitted comment, an interactive widget, or any other independent item of content. An article element is "independent" in the sense that its contents could stand alone, for example in syndication, or as a interchangeable component on a user-configurable portal page. footer The footer element represents a footer for its nearest ancestor sectioning content. A footer typically contains information about its section such as who wrote it, links to related documents, copyright data, and the like. Contact information belongs in an address element, possibly itself inside a footer. address The address element represents the contact information for its nearest article or body element ancestor. If that is the body element, then the contact information applies to the document as a whole. The address element must not be used to represent arbitrary addresses (e.g. postal addresses), unless those addresses are in fact the relevant contact information. time The time element represents either a time on a 24 hour clock, or a precise date in the proleptic Gregorian calendar, optionally with a time and a time-zone offset. This element is intended as a way to encode modern dates and times in a machine-readable way so that user agents can offer to add them to the user's calendar. For example, adding birthday reminders or scheduling events. On a brief note, the new <time> element is not without its problems. It's tied strictly to the Gregorian calendar so it cannot represent non-Gregorian dates from before its introduction. Since <time> was intended to replace the microformat <abbr> element in hCalendar, it's actually a failure since it can't represent historical dates. Hopefully HTML 5's final recommendation will fix this. Open your browser and navigate to http://zfblog.tld. You should see the new template rendered with all the default YUI CSS styling applied.

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (15 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Initial Blog Index Page

10.5. Extracting Static Markup Into A Layout


Now that we have an initial design, we need to identify what parts of this template are static, i.e. that will rarely change from page to page. A quick assessment is enough to see that for our index page, only the entries would be expected to change. We could have a variable number to display and their content will be updated regularly. Armed with this, we can reduce our index template to only include the parts that are dynamic - we'll encapsulate the rest momentarily into a layout. Here's the revised template /application/views/scripts/index/index.phtml.
q q q

<article> <header> <h2>One Day...Revisited</h2>


http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (16 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q

</header> <div> <p>I forgot!</p> <p>I already am writing a book!</p> </div> <footer> <address>By Pdraic</address> </footer> </article> <article> <header> <h2>One Day...</h2> </header> <div> <p>I will write a book</p> </div> <footer> <address>By Pdraic</address> </footer> </article>

Let's push the leftover markup into a new template, referred to as a Layout, in /application/views/layouts/default.
phtml.
q q q q q q q

q q q q q q q q q q q q q q q q q q q q q q q q q q q q

<!DOCTYPE html> <html> <head> <meta name="language" content="en" /> <meta charset="utf-8"/> <title>Pdraic Brady's Blog</title> <link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/resetfonts-grids.css" media="screen" rel="stylesheet" type="text/css" /> <link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" /> <!--[if IE]><script type="text/javascript" src="http://html5shiv.googlecode. com/svn/trunk/html5.js"></script><![endif]--> </head> <body> <header> <h1><a href="/">Pdraic Brady's Blog</a></h1> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Projects</a></li> <li><a href="#">Contact</a></li> </ul> </nav> </header> <section> <?php echo $this->layout()->content ?> </section> <footer> <p>Copyright 2009 Pdraic Brady</p> <p>Powered by <a href="http://www.survivethedeepend.com">ZFBlog </a></p> </footer> </body> </html>

You'll notice that we are now referencing a Zend_View method layout(). As we noted when discussing View Helpers, a View Helper's class name reflects its primary method which is typically used, at a minimum, to retrieve an instance of the View Helper object. In this case, we are retrieving an instance of Zend_View_Helper_Layout and printing whatever is
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (17 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

contained in its public $content property. To understand how our index template is pushed into the property, we need to understand that all rendering is, by default, handled by Controllers using the ViewRenderer Action Helper. This helper will render a template for the current Controller and Action into a response. Zend_Layout can then intercept the rendered response body and inject it in the layout template. Then the entire layout is rendered in turn to finally produce the entire page. You should also note that Layouts, like all templates, are executed within the variable scope of a Zend_View object, so all View Helpers and Zend_View methods are still accessible from your Layouts. To enable Zend_Layout, we also need to configure it for use. Typically this requires a call to the static method
Zend_Layout::startMvc() following any configuration of the instance, however since we are using Zend_Application for

our bootstrapping we simply need to define some configuration values for a Layout Resource in /config/application.
ini. The two options needed, and added at the bottom of our "Standard Resource Options" section set the name of the

layout template to use by default omitting the .phtml file extension. The second configures the path at which Layout templates can be located.
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

[production] ; PHP INI Settings phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 ; Bootstrap Location bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" ; Standard Resource Options resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.modifiedFrontController.contentType = "text/html;charset=utf-8" resources.layout.layout = "default" resources.layout.layoutPath = APPLICATION_PATH "/views/layouts" ; HTML Markup Options resources.view.doctype = "HTML5" ; Autoloader Options autoloaderNamespaces[] = "ZFExt_" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1

Returning to your browser, navigate to http://zfblog.tld and you should the exact same page as before with no differences. Our index template is now being rendered into the new Layout. To complete the picture, let's also take this opportunity to edit the second page template in our application at /application/
views/scripts/error/error.phtml. You'll notice that this is not enclosed in <article> tags since it's a generic message

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (18 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

and not a standalone document article. I've also switched to using a second level header <h2> enclosed by a <header> element for the <article>.
q q q q q q q q q q q q q q q q q

<header> <h2>An Error Has Occurred</h2> </header> <div> <?php if ($this->statusCode == 500): ?> <p>We're truly sorry, but we cannot complete your request at this time.</p> <p>If it's any consolation, we have scheduled a new appointment for our development team leader in Torture Chamber #7 to encourage his colleagues to investigate this incident.</p> <?php else: ?> <p>We're truly sorry, but the page you are trying to locate does not exist.</p> <p>Please double check the requested URL or consult your local Optometrist.</p> <?php endif; ?> </div>

Go ahead and give it a test run, using an invalid URL like http://zfblog.tld/foo/bar. Remember that you will need to temporarily disable the throwing of Exceptions in application.ini to see the error page.

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (19 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Initial Blog Error Page

10.6. Replacing Changeable Elements With Placeholders


Our Layout is now in place, however we can go further and ensure that page level templates, or even partials, can actually modify some parts of the Layout on demand, specifically in the area of stylesheets, page titles, javascript and meta tags. The Zend Framework can allow for such changes through the use of Placeholders, specifically a number of concrete implementations like Zend_View_Helper_HeadTitle, Zend_View_Helper_Doctype, etc. As mentioned earlier, Placeholders allow templates to add, remove, or even replace items within a self-contained data container, the contents of which are rendered into their final form either in an outer layer of the template hierarchy. Let's start by modifying our Layout at /application/views/layouts/default.phtml to use several of these Placeholder View Helpers.
q q q q q q q q q

q q q q q q q q q q q q q q q q q q q q q q q q q q q q

<?php echo $this->doctype() . "\n" ?> <html> <head> <?php echo $this->headMeta()->setIndent(' ') . "\n"; echo $this->headTitle('Pdraic Brady\'s Blog') ->setSeparator(' / ')->setIndent(' ') . "\n"; echo $this->headLink()->setIndent(' ') ->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/ reset-fonts-grids.css') ->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/base/base-min. css') . "\n "; echo $this->headScript()->prependFile( 'http://html5shiv.googlecode.com/svn/trunk/html5.js', 'text/javascript', array('conditional'=>'IE')) . "\n"; ?> </head> <body> <header> <h1><a href="/">Pdraic Brady's Blog</a></h1> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Projects</a></li> <li><a href="#">Contact</a></li> </ul> </nav> </header> <section> <?php echo $this->layout()->content ?> </section> <footer> <p>Copyright 2009 Pdraic Brady</p> <p>Powered by <a href="http://www.survivethedeepend.com">ZFBlog </a></p>

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (20 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q

</footer> </body> </html>

Zend_View_Helper_Doctype and Zend_View_Helper_HeadMeta are both echoed unchanged, except to add some standard

indentation and newlines where appropriate. This merely ensures the markup source is easier to read. Rather than set any values within the Layout for these two, we will instead configure them from our bootstrap using options from application.
ini. Zend_View_Helper_HeadTitle is called to set a Title part "Pdraic Brady's Blog". You can set additional parts and they will

be rendered within a <title> element separated by any separator you choose to set using
Zend_View_Helper_HeadTitle::setSeparator(). The optional second argument to the primary method headTitle() can

be set to "APPEND", the default, or "PREPEND" to ensure a title part is rendered at the start of the Title.
Zend_View_Helper_HeadLink can be used to append, prepend or replace stylesheet links. It also works for other generic

links by passing an array of attributes and values to Zend_View_Helper_HeadLink::headLink(). Alternate links can be set using appendAlternate() and its siblings (prepend/set/offsetSetAlternate()). When adding stylesheets the optional parameters include setting the media and type attributes, and also setting an array of conditionals (such as we use in the next line for including a javascript file only for Internet Explorer). If you need to add a block of styles, this can be accomplished using Zend_View_Helper_Style instead. Finally, Zend_View_Helper_Script allows for the inclusion of scripts including javascript files. You can note some of the similarities across these concrete Placeholder implementations. They have methods to set, append, and prepend items. Many also offer offsetSet*() methods to set items at a particular point in the array of items to render. One piece of markup I've omitted is the charset <meta> tag. This cannot currently be rendered by the Zend Framework, but we'll solve this in the next section. For now, let's make some changes to our ZFExt_Bootstrap::_initView() method to configure the meta tags and Doctype to be rendered.
q q q q q q q q q q q q q q q q q q q q q q q q

<?php class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap { // ... protected function _initView() { $options = $this->getOptions(); $config = $options['resources']['view']; if (isset($config)) { $view = new Zend_View($config); } else { $view = new Zend_View; } if (isset($config['doctype'])) { $view->doctype($config['doctype']); } if (isset($config['language'])) { $view->headMeta()->appendName('language', $config['language']); } $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer'

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (21 de 38)28/10/2011 12:52:04

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q q

); $viewRenderer->setView($view); return $view; } // ... }

We can now modify application.ini to add at least one piece of meta information, the page's language. This is not a standard Zend_View configuration value, just a default we impose from our bootstrap class when setting meta information for the page.
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

[production] ; PHP INI Settings phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 ; Bootstrap Location bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" ; Standard Resource Options resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.modifiedFrontController.contentType = "text/html;charset=utf-8" resources.layout.layout = "default" resources.layout.layoutPath = APPLICATION_PATH "/views/layouts" ; Autoloader Options autoloaderNamespaces[] = "ZFExt_" ; HTML Markup Options resources.view.doctype = "HTML5" resources.view.language = "en" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1

10.7. Improving HTML 5 Support With Custom View Helpers


For the moment at least, Zend Framework support for HTML 5 does not extend beyond offering an HTML Doctype. The Doctype used is actually important, not simply because it renders at the top of our Layout but because it determines how tags are constructed by a number of View Helpers. As I said before, I prefer an XHTML style approach to HTML 5 with closed tags, lowercase attribute names, and attribute values enclosed in quotes, among other facets. HTML 5 itself is relatively agnostic to these details, indeed it defines an XML serialisation as an alternate to the typical HTML variant. With our Doctype set to "HTML5" this is problematic because when testing for XHTML, View Helpers check whether the Doctype name starts with "XHTML". HTML5 (our current Doctype option value) does not. To resolve this we should add a custom implementation of the standard Zend_View_Helper_Doctype (a simple subclass is all that's needed) to add support for an option called "XHTML5" which will ensure XHTML rules are applied to any output from View Helpers where they apply.
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (22 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

We'll store this custom View Helper at /library/ZFExt/View/Helper/Doctype.php. In order for our application to tell
Zend_View where to find these custom helpers, we can define some new application.ini options in our "Standard Resource

Options" section. These will provide a new path where custom View Helpers can be found, and the class prefix they use. We need no additional changes thereafter - custom View Helpers which reflect the name of any Zend Framework View Helper (i.e. they end with the same camel cased term) override the framework's helpers. So calling Zend_View::doctype() would now first check for a custom View Helper of that name.
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

[production] ; PHP INI Settings phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 ; Bootstrap Location bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" ; Standard Resource Options resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.modifiedFrontController.contentType = "text/html;charset=utf-8" resources.layout.layout = "default" resources.layout.layoutPath = APPLICATION_PATH "/views/layouts" resources.view.helperPath = "ZFExt/View/Helper/" resources.view.helperPathPrefix = "ZFExt_View_Helper_" ; Autoloader Options autoloaderNamespaces[] = "ZFExt_" ; HTML Markup Options resources.view.doctype = "HTML5" resources.view.language = "en" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1

Implementing this new View Helper has a single goal - to allow for a new Doctype of XHTML5 which outputs the standard HTML5 doctype is echoed in a template. Any retrieval of the Doctype should also return the correct value which passes the XHTML test used by other View Helpers. If you followed the setting up of a new AllTests.php file for the domain model tests earlier, you can repeat that process for any custom View Helpers we intend adding. Here are our unit tests for such an implementation stored at /tests/ZFExt/View/Helper/DoctypeTest.php.
q q q q q q q q q q

<?php class ZFExt_View_Helper_DoctypeTest extends PHPUnit_Framework_TestCase { protected $helper = null; public function setup() { $this->helper = new ZFExt_View_Helper_Doctype;
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (23 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q

} public function testRendersHtml5DoctypeForXhtmlSerialisation() { $this->helper->doctype('XHTML5'); $this->assertEquals('<!DOCTYPE html>', (string) $this->helper); } public function testReturnsXhtmlDoctypeName() { $this->helper->doctype('XHTML5'); $this->assertEquals('XHTML5', $this->helper->getDoctype()); } }

Implementing this requires a simple subclass at /library/ZFExt/View/Helper/Doctype.php.


q q q q q q q q q q q q q q q q q q q q q q q

<?php class ZFExt_View_Helper_Doctype extends Zend_View_Helper_Doctype { const XHTML5 = 'XHTML5'; public function __construct() { parent::__construct(); $this->_registry['doctypes'][self::XHTML5] = '<!DOCTYPE html>'; } public function doctype($doctype = null) { if (null !== $doctype && $doctype == self::XHTML5) { $this->setDoctype($doctype); } else { parent::doctype($doctype); } return $this; } }

As you can see from the class, except for any specific handling of XHTML5, we just hand control back to the parent class for all other possible Doctypes. Another facet of HTML 5 support we can tackle is the new charset attribute that be used in a <meta> tag. Effectively this is used to replace:
q

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

With a shorter form which doesn't assert a Content-Type. Of course, you can still add the Content-Type if you need to but the web application should be served under the correct type anyway.
q

<meta charset="utf-8"/>

Unfortunately Zend_View_Helper_HeadMeta, the View Helper responsible for allowing templates and Layouts to add meta information does not recognise the charset attribute as valid. We can persuade it otherwise by adding another custom View Helper, again overriding the original class. Once more we'll subclass the original to minimise the code we need to write. Here are the relevant unit tests stored at /tests/ZFExt/View/Helper/HeadMetaTest.php.

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (24 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q

<?php class ZFExt_View_Helper_HeadMetaTest extends PHPUnit_Framework_TestCase { protected $helper = null; protected $view = null; public function setup() { foreach (array(Zend_View_Helper_Placeholder_Registry::REGISTRY_KEY, 'Zend_View_Helper_Doctype') as $key) { if (Zend_Registry::isRegistered($key)) { $registry = Zend_Registry::getInstance(); unset($registry[$key]); } } /** * This custom helper only concerns (X)HTML 5 support * using the ZFExt doctype helper for the XHTML flavour */ $this->view = new Zend_View(); $this->view->addHelperPath('ZFExt/View/Helper', 'ZFExt_View_Helper'); $this->helper = new ZFExt_View_Helper_HeadMeta(); $this->helper->setView($this->view); } public function testRendersHtml5CharsetMetaElement() { $this->view->doctype('HTML5'); $this->helper->setCharset('utf-8'); $this->assertEquals('<meta charset="utf-8">', (string) $this->helper); } public function testRendersXhtml5CharsetMetaElement() { $this->view->doctype('XHTML5'); $this->helper->setCharset('utf-8'); $this->assertEquals('<meta charset="utf-8"/>', (string) $this->helper); } }

q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

The implementation is a little more involved than usual since we are adding support for a whole other <meta> tag type not present previously. The new class is written to /library/ZFExt/View/Helper/HeadMeta.php.
q q q q q q q q q q q q q q q q q q q q q q

<?php class ZFExt_View_Helper_HeadMeta extends Zend_View_Helper_HeadMeta { protected $_typeKeys = array('name', 'http-equiv', 'charset'); public function setCharset($charset) { $item = new stdClass; $item->type = 'charset'; $item->charset = $charset; $item->content = null; $item->modifiers = array(); $this->set($item); return $this; } protected function _isValid($item) { if ((!$item instanceof stdClass) || !isset($item->type)

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (25 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q

|| !isset($item->modifiers)) { return false; } $doctype = $this->view->doctype()->getDoctype(); if (!isset($item->content) && (($doctype !== 'HTML5' && $doctype !== 'XHTML5') || (($doctype == 'HTML5' || $doctype == 'XHTML5') && $item->type !== 'charset')) ) { return false; } return true; } public function itemToString(stdClass $item) { if (!in_array($item->type, $this->_typeKeys)) { require_once 'Zend/View/Exception.php'; throw new Zend_View_Exception(sprintf('Invalid type "%s" provided for meta', $item->type)); } $type = $item->type; $modifiersString = ''; foreach ($item->modifiers as $key => $value) { if (!in_array($key, $this->_modifierKeys)) { continue; } $modifiersString .= $key . '="' . $this->_escape($value) . '" '; } if ($this->view instanceof Zend_View_Abstract) { if ($this->view->doctype()->getDoctype() == 'XHTML5' && $type == 'charset') { $tpl = '<meta %s="%s"/>'; } elseif ($this->view->doctype()->getDoctype() == 'HTML5' && $type == 'charset') { $tpl = '<meta %s="%s">'; } elseif ($this->view->doctype()->isXhtml()) { $tpl = '<meta %s="%s" content="%s" %s/>'; } else { $tpl = '<meta %s="%s" content="%s" %s>'; } } else { $tpl = '<meta %s="%s" content="%s" %s/>'; } $meta = sprintf( $tpl, $type, $this->_escape($item->$type), $this->_escape($item->content), $modifiersString ); return $meta; } }

q q q q q q q q q q q q

q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

Don't worry if this all looks incomprehensible! We'll cover some application specific View Helpers in later chapters but, for the moment, we just need these two custom helpers to ensure we have fuller HTML 5 support at our fingertips. In the class above we're replacing two original methods: _isValid() and itemToString(). The first validates the details of some meta information we want to render in a <meta> tag. The data is stored as an instance of stdClass, i.e. it's a simple

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (26 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

data container that could just as easily have been an array. The main validation addition I made was to allow a new meta type of "charset" if the Doctype was one of the HTML 5 types. In the itemToString() method I added the capability to compose this new meta type into a <meta> tag, closing the tag if the Doctype used was XHTML5. I also added a new method, setCharset() so creating this new tag is differentiated from the original two meta types supported, i.e. simple name/value types and http-equiv types. With our new HTML 5 support completed, let's revisit application.ini and add a new HTML Markup Option called "charset" along with the new Doctype. Really, we could just use the Zend_View setting for character encoding but I decided to keep them separate so we could conditionally add the new meta tag type only if its specific option was set.
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

[production] ; PHP INI Settings phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 ; Bootstrap Location bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" ; Standard Resource Options resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.view.helperPath = "ZFExt/View/Helper/" resources.view.helperPathPrefix = "ZFExt_View_Helper_" resources.modifiedFrontController.contentType = "text/html;charset=utf-8" resources.layout.layout = "default" resources.layout.layoutPath = APPLICATION_PATH "/views/layouts" ; Autoloader Options autoloaderNamespaces[] = "ZFExt_" ; HTML Markup Options resources.view.charset = "utf-8" resources.view.doctype = "XHTML5" resources.view.language = "en" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1

We can now also amend the bootstrap class _initView() method to follow up and use the new charset option.
q q q q q q q q q q q

<?php class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap { // ... protected function _initView() { $options = $this->getOptions(); $config = $options['resources']['view'];
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (27 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q

if (isset($config)) { $view = new Zend_View($config); } else { $view = new Zend_View; } if (isset($config['doctype'])) { $view->doctype($config['doctype']); } if (isset($config['language'])) { $view->headMeta()->appendName('language', $config['language']); } if (isset($config['charset'])) { $view->headMeta()->setCharset($config['charset'], 'charset'); } $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' ); $viewRenderer->setView($view); return $view; } // ... }

Go ahead now and reload the blog's index page. If you check the source code, it should now be completely in line with the original markup we introduced at the start of the chapter.

10.8. Adding A Link To A Custom Stylesheet


Our use of the YUI CSS framework has supplied us with a baseline style for the blog which is very basic but agreeable across all major browsers. We can supplement this with our own customised styles to provide the blog with a more well rounded look. To achieve this we need to make a change so that our Layout template also includes a style.css file stored at /public/css/
style.css. Into this file we can place additional CSS rules. Here's the revised <head> section of the altered layout.
q q q q q q q q q

q q q q q q q q

<?php echo $this->doctype() . "\n" ?> <html> <head> <?php echo $this->headMeta()->setIndent(' ') . "\n"; echo $this->headTitle('Pdraic Brady\'s Blog') ->setSeparator(' / ')->setIndent(' ') . "\n"; echo $this->headLink()->setIndent(' ') ->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/ reset-fonts-grids.css') ->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/base/base-min.css') ->appendStylesheet('/css/style.css') . "\n "; echo $this->headScript()->prependFile( 'http://html5shiv.googlecode.com/svn/trunk/html5.js', 'text/javascript', array('conditional'=>'IE')) . "\n"; ?> </head>

Bearing in mind that we may take the Yahoo Performance Best Practices into consideration, let's formally add our previous View Helper example into the mix with a few changes. Here's the actual unit tests utilised by the custom helper
ZFExt_View_Helper_IncludeModifiedDate. They utilise an empty style.css file you can put at /tests/ZFExt/View/ Helper/_files/style.css to get a modified date. The tests are stored at /tests/ZFExt/View/Helper/ IncludeModifiedDateTest.php. As with all these tests, add them to the closest AllTests.php file to run from PHPUnit.

The only tricky part is managing the working directory PHP is currently using so a relative URI makes sense in the test and

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (28 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

hits our style.css file correctly.


q q q

<?php class ZFExt_View_Helper_IncludeModifiedDateTest extends PHPUnit_Framework_TestCase { protected $helper = null; private $cwd = null; public function setup() { $this->helper = new ZFExt_View_Helper_IncludeModifiedDate; $this->cwd = getcwd(); chdir(dirname(__FILE__)); } public function teardown() { chdir($this->cwd); } public function testAddsTimestampToFilenameBeforeFileExtension() { $file = '/_files/style.css'; $timestamped = $this->helper->includeModifiedDate($file); $this->assertTrue((bool) preg_match("/^\/_files\/style\.\d{10,}\.css\ $/", $timestamped)); } public function testAddsTimestampToFilenameBeforeFileExtensionWithUriQueryString() { $file = '/_files/style.css?version=2.0'; $timestamped = $this->helper->includeModifiedDate($file); $this->assertTrue((bool) preg_match("/^\/_files\/style\.\d{10,}\.css\? version=2\.0$/", $timestamped)); } }

q q q q q q q q q q q q q q q q q q q q q q q

q q q

q q q q

q q q

As you can probably notice, we've beefed up the helper to handle an additional case where a file URI may already include a query string of some type already. This time it also adds the modification timestamp within the filename instead of the query string. This handles a scenario where many proxies ignore the query string when caching files, so changing the filename or path is far more effective. Of course we do not physically change the filename - instead we'll add a new rewrite rule to /
public/.htaccess so any URIs to physical files of the form /name.timestamp.extension (e.g. /style.1252108229.css)

are remapped to the actual filename correctly. Here's the implementation stored at /library/ZFExt/View/Helper/
IncludeModifiedDate.php.
q q q q q q q q q q q q q q

<?php class ZFExt_View_Helper_IncludeModifiedDate extends Zend_View_Helper_Abstract { public function includeModifiedDate($uri) { $parts = parse_url($uri); $root = getcwd(); $mtime = filemtime($root . $parts['path']); return preg_replace( "/(\.[a-z0-9]+)(\?*.*)$/", '.'.$mtime.'$1$2', $uri );

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (29 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q

} }

The following change adds support for this style of timestamping files to /public/.htaccess, so we do load the underlying file which doesn't have a timestamp included in its name. The rewrite rule merely removes the timestamp part of the incoming filename. You can add in additional file extensions or directories this applies to as needed. I've assumed some reasonable common defaults.

SetEnv APPLICATION_ENV development RewriteEngine On RewriteRule ^(scripts|css|images)/(.+)\.(.+)\.(js|css|jpg|gif|png)$ $1/ $2.$4 [L] RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^.*$ - [NC,L] RewriteRule ^.*$ /index.php [NC,L]
Our layout template can now utilise the new custom helper as follows.
q q q q q q q q q

q q q q q q q q

<?php echo $this->doctype() . "\n" ?> <html> <head> <?php echo $this->headMeta()->setIndent(' ') . "\n"; echo $this->headTitle('Pdraic Brady\'s Blog') ->setSeparator(' / ')->setIndent(' ') . "\n"; echo $this->headLink()->setIndent(' ') ->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/ reset-fonts-grids.css') ->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/base/base-min.css') ->appendStylesheet($this->includeModifiedDate('/css/style.css')) . "\n "; echo $this->headScript()->prependFile( 'http://html5shiv.googlecode.com/svn/trunk/html5.js', 'text/javascript', array('conditional'=>'IE')) . "\n"; ?> </head>

We won't use the View Helper with any of the YUI CSS URIs since they already have a built in reference to a version number. We can change the URIs manually as new version of the CSS framework are released, all from the layout template, without necessitating any other changes. Keeping track of our layout markup, here's what the browser output source looks like at this point for our <head> section:
q q q q q q q

<!DOCTYPE html> <html> <head> <meta name="language" content="en" /> <meta charset="utf-8"/> <title>Pdraic Bradys Blog</title> <link href="http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/resetfonts-grids.css" media="screen" rel="stylesheet" type="text/css" /> <link href="http://yui.yahooapis.com/2.7.0/build/base/base-min.css" media="screen" rel="stylesheet" type="text/css" />
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (30 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q

<link href="/css/style.1252108229.css" media="screen" rel="stylesheet" type="text/css" /> <!--[if IE]> <script type="text/javascript" src="http://html5shiv. googlecode.com/svn/trunk/html5.js"></script><![endif]--> </head>

As you can see, the style.css URI now has a modified date attached in the query string as we would expect. If you were wondering how to accomplish the use of the Expires and Cache-Control headers in practice, it's usually done from your Virtual Host configuration. You will need to enable the Apache mod_expires Module in your web server's configuration beforehand. Here's a sample configuration of the blog's Virtual Host implementing an Expires header of six months from the first time a client accesses a static file. I've added two rules to illustrate the options - setting by matching files to a regular expression or by explicitly referring to the type the header should be applied to. The rules are embedded in a conditional so they are ignored if the required Module is not loaded in Apache. It is also essential to remember that unless you use a modified date/ hash query string for a resource's URI, the client will cache these resources for six months and never retrieve updates during that time unless they lose their cache or force a complete reload (e.g. using CTRL+SHIFT+R in Firefox).

<VirtualHost *:80> ServerName zfblog.tld DocumentRoot /home/padraic/projects/zfblog/public <Directory "/home/padraic/projects/zfblog/public"> Options Indexes MultiViews FollowSymLinks AllowOverride all Order deny,allow Allow from all <IfModule mod_expires.c> ExpiresActive on <FilesMatch "\.(ico|jpg|jpeg|png|gif)$"> ExpiresDefault "access plus 6 months" </FilesMatch> ExpiresByType text/css "access plus 1 year" ExpiresByType text/javascript "access plus 1 year" </IfModule> </Directory> </VirtualHost>
Utilising this configuration will cause all the matching file types to be sent to a client with two new headers. For example:

Cache-Control: max-age=31536000 Expires: Sun, 05 Sep 2010 00:39:27 GMT


Expires indicates a date six months away on which the file should be deemed as expired and requested once again from

the server, and the second, Cache-Control, indicates the maximum life of the resource in seconds. At a technical level, the
Cache-Control header is the most important since in most cases it actually overrides the Expires header. You can also set

this up from your .htaccess file using the same set of rules.

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (31 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

10.9. Customising The Style


Before we hit the custom style sheet, let's first make use of what we've already include with the Yahoo User Interface CSS. The following set of changes to our default layout template at /application/views/layouts/default.phtml will add a typical website layout with header, footer, main body and a left handed column for any additional navigation links or complementary content.
q q q q q q q q q

q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

<?php echo $this->doctype() . "\n" ?> <html> <head> <?php echo $this->headMeta()->setIndent(' ') . "\n"; echo $this->headTitle('Pdraic Brady\'s Blog') ->setSeparator(' / ')->setIndent(' ') . "\n"; echo $this->headLink()->setIndent(' ') ->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/reset-fonts-grids/ reset-fonts-grids.css') ->appendStylesheet('http://yui.yahooapis.com/2.7.0/build/base/base-min.css') ->appendStylesheet($this->includeModifiedDate('/css/style.css')) . "\n "; echo $this->headScript()->prependFile( 'http://html5shiv.googlecode.com/svn/trunk/html5.js', 'text/javascript', array('conditional'=>'IE')) . "\n"; ?> </head> <body> <div id="doc" class="yui-t1"> <header id="hd" role="banner"> <hgroup> <h1><a href="/">Pdraic Brady's Blog</a></h1> <h2>Musings On PHP And Zend Framework</h2> </hgroup> <nav role="navigation"> <ul> <li><a href="/">Home</a></li> <li><a href="#">About</a></li> <li><a href="#">Projects</a></li> <li><a href="#">Contact</a></li> </ul> </nav> </header> <div id="bd"> <div id="yui-main"> <div class="yui-b" role="main"> <section id ="content" class="yui-g"> <?php echo $this->layout()->content ?> </section> </div> </div> <section class="yui-b"> <nav role="navigation" aria-labelledby="leftnav-label"> <h2 id="leftnav-label">External Links</h2> <ul> <li><a href="#">Survive The Deep End</a></li> <li><a href="#">Zend Framework</a></li> <li><a href="#">Planet-PHP</a></li> <li><a href="#">I'm on Twitter!</a></li> </ul> </nav> </section> </div> <footer id="ft" role="contentinfo">

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (32 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End
q q q q q q q q

<p>Copyright 2009 Pdraic Brady</p> <p>Powered by <a href="http://www.survivethedeepend.com">ZFBlog </a></p> </footer> </div> </body> </html>

Since we are using HTML 5, much of the new markup uses the new elements except where they are very clearly needed for the sole purpose of applying the grid layout. For these cases, I've used the normal <div> element. This maintains a logical set of elements where there are only two distinct <section> elements. One for the main content, the other for the left hand sidebar I've introduced. <div> is thus relegated to the boring category of adding CSS "hooks" to the markup for styling - not exactly the cleanest approach but I'm using a CSS Framework so I can live with that. I've also added roles to a number of elements in accordance with another new standard besides HTML 5, Accessible Rich
Internet Applications Suite (WAIARIA 1.0), which defines a way to try and make web content and applications more

accessible to people with disabilities. It's a small gesture to make while we're adopting HTML 5 and creating a layout from scratch. The standard defines a set of "document landmark roles" which in theory would allow a person using the right software to cut through the web of markup to the parts of the HTML document they want. The roles are therefore very intuitive: banner, main, navigation, contentinfo, complementary, search, etc. There are a lot of these but I'm only using the bare minimum. As an added incentive landmark roles are currently supported by the JAWS 10 screen reader. In adding a subtitle to the blog, I added a new <hgroup> element. This element groups together related headers and is defined in the HTML 5 specification as follows. hgroup The hgroup element is typically used to group a set of one or more h1-h6 elements to group, for example, a section title and an accompanying subtitle. Reload the URI http://zfblog.tld in your browser to see what impact this all has.

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (33 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Basic Blog Index Style

The updated design is beginning, slowly, to look like something we could endure if we possessed endless patience. Let's improve it a bit more, this time by editing /public/css/style.css.

/** * Basic Elements */ body { font-family: Geneva, Verdana, Helvetica, sans-serif; } a:link, a:visited { color: blue; text-decoration: none; } a:hover {
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (34 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

color: red; text-decoration: underline; } /** * HTML 5 Block Display * * Required by most, if not all, browsers until support is added. */ article, aside, dialog, figure, footer, header, hgroup, nav, section { display:block; } /** * Page Elements */ header#hd { text-align:center; } footer#ft { text-align: center; margin-top: 25px; border-top: 1px solid lightgray; border-bottom: 1px solid lightgray; } section#content { border-left: 3px double lightgray; padding-left: 10px; } /** * Headers */ h1, h2, h3, h4, h5, h6 { Helvetica,Arial,Calibri,sans-serif; } header#hd h1 { font-size: 200%; margin-bottom: 0; } header#hd h2 { margin-top: 0.4em; font-size: 100%; } /** * Horizontal Header Navigation Menu */ header#hd nav { border-top: 2px solid #000; border-bottom: 2px solid #000; } header#hd nav ul { list-style: none; margin: 0 0 0 20px; text-align: left; } header#hd nav li { display: inline-block; min-width: 50px; margin: 0 2px 0 2px; } header#hd nav a:link, nav a:visited {
http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (35 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

color: blue; display: inline-block; height: 20px; padding: 5px 1.5em; text-decoration: none; } header#hd nav a:hover { background-color: lightgray; } /** * Vertical Sidebar Menu/Links */ section nav h2 { font-size: 120%; } section nav ul { list-style: none; width: 100%; margin: 0 auto; } section nav ul li { float: left; display: inline; margin: 0; } /** * Article related styling */ article header { margin-bottom: 1em; } article header h2 { border-bottom: 1px dashed gray; font-size: 130%; color: green; margin-bottom: 0.5em; } article header p { font-style: italic; }
Now we have a very basic but acceptable style to work with for now.

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (36 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Final Blog Index Style

10.10. Conclusion
In this chapter we've covered the basics of creating a simple web design and migrating it to the Zend Framework templating system using Zend_View, Zend_Layout and a number of View Helpers. We'll cover the creation of custom View Helpers in a lot more detail in the chapters to come. We've also covered some of the basics of HTML 5, the upcoming standards update for HTML, and the impact it currently has during implementation on requiring the use of some custom helpers. I have no doubt HTML 5 support in the Zend Framework will materialise in the very near future however, so stay tuned! In Chapter 11, we'll move on towards the next basic set of functionality. We'll need to display blog entries from our database and, of course, have a means of writing them in the first place! So we will be looking at using our pre-designed domain model and accepting writing new blog entries using forms generated using Zend_Form.

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (37 de 38)28/10/2011 12:52:05

Chapter 10. Setting The Design With Zend_View, Zend_Layout, HTML 5 ...ser Interface Library - Zend Framework Book: Surviving The Deep End

Prev Chapter 9. Implementing The Domain Model: Entries and Authors Home

Next Appendix A. Creating A Local Domain Using Apache Virtual Hosts

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1...iew.zend.layout.html.5.and.yahoo.user.interface.library (38 de 38)28/10/2011 12:52:05

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 3. The Model


Table of Contents 3.1. Introduction 3.2. Clarifying The Model 3.3. In Programming, Fat Models Are Preferable To Size Zero Models 3.4. The Fat Stupid Ugly Controller 3.5. Controllers Are Not The Data Police 3.6. Conclusion

3.1. Introduction
In Chapter 2, we introduced the Model-View-Controller (MVC) design pattern and explored how it is composed, and why it is beneficial to web applications. I noted back then that the most difficult

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (1 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

concept to grasp is that of the Model. There are numerous interpretations of the Model but for many programmers the Model is equated with data access, a misconception most frameworks inadvertently promote by not obviously acknowledging that they do not provide full Models. In our buzzword inundated community, many frameworks leave the definition of the Model unclear and obscured in their documentation. In reality, a Model is a representation of one part of the Domain Model. The Domain Model is comprised of many connected Model objects which represent a system's data and behaviour. It's completely independent of the Controller and View. Talking about Domain Models and systems isn't an obvious revelation, so consider the analogy of a supercomputer running a simulation for climate modelling. While the simulation might be running on any number of backend architectures, its defining features include the data that create the initial conditions of the climate model and the logic rules which determine the laws governing the model's evolution over time. Within this overall system are many interconnected and interdependent models. The M in MVC is called the Model for similar reasons - it models a system's behaviour as much as defining it's data. The mapping of a Model to a database or any other storage medium where its data is recorded can be a simple mapping where each Model relates to a single database table or, where a Model is composed of many child Models, a more complex affair involving multiple tables. However, the mapping of Models to a database or other storage medium does not define the Model. In fact you can add data access to developed Models after the fact and this is not an uncommon approach! The Model includes the business rules, behaviour and constraints of the data it represents, and these may be stored to a database (generally once they are reduced to quantifiable configurable rules) after they have been designed and documented. The other side of the Model issue, is how they are utilised and how we balance Models against the other components of MVC. The primary rule, of course, is that Models must be unaware of how they are presented. Presentation is the responsibility of both the View and the Controller who must be aware of the Models they are interacting with. It's a one way street though. Controllers and
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (2 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

Views may know the Model, but the Model will never know about them.

3.2. Clarifying The Model


As I explained in Chapter 2, the Model in Model-View-Controller has two primary roles: to maintain state between requests by storing all application data before the request ends so it can be retrieved for subsequent requests, and also to host the business rules and constraints which apply to the data our state is composed of. However, no web application framework can predict business rules, forms of data or constraints, and so frameworks fall short of the mark of being capable of offering a complete Model. In effect, developers must create all Models themselves since they are specific to the application. This doesn't mean a framework's Model related features are useless! Frameworks still offer invaluable components to assist in accessing Model's data from databases, web services, and other sources. This should, however, highlight an essential concept to bear in mind: a Model is not merely accessing a database. It encapsulates all the data and behaviour of a specific unit within the application, excluding any presentation related logic. This is all fine in theory, so how about a quick example? Our example follows a simple Model design, where each Model maps to a single database table. Additionally, our Model is represented as a subclass of Zend_Db_Table_Abstract. This forms an is-a relationship with the data access layer, rather than the alternative has-a relationship common in more complex Domain Models where data access is added to Models, and not necessarily used as the foundation upon which Models are built.
q q q q q q q q q q q q

<?php class Order extends Zend_Db_Table_Abstract { protected $_name = 'orders'; protected $_limit = 200; protected $_authorised = false; public function setLimit($limit) {
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (3 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q

$this->_limit = $limit; } public function setAuthorised($auth) { $this->_authorised = (bool) $auth; } public function insert(array $data) { if ($data['amount'] > $this->_limit && $this->_authorised === false) { throw new Exception('Unauthorised transaction of greater than ' . $this->_limit . ' units'); } return parent::insert($data); } }

q q q q q

In the class above, we've implemented a really simple Order Model which relies on users having sufficient authority before allowing Orders with amounts greater than 200. If such authority is not granted by some external force, an Exception is thrown to be picked up by a Controller which can handle the problem. The limit is 200 by default but can be modified from the outside (e.g. a configuration file). Another useful way of considering the idea of a Model is to strip out the business logic in the above class and think about where it should go if not the Model. This leads us to another classic concept in thinking which we'll examine.

3.3. In Programming, Fat Models Are Preferable To Size Zero Models


Jamis Buck (the author of Capistrano now employed by 37signals) once wrote about a concept called "Skinny Controller, Fat Model" (our own Chris Hartjes wrote a blog entry on the same topic). I've always liked the simplicity of this concept since it illustrates a key facet of MVC. In this concept, it is considered best practice to maintain application logic (like our business logic from above) within the Model as much as possible and not in either of the View or Controller layers. The View should only be concerned with generating and presenting a UI so users can receive requested output and communicate requests to the application. Controllers are the orchestrators

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (4 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

who translate UI inputs into actions on the Model and pass back output to the client from whatever View has been made aware of the Models it is to present. Controllers must define application behaviour only in the sense that they map input from the UI onto calls in Models and handle client interaction, but beyond that role it should be clear all other application logic is contained within the Model. Controllers are lowly creatures with minimal code who just set the stage and let things work in an organised fashion for the environment the application operates in. This creates an important link in that both the Controller and the View are geared towards presentation. This is obvious with the View but not so much with the Controller. However, since the Controller is so preoccupied with handling the HTTP or Console mechanics of the application, it really is lodged in the presentation layer. PHP developers, by and large however, don't fully understand these ideas in depth. Many see the Model as a fancy term for database access and others equate it with various design patterns for database access like Active Record, Data Mapper and Table Data Gateway. We can't put it all on PHP developers however - there are plenty of Ruby and Python developers with similar notions. This is the misconception frameworks often promote unintentionally by not defining the Model up front. By not fully comprehending what a Model is, why it's such a great idea, and how one should be developed and deployed, developers unintentionally shoot themselves in the foot by adopting other alternatives. On this alternative road, developers restrict their Models to accessing data and push business logic into the Controller. This is a problem because it mixes business logic into that part of the MVC which is concerned with presentation. Yes, again, Controllers are part of the presentation layer. Mapping UI inputs to Model actions, locating and rendering Views, handling client communications... See, presentation! The simplest means of demonstrating this problem is assessing the ability to unit test such business logic. Controllers are notorious for being difficult to test since they are presentational units which, like the View, require entire application runs for each discrete test. Then you can only test the final output, meaning that Controller tests make assertions against the data passed to a View,

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (5 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

or even just the content of the final View itself. If you are familiar with unit testing, this breaches the basic principle of testing units in isolation, and it also makes the practices of Test-Driven Design (TDD) and Behavioural-Driven Development (BDD) difficult to apply to business rules. Most of these unit tests are really functional tests, testing the entire application and not just one focused part of it. Another practical demonstration of this problem is imagining a new project you just completed using the Zend Framework. The client is impressed but breaks the news that they need all their applications transitioned to Symfony. Granted, not a common scenario, but more than one application has needed to migrate to another platform in the past or required a last ditch rennovation. If you pushed all business rules into the Domain Model, then you can pretty much pick up and migrate with ease. Models are essentially independent of the web application framework, although they may rely on data access components that are not so easily migrated (not an issue for the Zend Framework since it's loosely coupled) unless a has-a relationship to the data access layer was employed to allow data access components to be more easily replaced with substitutes. If you pushed business logic into Controllers, you'll realise your mistake - Symfony cannot execute Zend Framework Controllers! Now you need to start migrating entire tracts of source code between frameworks, rather than the minimal orchestral logic a Controller should restrict itself to. Consider this in light of the unit testing difficulties and you can also wave goodbye to those functional tests relying on Zend_Test. At the end of the day, Models are discrete classes that can be tested in isolation from the presentation layer and migrated to other framework systems with ease. Controllers are the exact opposite! They are bound to the presentation layer and the framework itself to such an extent that they are not instantly portable.

3.4. The Fat Stupid Ugly Controller


In the past, PHP developers have employed two common design patterns to create applications: the Page Controller and Transaction Script. Despite their lofty names and standing as Design
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (6 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

Patterns, they merely refer to the most intuitive approach of creating content "pages" with PHP where each page is represented by a PHP script containing all the operations for that page laid out in a procedural style. Although we've moved on from these patterns towards MVC, old habits die hard and the mentality used for these has found a way to be reborn within the confines of the ModelView-Controller. Since it's catchy, colourful, and might win me recognition for my wit (or lack thereof), I refer to these reborn elements as Fat Stupid Ugly Controllers (FSUCs). Initially I had considered Stupid Fat Ugly Controllers (SFUC) but it seemed a bit too rude even if it's more emphatically denigrating. The typical FSUC is the product of remaining in the Page Controller and Transaction Script mental mode when adopting MVC, and continually demoting Models by restricting them to data access roles only. It generates Controllers which break the mold of the "Skinny Controller, Fat Model" paradigm and reverses it completely. Now, business logic is pushed entirely into Controllers. The typical FSUC performs all business logic operations, and struggles with the concept of a Domain Model since Controllers are not discrete reusable classes. Developers use them as naturally, and as frequently, as they ever did Page Controllers with much the same impact. Now every page in the application is bound to a horrid mess of spaghetti code which shows few benefits of good Object Oriented Design. Controller methods expand, helper classes explode in number as a retrofit towards the Domain Model is attempted at some level, and unit testing is often abandoned or restricted to functional testing since you need to initialise the whole MVC operation to do anything - which is hard when there's no set View because your web designer hasn't been hired yet. FSUCs are big, unwieldy, ugly looking and fat. The quasi programming term that applies is "bloated". They perform roles they were never intended for. They are a complete reversal of everything you are taught to do in applying Object Oriented Programming principles. Developers, for some mysterious reason, seem to prefer FSUCs to Models despite the fact those Controllers really are just Page Controllers or Transaction Scripts in disguise. Remember the problems from replacing Models with Controllers I discussed? Unit testing becomes

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (7 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

difficult, applying TDD or BDD becomes near impossible without an obsessive stubborn streak, and you'll never be able to efficiently migrate this mass to another framework without redeveloping half the application. This type of tight coupling and confused code design is the kind of thing Kent Beck fans are supposed to identify as a "code smell" in Refactoring. But it's not the only smelly artifact of FSUCs. FSUCs have frequently duplicated code, lack refactored extracted classes and they can be a nightmare to maintain. In short, they are borderline evil in anything but the simplest of prototyped applications. Consider this example of a simple Model and FSUC combination:
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

<?php class Order extends Zend_Db_Table_Abstract { protected $_name = 'orders'; } class OrderController extends Zend_Controller_Action { protected $_limit = 200; protected $_authorised = false; public function init() { // magically set authorised flag or limit // restriction here somehow } public function createAction() { // keeping it simple, if insecure $data = $_POST; $model = new Order; if ($data['amount'] > $this->_limit && $this->_authorised === false) { // setup View to report the error } $model->insert($data); } }

Now imagine you want to implement a new feature called Order Batch where multiple Orders are
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (8 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

processed and committed in batches. You immediately meet a problem - all the Order business logic is stuck in OrderController::createAction() which is not a reusable object. Will you copy the business rules to the new BatchController? Create a task queue to make multiple calls to the OrderController? Perhaps you can create a new Action Helper common to all Controllers? Keep ticking off the options until you realise a standalone class (i.e. Model) works best. It's the most flexible solution, and it obeys the concept of Keep It Simple Stupid (KISS).

3.5. Controllers Are Not The Data Police


The other facet of this lack of Model consideration is that when developers are convinced that Models should be minimal, and Controllers are all important, it reinforces a reliance on Controllers to take on a new role as the Data Police (a primary reason why they mutate into FSUCs). In this role, all requests for data are channeled through a Controller. However this is not always a suitable strategy, especially when it's used in scenarios where other parts of the application only want to read data. Here's an example I came across on the Zend Framework mailing lists while writing the book. In some unknown application, every page displays the total number of users somewhere in a sidebar. In order to implant this piece of data into a template, all Controller Actions across the application are retrieving the data and passing it to the current View. Much of the source code to accomplish this is copied into every Controller's initialisation method. The obvious problem with the above is code duplication. With the source code copied to every Controller class, it's going to be difficult to maintain. There's a more nefarious element however. From the description, the only part of the application using this data is the View. The Controller never needs it directly, never manipulates it, and never performs any writes back to the Model for it. Given that the Controller never uses this data, why is it the component doing all the legwork to retrieve it? The solution I suggested to the mailing list was to eradicate the middleman. Since the Controller never uses the data, step back a bit and let the View retrieve the data itself using a simple View
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (9 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

Helper (one class, infinitely reusable). This little story illustrates a common fallacy - Controllers are not the sole source of all Model interaction. They do not exclusively police data. Views can themselves pull up a Model without the Controller's permission and retrieve data for presentation more efficiently. If you expand this option further, you'll quickly realise this is one more layer of complexity you could offload from Controllers, reducing their average size even further. The thinner the better! It's worth noting that the term View Helper, is associated with Java as a design pattern in the book "Core J2EE Design Patterns". In MVC, there is every indication that Views should know about the Models they are presenting. Not just whatever arrays we decide to spoon feed them from Controllers.

3.6. Conclusion
Reading through this chapter will have hopefully inspired some new ideas. The one idea which underpins everything else above is applying elegant Object Oriented Programming. Bloated Controllers which are not decoupled from the underlying framework pose obvious problems in retrospect whereas Models, which are heavily decoupled and structured as a system of independent classes, are far more maintainable, testable and portable. This decoupling of Models makes them easy to access from anywhere, including the View. This chapter concludes our examination of MVC, and I trust the diversion into more detail concerning the Model in this pattern was time well spent. In Chapter 4 which follows, we'll spend some time examining the Zend Framework in light of MVC and meet some of its core components for implementing MVC architected applications.

Prev Chapter 2. The Architecture of Zend Framework Applications Home

Next Chapter 4. Installing The Zend Framework

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (10 de 11)28/10/2011 12:52:12

Chapter 3. The Model - Zend Framework Book: Surviving The Deep End

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.model (11 de 11)28/10/2011 12:52:12

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 2. The Architecture of Zend Framework Applications


Table of Contents 2.1. Introduction 2.2. The Model-View-Controller 2.2.1. The View 2.2.2. The Controller 2.2.3. The Model 2.3. In Review 2.4. Conclusion

2.1. Introduction
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (1 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

I've spoken to a lot of developers over the years and the primary concern they've voiced about adopting web application frameworks is understanding the architecture of those frameworks. Nearly all web application frameworks have adopted an architecture first described in 1979 by Trygve Reenskaug called Model-View-Controller (MVC). It might be an old idea but its recent revival in web applications seems to fit like a glove, more so than it ever did in desktop applications. In PHP, there is a lot of misunderstanding surrounding MVC. Many developers see it as being overly complex, difficult to understand and not worth the effort. The same people often label frameworks using MVC as time consuming, performance damaging or bloated with unneeded features and source code. While I understand those sentiments they are not entirely true since MVC is actually quite a simple concept. In fact it is now regarded as one of the cornerstones of what has made web application frameworks so popular. Since it is nigh on impossible to understand the Zend Framework without a working grasp of MVC it needs to be explained up front. This is not a chapter you want to skip! The first thing to remember is that MVC is not a specified implementation, rather it's a loosely defined pattern of behaviour whose implementation varies depending on the goals of the framework, the application and the programming language being used. This concept of a named general solution to a programming problem where the implementation is not specified but described in general terms is known as a Design Pattern. We'll see more examples of how Design Patterns enable understanding of framework architecture in later chapters. As a bit of background, consider the historic use of PHP in crafting dynamic web pages. The typical approach is often referred to as the Page Controller (there's also Transaction Script but you can't explain total chaos reliably!). The Page Controller is another Design Pattern described by Martin Fowler in his famous book "Patterns Of Enterprise Application Architecture" (fondly abbreviated to POEAA in the community). It describes an approach where each HTML page of your application may have its own dedicated PHP file. Often it ends up as many HTML pages per PHP file, but only if those pages are sufficiently similar (e.g. forms and form processing are typical similarities) that the relationship is formed out of the need to reuse source code in the same file. Frequently these pages will have common includes at the top to handle importing libraries or constants. All pages
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (2 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

may need Smarty, for example, or a database connection. In this scenario however, it becomes quite difficult to manage growth and change. Every new feature or bug fix requires additional code and where you end up putting that code becomes a major factor which influences the maintainability of your application. Perhaps a small change requires several changes to multiple pieces of business logic which over time have been distributed across a dozen or more files such that you end up duplicating sections of source code. Perhaps you discover that SQL statements in another dozen files all refer to a table name which is being altered. You can imagine the profusion of small changes such as these across multiple files exploding exponentially until you realise that merely maintaining this mass of source code takes a lot of time and resources. This is the point at which many projects simply stagnate despite the enthusiasm of its developers. I've fallen into this trap myself, and witnessing it first hand was what encouraged me to seek out a better approach. Over the years, PHP application development has undergone a few important revolutions. The most important was the widespread adoption of Object Oriented Programming (OOP). A second was the adoption, slow but steady, of best practices enabled by OOP such as Unit Testing. A third was the explosive increase in use of the Model-View-Controller architectural Design Pattern and its influence on the current generation of web application frameworks like the Zend Framework.

2.2. The Model-View-Controller


The Model-View-Controller architecture (usually abbreviated to MVC) is a general solution or Application Design Pattern to the question of how to separate the responsibilities in an application in a highly structured manner. Basically it avoids total chaos! Its purpose is to prevent dissimilar code from mixing uncontrollably, emphasising that there are three component types which should cooperate at a distance through a set of interfaces. While that sounds horrendously complex (don't worry!), it prevents source code from collapsing under its own weight into the infamous spaghetti mess characteristic of many disorganised PHP applications. The name of this architecture mentions all three of the component separations: Model, View and
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (3 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

Controller. Although MVC may seem to be one of those esoteric concepts in programming, it's actually quite a simple concept. You pick some nugget of functionality, determine its purpose, assign it to one of the three separations, and finally to a specific class within that division. Once everything is split correctly, you end up with an application composed of smaller pieces which are reusable, centralised, accessible, and fit together like building blocks exposing an abstract API to let them link together - working now with abstracted APIs makes incremental changes extremely simple (if done correctly of course!). With everything tidily organised into objects with specific responsibilities, the cost of change is normally reduced significantly which is really the whole point - we want change to be cheap, easy and horror free. Obviously I'm not covering the entire field of Object Oriented Programming here. But hopefully the message is sufficiently clear. MVC ultimately reinforces all the benefits good OOP practice should generate. Code adheres to principles like Don't Repeat Yourself (DRY) and Keep It Simple Stupid (KISS) which discourage messy duplication and enhance reuse and maintainability. The one thing which confuses developers though, is that MVC seems to create more code than if you didn't use it at all. This isn't a symptom of MVC being bad, it's a common effect of applying Object Oriented Programming and a well known outcome of the increased structure. When using a web application framework however that problem is not yours. You're not writing the framework from scratch now, are you? Another reason for an increased amount of code is fairly obvious frameworks add a multitude of features beyond what a simple MVC implementation would offer. However, since OOP does create lots of classes, you should understand why that's a good thing. A massive procedural script would have less code, and even be a lot lighter on its feet, but it's not a permanent solution. OOP code is designed to significantly decrease the cost of maintenance, testing, adaptation and reuse. These normally far exceed the up front cost of development! Remember, you will have to live with that crazy code for years. Write once, use forever. Constantly recreating solutions is not a sustainable method of development since instead of having immediate sunk costs you'll never see again, you have continually escalating costs with each new reinvention
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (4 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

of the same old wheel under a different guise. Rewriting or reinvention is expensive and also risky. The Zend Framework, being a glue framework, offers MVC in a componentised form. This means the framework's MVC architecture is split across a number of apparently independent components including, but not limited to, Zend_Controller, Zend_Db if part of a Model, Zend_View, and
Zend_Layout. Each component covers a specific role within MVC but retains all the characteristics

of independent components you can use outside an MVC architected application. This all certainly results in a profusion of focused classes to such an extent that it can become difficult to see how all the pieces work but honestly you only need the outer abstracted API of each component and can ignore the rest unless you really really want to customise something. For every commonly used class method, there are probably another five only a minority of developers will be interested in. This emphasises the Zend Framework's adaptability - the extra methods exist so you can customise the framework should the need arise (and it nearly always does unless you like restrictive pigeon holes). Let me enforce one other point to erase any lingering doubts. MVC is as common as dirt. Nearly every modern web application framework utilises MVC. It's used in the Zend Framework, Symfony,
Django for Python, Spring, Ruby On Rails and Merb. Pick a framework and odds are it will

describe itself as an MVC framework! Let's meet each MVC component!

2.2.1. The View


The View is responsible for generating a user interface for your application. In PHP, this is often narrowly defined as where you put all your presentational HTML. While this is true, it's also the place where you can create a system of dynamically generated HTML, RSS, XML, JSON or indeed anything at all being sent to the client application whether a browser or some requesting web service. Such generation usually takes one or more Models as the source of the dynamic data the View is generated to present. We can't be too strict though, the View can also accept simple data like scalars and arrays so don't start wrapping every smidgen of data in a Model if it's not required.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (5 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

The View is ordinarily organised into template files but it can also simply be echoed from, or manipulated by, the Controller prior to output. It's essential to remember that the View is not just a file format - it also encompasses any PHP code or parsed tags used to organise, filter, decorate and manipulate the format based on data retrieved from one or more Models (or, as is often the case, passed from the Model to the View by the Controller). A second facet of the View is that since all content is generated dynamically by PHP, it is entirely reasonable to create custom plugins within which to capture frequently used tasks. For example, when translating the title of a blog post into a URL friendly format it's necessary to remove some punctuation and replace non ASCII characters with their ASCII equivalents. This task could easily become a View plugin that operates on the text title of the entry when adding URLs to your templates. We know such plugins belong to the View since they represent presentation logic - logic whose sole purpose is to manipulate the presentation of data. On a side note, this book will not require Smarty or any other standalone library for interpreting templates. Smarty has a respected history in PHP but it does have serious failings once you start thinking of the View as a jigsaw puzzle of potentially dozens of reusable pieces pulled together into a single overarching layout. In effect, this method of View management is so closely related to OOP as a concept that using PHP itself as a templating language becomes almost inevitable. That's not without a cost though since not all web designers know PHP but the Zend Framework does allow you to integrate whatever templating engine you prefer.

2.2.2. The Controller


Controllers are almost deceptively simple in comparison to the View and Model. The primary function of the Controller is to control and delegate. In a typical request to an MVC architecture for the web, the Controller will retrieve user input, translate it into actions on one or more Models, and return output generated by the View based on the results of those Model actions. Now, I'm going to immediately point out that this workflow emphasises an important fact about Controllers. Controllers delegate as much as possible because if you overload them with responsibility they will quickly become overburdened and a pain to maintain. In fact they'll start to
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (6 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

dangerously resemble the age old Page Controller approach which is BAD(TM). Application logic should be delegated as much as possible to Models. We'll meet Models next, and we'll dig more into the concept of Models in Chapter 3. The Controller also has a unique difference from other forms of PHP architectures since it only requires a single point of entry into the application - almost inevitably called index.php. The existence of one route into an application makes certain tasks a lot easier. For example, you can set up a chain of request filters which execute on all requests. Access Control Lists (ACL) are a prime example of one common filter. Hooking in an application firewall or honey pot (security layers) is another. In fact we'll meet a little known project called the PHP-Intrusion Detection System (PHPIDS) at some point in a later chapter.

2.2.3. The Model


The Model can be explained a few ways. In fact, you can write entire books about just the Model and many people have! Chapter 3 delves into this realm in far more detail but let's cover it quickly here. The Model generally has two roles broadly defined: 1. The Model is responsible for maintaining state between HTTP requests. Basically any data whether on a database, in a file, stored in the Session, or cached inside APC must be preserved between requests and thus forms part of the state of the application at the time the last request was completed. So always remember that the Model is not just the database. Even the data you get from web services can be expressed as a Model! Yes, even Atom feeds! Frameworks which rattle off introductions to the Model, almost never explain this upfront which only exacerbates misunderstandings. Here's an example, Zend_Feed_Reader which I developed with Jurrin Stutterheim is actually a Model. It reads feeds, manipulates them, interprets data, adds restrictions and rules, and generally creates a usable representation of the underlying data. Without it, you have Zend_Feed (the current top dog in feed reading) which requires lots of additional work to develop it into a viable Model. In
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (7 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

other words, where Zend_Feed_Reader is a partial Model, Zend_Feed is pure data access. 2. The Model incorporates all the rules, restraints and behaviour governing and utilising this information. For example, if you wrote business logic for an Order Model in an inventory management application, company internal controls could dictate that purchase orders be subject to a single cash purchase limit of 500. Purchases over 500 would need to be considered illegal actions by your Order Model (unless perhaps authorised by someone with elevated authority). The Model should be capable of enforcing this constraint. This makes incredible sense if you examine the meaning of the word "model". We have climate models in climatology, for example, which define the data, run processes, assume behaviours and calculate probable outcomes from these. The M in MVC is called a Model for a reason. The Model doesn't just represent mere data, it represents the entire system for which that data is useful. Of course any system can be complex enough to require multiple interacting Models but you get the idea. If you read these two points, you may realise something startling. With the exception of the UI, most of an application can be expressed within Models. It's the Model that centers on all the data and underlying behaviour of that data, and sometimes even how to display that data. It's the Model which can understand, interpret and represent that data and provide it with meaningful uses. It's also a logical place to store validation rules and constraints - why not keep those with the data they operate on? This examination makes it obvious that the Model is the one part of the MVC tripod you need to develop almost from scratch. The Zend Framework can provide data access components, but it can't predict what your application will do! Since Models go beyond mere data access, a lot still needs to be developed and tested before you have a viable Model. It's not always as easy as extending Zend_Db_Table.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (8 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

We'll cover the Model further in Chapter 3 since there is a wealth of information to consider about what it represents, and where it fits in the overall MVC picture.

2.3. In Review
The Model-View-Controller architecture has become a widely recognised solution suitable for web applications and it's evident in the majority of the current generation of frameworks for most programming languages. In the Zend Framework, these three separations are represented by the Zend_Db, Zend_View,
Zend_Layout and Zend_Controller components. You'll be hearing a lot about these four

framework components and the classes they are composed of in the chapters to come! Together these form the backbone of the Zend Framework's MVC architecture and underpin a lot of its best practices. MVC was originally used to promote the "separation of concerns" in desktop GUI applications. By separating each concern into an independent layer of the application, it decreased coupling and interdependencies which in turn made applications easier to design, write, test and maintain. Although GUI applications have turned away from MVC in recent years, it has proven to be highly effective when applied to web applications. In the framework this adds a lot of predictable structure since each segment of MVC for any one supported request is segregated into its own group of files. The Controller is represented by Zend_Controller_Front and Zend_Controller_Action subclasses, the Model by domain classes or another set of classes which may rely on Zend_Db_Table and Zend_Db_Table_Row for database persistence, and the View by .phtml template files and View Helpers. The Zend Framework manages how each is orchestrated in the big picture leaving you free to focus on just those groupings without worrying about all the code combining them together into a cohesive work unit. In a sense it's like building a house where the foundations, walls, internal wiring and plumbing are already in place and all that's left is the internal decoration and a roof. It may take some time to learn how to decorate and roof the prepared sections but once you have learned how, later houses are finished a lot faster!

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (9 de 10)28/10/2011 12:52:31

Chapter 2. The Architecture of Zend Framework Applications - Zend Framework Book: Surviving The Deep End

2.4. Conclusion
This was a quick assault on the concept of the Model View Controller (MVC) architecture. Despite its length it's not an exhaustive examination but it covers enough ground that later concepts won't be completely out of context. Outside this book there exists a flood of articles concerning the ins and outs of MVC on the internet and elsewhere and I encourage you go find some because it will not be wasted time. Every bit of understanding is worth the time you spend finding it. In the next chapter of this journey we'll spend some time getting to know the Model. It's treated separately since the Model can pose a lot of difficulty as a concept and it has a wide range of uses.

Prev Chapter 1. Introduction Home

Next Chapter 3. The Model

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/the.architecture.of.zend.framework.applications (10 de 10)28/10/2011 12:52:31

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 1. Introduction
Table of Contents 1.1. The Zend Framework 1.2. About This Book 1.2.1. Obtaining The Source Code For Chapters 1.3. Me, Me, Me! 1.4. You, You, You!

1.1. The Zend Framework


Since its release, the Zend Framework has taken the PHP community by storm. Its approach to the whole framework business is almost unique in an environment which emphasises tightly integrated and restrictive solutions to a broad set of web application problems. So far, the ZF's approach has

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (1 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

proven popular with PHP programmers. The Zend Framework was conceived to make writing web applications easier, simpler, and faster. It accomplishes most of this objective by presenting you with a body of source code that has been developed by dozens of developers and unit tested until it squeeks for mercy. By relying on this body of code there is no need for you, the developer, to cover the same ground in your development work. This essentially lets you skip developing similar application functionality already provided by the framework and focus more on what your application will do. That's an extremely simplistic way of thinking about frameworks but it gets the point across. Over the years developers have come to recognise that they have been spending a ludicrous amount of time developing the same basic application functionality every time they started a new application. This is a familiar story to every PHP developer out there from the amateur to the professional, and it's just as frustrating now as it always has been. Frustrated developers tend to be highly motivated to remove these frustrations. Over the years PHP developers have created libraries, class collections, function libraries, C extensions, and even text files littered with useful code snippets. A web application framework is one step above that - it's basically a condensed application with all the application specific source code removed. It's portable, standardised, highly tested and unleashed on the community at large to be reused thousands and thousands of times. Choosing a framework from the many alternatives which exist, especially in our current climate where migrating between programming languages, let alone frameworks, is perfectly acceptable and common, is no easy task. Practically every web application framework will promise you simpler, easier, faster development. Other factors which must be considered include maintainability, adaptability, ease of testing, technical and community support, quality of documentation, regularity of upgrades and fixes, quality control, base performance, the learning curve, features and innovations, hosting availability, support for current best practices, and community sourced literature. And that's not an exhaustive list! Every group of programmers can add extra factors for consideration which are far more specific than these categories.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (2 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

The Zend Framework scores well in many of these areas but certainly not all of them. In fact, no framework scores well in all of them. Any choice to adopt a framework will result in a tradeoff which is one of those less publicised facts. At this point I am certain there are still tens of thousands of developers in PHP who fully intend never to use a framework because they're evil and built by misinformed fanatics (like me). PHP is peculiar in that it is seemingly slow or resistant to the adoption of a mind set encompassing the principles of web application frameworks taken for granted in other languages. A lot of this is mere demographics since PHP has more than its fair share of amateur programmers or self-taught experts due to the ease with which anyone can start using and learning PHP, not to mention PHP is an elderly member of the web programming language category. In a sense its the same demographic quirk which often gets PHP unfairly squared away as an insecure programming languages since most of the security problems lie not with the language but with its less informed practitioners who don't read security books and articles. I urge those in this camp to re-examine their beliefs and keep an open mind. I'm not evil, as far as I know. To use the Zend Framework you need to understand how it's structured and formed. Many frameworks follow the path of an all or nothing approach, a mindset where you are expected to follow the conventions and tools a framework provides with minimal departure from the expected development track. Such frameworks were once called "full stack frameworks" by Chris Hartjes, referring to these frameworks' reliance on developers using everything the framework offers with little to no importing of similar external libraries. Full stack frameworks include CakePHP, Django for Python and Ruby On Rails. On the flip side, Chris defined "glue frameworks" where developers are free to pick and choose what they need, to extend every possible class, to replace entire components, and generally structure their application more freely. Glue frameworks include Code Igniter and ezComponents. The Zend Framework is also a glue framework. It offers all of its features as loosely coupled components which are capable of an independent existence outside the framework. In essence, it's a collection of libraries with a few binding elements like the MVC components which apply several

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (3 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

default conventions so it feels like a full stack framework while you're using it. This componentised approach creates one factor which accounts for a lot of the Zend Framework's popularity: it's incredibly extendable and customisable. Every component and class can be subclassed, registered to the framework stack, and used with minimal fuss. Yes, that adds more work (if you choose that path) but it means the only limit to the framework's capabilities and features is your own imagination. Components exist which go far beyond the mere duty of a full stack framework since the community has added features for PDF generation, caching, RSS/Atom aggregation, Dojo and jQuery integration, dozens of web service APIs, and countless more besides these random mentions. The other part of its popularity is that nothing stops you from using all these components outside the framework stack in CakePHP, Symfony or Code Igniter! It's a truly open and portable architecture. So how does the Zend Framework match up against those decision factors I previously mentioned for consideration when picking a framework? I noted thirteen (unlucky for some) in total. Maintainability The Zend Framework is highly maintainable through its support of subclassing, reliance on interfaces, impressive unit testing suite and the constant oversight of the community. There are backwards compatibility checks in place to ensure each new revision has minimal impact on your existing applications including public subversion access, sanity releases, and a strenuous resistance to behavioural changes. Adaptability Given its glue framework nature, the framework has incredible scope for adaptation and customisation. I don't think there is a shortage of classes and components you can adapt, subclass or simply replace with existing alternatives. There are already collections of pre-adapted components you can download outside of the framework which add features not included in the framework already. Learning Curve

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (4 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

The learning curve comes in two discrete stages. The initial curve is about as steep as any other framework and won't pose developers much of a problem. This is assisted to a large degree by the excellent Reference Guide. The second stage is in learning how to use the frameworks adaptability to your advantage to write plugins, extensions and even new components which are often demanded by real applications. This is certainly a shallower curve since it comes from experience and it's actually the exact curve for which this book was written. Features and Innovations Dozens of components, hundreds of classes, and a proposal queue a mile long. There is no shortage of features! In terms of innovations, the framework has worked at setting itself up to carry implementations for the latest web technologies. For the most part that has been highly successful and you'll find the Zend Framework carries official reference PHP implementations of technologies from partners like Google, Adobe and Microsoft. Quality of Documentation There are two bodies of thought about the framework's documentation. Both agree that the Reference Guide is incredibly important and highly successful in passing knowledge to developers. It does however fall victim to its immense size making certain details hard to find, and even basic material appear overly complex or difficult to decipher. Don't deem it bad though! It's vast lightyears ahead of many long standing frameworks which can barely bother to scratch together generated API documentation. The Reference Guide is also supplemented by...that's right...this book! In fairness, I should add there are three other books currently published about the Zend Framework so documentation is well covered. This is the only free one though (shameless plug). Technical and Community Support A key selling point of the Zend Framework is that it has the official backing of Zend Technologies Inc. Zend supervises the entire development process ensuring all components are vetted, reviewed, and their utility confirmed. Zend and their worldwide partners also offer support and training packages for a price which many businesses will be interested in. On the community side
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (5 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

there's the numerous mailing lists, IRC channels and a community of bloggers who are all too happy to drive themselves silly answering your every whimsical question. Most community support resources (except the mailing lists) tend to be independent of Zend and there are numerous nonEnglish support sites and forums. Regularity of Upgrades and Fixes Updates occur frequently usually with a new minor release every couple of weeks encompassing bug fixes, new features, and enhancements to current features. There are no security-only releases which may be problematic if small bugs accumulate for too long but the release schedule is kept tight, frequent and predictable. Quality Control Quality control is a function of Zend who review all components before they are cleared for further development and distribution in any release. There are components where quality is obviously sub par in terms of features, ease of use, and other assorted factors but they are far and few between and, if popular, will usually get the attention needed. All components undergo rigorous unit testing and the ever watchful eye of the community don't let much past them. The framework recently held a bug hunt event which I hope continues into the future with some regularity. Base Performance Base performance is something you must take with a pinch of salt. Since every framework has varying requirements and feature mixes it's hard (many would say even pointless) to compare them on an equal footing. The main point I'd make is that judicious use of caching and optimisation eliminates most base performance advantages any framework has over any other framework. Don't neglect that point please! If benchmark interpretation is your thing however, the best source I know of was a series of benchmarking tests run by Paul M. Jones in September 2008 and updated
to correct a small shortcoming in March 2009. All these benchmarks omit any application

code except what was essential for a full stack request/response cycle. It shows the Zend Framework at that time obtaining 78.93 requests per second on Paul's Amazon EC2 reference

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (6 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

system. This compares to 61.84 for Symfony, 42.79 for CakePHP and 138.64 for Solar. Code Igniter was not benchmarked in Paul's tests but likely would beat most others given its target market and development approach. Overall it shows the Zend Framework is more or less in line with its main alternatives. I consider the minor lead it holds as being completely irrelevant in the overall analysis. Ease of Testing I'll be honest. This is my pet peeve with the Zend Framework. Readers of my blog will know I enjoy ranting about subjects like Behaviour-Driven Design (BDD) and eXtreme Programming (XP). These practices demand a level of testing support so we fanatics can practise BDD or Test-Driven Design (TDD) to our fanatical heart's content. The Zend Framework, due mostly to its PHP mindset, managed to shoot itself in the foot on this one with spectacular style. It has no internal testing API! I will temper my vocal opinion by noting there is now a new Zend_Test component which makes functional testing a great deal easier if you use PHPUnit. It functions as an abstract proxy into the heart of the MVC component offering a good level of control over initiating requests and testing responses. It won't satisfy everyone but it gets the basics right and that will be sufficient for the majority of PHP developers. So long as developers keep their Model independent of the framework Controller/View layers any issues will be minimised. Availability of Hosting The Zend Frameworks runs on PHP. Seriously, need I say more? Might as well talk about the availability of air, or pollution, or maybe rabbits? PHP has something in common with all three...it's everywhere. Support for Current Best Practices The Zend Framework always had one achilles heel: testing. I've covered that previously, and outside of that criticism there is nothing to prevent developers employing their own brand of fanaticism or programming methodology (which only sounds like fanaticism to sarcastic minded people who poke fun at themselves...like me).

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (7 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

Community Sourced Literature Hands down, the Zend Framework has engaged the community with great success. On every conceivable topic there are reams of articles, books (including this one!), reference guide sections, and blog entries. You did know this very book once lived a modest existence on my blog as a 9 part tutorial series, right? Thirteen factors later and I think I've covered all the bases. The purpose of this exercise was to instill a little bit of familiarity and fanatical fervour in you, the reader, about the Zend Framework (so you'll keep reading and maybe give me money for my new Macbook Pro). But it will also let you go look at other frameworks with some idea of what to look for in an organised fashion. Life doesn't start and end with the Zend Framework afterall, though it should for the length of your attention span while reading this book!

1.2. About This Book


Zend Framework: Surviving The Deep End is written in the form of a detailed tutorial following a step by step approach to building a real life application. Topics are grouped where it makes sense and there will be continual references to earlier chapters which serves to reinforce what you're learning as you read. The book was designed to bring together elements of the Reference Guide, the growing body of community knowledge and my own personal experience so developers can see the bigger picture of developing a real application with the Zend Framework. To my mind that's always been the framework's main problem since the Reference Guide adds little beyond explaining each framework component in total isolation. It doesn't offer a development approach, ways of thinking or a list of advanced topics which combine components. You should note though that this book is not a replacement for the Zend Framework Reference Guide. It's assumed you can do some independent reading of the Reference Guide. The Guide is free, detailed, and reasonably easy to search. This book is a complement to it, not a replacement. The book also includes the full source code of the application within the text, and may repeat it

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (8 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

several times to highlight new changes I am making. I understand that pages of source code can sometimes be frustrating but it does enforce clarity and I value clarity a great deal. For simplicity the full finalised source code of each chapter is available as a separate internet download as detailed below. I will over time refer to several external libraries, other than the Zend Framework, which you are expected to install. These may include PEAR, Blueprint CSS Framework, jQuery, HTMLPurifier and PHPUnit. I know from experience this can be unpopular with some people but I assure you that their installation will be covered in detail and is quite straightforward even for beginners. You should bear in mind a real life application will require numerous external libraries! Finally, note that this book assumes a basic working knowledge of PHP 5, SQL, and Object Oriented Programming (OOP). These are necessary skills if you intend learning the Zend Framework but will not be covered by this book in detail. Since PHP is so simple to learn though, I don't doubt you can find countless resources online to get you started down the road towards PHP Guru status.

1.2.1. Obtaining The Source Code For Chapters


The source code for all chapters is maintained publicly on Github.com using a git repository. You can find the source code for a specific chapter by navigating to the relevant tag. For example, the source code for Chapter 10 can be found at http://github.com/padraic/ZFBlog/tree/Chapter-10. The decision to use git was easy with Github. If you are unfamiliar with this version control system, Github does offer a download option for all tags and branches. Here's a quick overview of how to get the source code for a chapter using git. To grab the source code from the repository, you should clone it. Cloning is equivelant to Subversion's checkout method. To clone the repository to the directory ./zfblog use the following command from a console.

git clone git://github.com/padraic/ZFBlog.git zfblog

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (9 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

You should see the clone take effect quite rapidly (git is fast!).

Initialized empty Git repository in /home/padraic/ projects/misc/zfblog/.git/ remote: Counting objects: 120, done. remote: Compressing objects: 100% (94/94), done. remote: Total 120 (delta 31), reused 0 (delta 0) Receiving objects: 100% (120/120), 19.63 KiB, done. Resolving deltas: 100% (31/31), done.
By default, this will set your branch to "master". Unlike Subversion, git doesn't use the typical trunk/ branches/tags setup but the "master" branch is the closest concept to trunk for many projects. All contents of the ./zfblog directory will be from this branch. Since you will likely be interested in using the source code on a per chapter basis rather than the completed Chapter X version, you will need to get the most relevant tagged version. Get a list of all available tags using:

git tag -l
Actually accessing any tag or branch with git is certainly odd compared to subversion. For starters, they have no physical path! Instead you "checkout" a branch or tag and git updates the current working directory with it. You can checkout any branch or tag, including returning to master at any time. Branch merging works similarly - you don't need all the paths for each to be physically present. This seems very odd at first, but you soon get used to it. You also realise branching is extremely cheap and easy in git. To checkout the Chapter-10 tag you would issue the following command:

git checkout -b Chapter-10


Your working directory is now updated to that tag's state and you'll get the following result message.

Switched to a new branch "Chapter-10"


You can now view the source code for the relevant chapter without any future changes (applied to
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (10 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

master) getting in the way. You can switch to any other tag or branch the same way. The -b option is actually only required when checking out a new branch or tag for the first time - this creates a local branch which can be updated from the repository. If you get lost and can't figure out which branch or tag you are currently in (easy without the physical paths!), you run the command:

git branch
Without any arguments, this creates a list of all existing branches and tags, and places an asterix in front of the current one.

* Chapter-10 master
On a final note, subsequent chapters may include any configuration files added as .example files this indicates they should be copied and edited before they may be used. This will occur where options should contain private API keys or similar information.

1.3. Me, Me, Me!


This book is not about me but as the author I'm sort of unavoidable! I've been a PHP developer for about ten years now with experience in other programming languages including Java, Ruby, and C/C++. For many of those years I've maintained a blog at
http://blog.astrumfutura.com on which I've spent hours out of every month writing my thoughts,

opinions and illuminating areas of interest to readers with articles and tutorials. I live in Ireland and hopefully always will. I love this tiny little island to bits. I'm known as an opinionated individual with a sense of humour. I will make jokes throughout the book and, regardless of their quality, you are expected to smile or perhaps giggle for a few seconds after reading them. Go on, I know you want to... I have in the past been highly critical of certain

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (11 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

Zend Framework features and ways of thinking and will continue to be so. You can expect me to air those opinions in this book (within reason) since I think they illustrate specific points of interest to developers and gives them fair and reasonable warning where the water gets muddy. The title of this book is not "Blindly Praising The Zend Framework". All frameworks have warts, no matter what their developers say. Knowing what the warts are, and how to work around them, is valuable knowledge. Finally, I am a Zend Framework Contributor (for all my griping). I've proposed and worked on Zend_View, Zend_Oauth, Zend_Crypt, Zend_Feed_Reader, Zend_Feed_Pubsubhubbub, Zend_Service_Yadis, and Zend_Captcha_Recaptcha. I probably have more proposed I haven't gotten around to yet. Presumably that means I tend to know what I'm doing, just in case you thought I was a crackpot or something. If I do get something wrong, be vocal and I'll correct it!

1.4. You, You, You!


The book's nature might seem somewhat odd. It's structured around an application, not a specific breakdown of Zend Framework components, and knowledge is passed on when the application meets problems. The approach is highly practical and designed to focus more on the big picture of the application, rather than the nitty gritty details of every component's API which is more than sufficiently covered by the Reference Guide. As you read the book, you are encouraged to experiment. There is no one true path to developing any piece of an application functionality so feel free to wander a bit and try things your way. You'll learn more. If you are feeling talkative, you can explore any section of the book and even complain about it at the official book website, http://www.survivethedeepend.com, where we host a comments section for the online version of the book and a forum to raise questions for other readers (and me, I suppose!). You can also drop me a short line on Twitter at http://www.twitter.com/padraicb. Now, sit back, relax, keep all appendages inside the cockpit, and turn the page for Chapter 2.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (12 de 13)28/10/2011 12:52:44

Chapter 1. Introduction - Zend Framework Book: Surviving The Deep End

Prev Zend Framework: Surviving The Deep End Home

Next Chapter 2. The Architecture of Zend Framework Applications

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/introduction (13 de 13)28/10/2011 12:52:44

Chapter 4. Installing The Zend Framework - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 4. Installing The Zend Framework


Table of Contents 4.1. Introduction 4.2. Before You Install The Framework 4.3. Getting The Zend Framework 4.3.1. Download As Compressed Archive File 4.3.2. Checkout From Subversion 4.3.3. Download As A Linux Distribution Package 4.3.4. Nightly Build Download 4.4. Post Installation

4.1. Introduction
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/installing.the.zend.framework (1 de 6)28/10/2011 12:53:05

Chapter 4. Installing The Zend Framework - Zend Framework Book: Surviving The Deep End

The past few chapters were replete with theory so it will be a nice break to do something concrete. Afterall there's only so much I can pummel your brain with before I surrender and get on with the show! I know books with tons of theory can be tough going but stick with it - it's well worth letting it all kick around at the back of your mind. When I started writing this chapter about installing the Zend Framework I complained on Twitter that the Zend Framework would be easier to install if someone created a PEAR package for it (something Symfony and ezComponents do). Going by the responses this is actually a plan Zend are considering and which will make some of the following chapter obsolete if it does.

4.2. Before You Install The Framework


Before installing the Zend Framework you're going to need a fully functional development system. While writing this book I used Ubuntu Linux 9.04 (the Jaunty Jackalope), Apache 2.2, MySQL 5.0.65 and PHP 5.2.6 - a typical up to date LAMP system from the Ubuntu repositories in other words. Any Windows equivelant using similar versions of Apache, MySQL and PHP would be equally usable. To follow this book, I'm making the assumption that you are at least using the Apache web server and MySQL database management system. If you want to use ISS and MSSQL, I'm not going to stop you or anything but be aware some steps in the next couple of chapters will need to be tweaked to apply to those systems. Where it's needed throughout the book, I will mention Windows specific instructions below the Linux ones. Installing all these is not something I'm going to cover. The internet is practically swamped with installation guides for every conceivable system and scenario, and you should use your preferred search engine to search for relevant instructions. The PHP manual is a good place to start but you can also either use your Linux distro's update system (like aptitude for Ubuntu) or even grab an allin-one development system for Windows like XAMPP to get this done in fewer steps.

4.3. Getting The Zend Framework


You can currently get the Zend Framework in a few ways. You need to be careful however since some methods tend to be a bit...dodgy.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/installing.the.zend.framework (2 de 6)28/10/2011 12:53:05

Chapter 4. Installing The Zend Framework - Zend Framework Book: Surviving The Deep End

4.3.1. Download As Compressed Archive File


The simplest is to grab the most recent version from http://framework.zend.com/download/
latest. As I'm writing this, the latest version is 1.9.1. You will need to extract the downloaded

archive to a working location like your OS desktop. We'll cover the structure of the downloaded files shortly. Once you have the framework files downloaded it's worth taking some time to learn your way around them. You see the Zend Framework, as we discussed in the last chapter, has four different collections of useful classes: Standard, Standard Incubator, Extras, and Extras Incubator. The most important one is Standard - it's the main collection you will be used. This complicates things a little since the major part of any initial setup is knowing what to add to your PHP include_path, whether you intend editing it in a php.ini configuration file or changing it with set_include_path() when you are creating a new application. Here's where to find each of those four collections relative to the parent Zend Framework directory you extracted:

Standard: /library Standard Incubator: /incubator/library Extras: /extras/library Extras Incubator: /extras/incubator/library
Inside each of those locations is a directory called either Zend or ZendX. The "X" postfix identifies extra components distributed for which Zend offers no official support (the lack of support doesn't prevent them being very useful however!). It is the above paths (the full path from your root or drive letter) you'll need to eventually add to the PHP include_path. For example if I copied the framework files to /usr/share/php/zf the full path to add to my include_path for the Extras Incubator would be /usr/share/php/zf/extras/incubator/library. This should be enough to go away and edit php.ini if you want to (not a bad idea) and I'll show you how to manage the PHP include_path for a Zend Framework application in subsequent chapters if you don't wish to perform any php.ini editing. With the information above go copy the framework files somewhere more permanant. I use /usr/ share/php/zf on Ubuntu but any location will work. Just try not to forget where you put it!

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/installing.the.zend.framework (3 de 6)28/10/2011 12:53:05

Chapter 4. Installing The Zend Framework - Zend Framework Book: Surviving The Deep End

4.3.2. Checkout From Subversion


If you prefer keeping pace with active development in trunk, or keeping your fixes updated from the current release branch, you can also "checkout" the framework from it's subversion repository at
http://framework.zend.com/svn/framework/. Subversion is a version control system which

developers use to maintain a central online repository for source code they are developing. Its attraction is allowing multiple developers to work on the same source code without constantly swapping notes or taking turns. I would suggest creating a permanent location like /usr/share/php/zf and checking out the repository into that directory. The only problem with subversion is that there is a lot (by a lot I mean a mind blowing crazy number) of files when you include all the branches and tags. To grab only the needed files from subversion you'll need to make four separate checkouts only updating from the locations of the main four file collections whose URLs are:

Standard: http://framework.zend.com/svn/framework/ standard/trunk/library/ Standard Incubator: http://framework.zend.com/svn/ framework/standard/incubator/library/ Extras: http://framework.zend.com/svn/framework/extras/ trunk/library/ Extras Incubator: http://framework.zend.com/svn/ framework/extras/incubator/library/
If using trunk for each is not your thing, you can instead use the relevant release branch for each. Simplest way to do this is to create the parent directory as usual, for example at /usr/share/php/
zf, and inside this run the svn checkout command referencing four other directories named for the

collection name, similar to standard, standard_incubator, extras, extras_incubator. Whenever you want to update to the latest development code, you only need to perform a subversion update for each of those four directories.

4.3.3. Download As A Linux Distribution Package


There is a third option - grab it from your Linux distribution's update server using apt-get or aptitude (if using a Debian based distribution). I really don't recommend this since you may end up with a
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/installing.the.zend.framework (4 de 6)28/10/2011 12:53:05

Chapter 4. Installing The Zend Framework - Zend Framework Book: Surviving The Deep End

framework version that was released months behind the latest one. This is borderline dangerous if you are writing to a version with a significant security issue (possible). On Ubuntu you would simply run a command similar to:

sudo aptitude install zendframework


If you are certain the latest version is available from your distribution's update server, only then consider it. At the time of writing this appears not to be the case though.

4.3.4. Nightly Build Download


I won't describe this further since it's currently not operable on the Zend Framework website. In the future however, this would allow you to grab the most recent nightly built version without going through Subversion.

4.4. Post Installation


That's about it for installing the Zend Framework. The only complex step is being aware the source code is packaged into four distinct parts, each of which would need to be separately added to the PHP include_path at some time. If you are comfortable with editing php.ini, you may do this immediately. If you do I suggest ordering included paths so that the Incubator paths are last. This ensures Incubator component classes do not take precedence over all others, but still allows access to any new original Incubator components. This ordering of include_paths also counts as a minor performance optimisation as described in this book's appendices.

Prev Chapter 3. The Model Home

Next Chapter 5. A Not So Simple Hello World Tutorial

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/installing.the.zend.framework (5 de 6)28/10/2011 12:53:05

Chapter 4. Installing The Zend Framework - Zend Framework Book: Surviving The Deep End

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/installing.the.zend.framework (6 de 6)28/10/2011 12:53:05

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 5. A Not So Simple Hello World Tutorial


Table of Contents 5.1. Introduction 5.2. Step 1: Creating A New Local Domain 5.3. Step 2: Creating A Project Directory Structure 5.4. Step 3: Implement Application Bootstrapping 5.5. Step 4: The Only Way In, The Index File 5.6. Step 5: Adding A Default Controller and View 5.7. Conclusion

5.1. Introduction
It's become traditional for programming books to offer the simplest possible example of whatever

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (1 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

programming language, framework or library they are covering. Usually, this means doing just enough to print "Hello World" to the screen so you can see how the basic functionality works. This is not really the most realistic example when applied to frameworks but it's as good a starting point as any!

5.2. Step 1: Creating A New Local Domain


Before we hit the source code, it's generally recommended to develop using an environment a bit closer to what is typical of our production environment. Throughout the book I will develop applications within the document root of a web server rather than a subdirectory. While you can use a subdirectory, it may require a bit more work when setting up URI references across the application, something I will cover in more detail later. For now, take a look at Appendix A: Creating A Local Domain Using Apache Virtual Hosts which details a simple process for enabling a local domain with its own separate document root for this example. Using this domain and an Apache Virtual Host, we can serve the example from the
http://helloworld.tld domain on our development system.

5.3. Step 2: Creating A Project Directory Structure


The directory structure of our example project is next on the list. First, create a new directory matching the path (excluding the trailing /public subdirectory part) you previously entered as the Document Root for the helloworld virtual host. This will be where all our project files will be saved. So for Ubuntu I could create the directory at /home/padraic/www/helloworld or under Windows,
C:\projects\helloworld.

There is always a lot of debate about how to structure the directory layout but a lot of that is being formalised by the ongoing Zend_Tool effort led by Ralph Schindler which will result in a stable full command line tool for creating and manipulating projects. Until it's fully stable and documented we won't use it here, so that means some manual grunt work in creating the directory layout by hand.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (2 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

Application Directory Layout

Here's my suggested directory layout. This is by no means compulsory and you may vary this according to your preferences on other applications. For the purposes of our example, we only need to use a subset of these to start with. As you can see, all Controller and View source code will be stored within the /application directory. Controllers and Views all have framework specific loading conventions so it makes sense to follow this convention. Here we also store any Modules which are basically discrete collections of Controllers and Views (and optionally Models and application specific non-reusable helpers). You may be used to seeing an /application/models directory here from the documentation. I do not use this at present since Models are so incredibly diverse in how they are implemented that there exists no one single Model loading convention. Therefore, I maintain Models as part of my generic application library, /library, along with any other classes specific to this application. It is still possible to store Models in /application/
models. You may prefer to maintain Models within a more specifically named location but

remember that every new location storing source code may become another entry on your PHP
include_path. Later in the book we'll work with Models that may be stored as traditionally noted in

the Reference Guide, since the introduction of Zend_Loader_Autoload allows us to be more flexible in this regard. Everything else exists at the parent directory level, and all future application source code outside of Controllers, Views and non-reusable classes will exist within either the /library or /vendor directory. The library directory is where I store any general classes used within this application. The vendor directory is generally for non-specific classes or third-party libraries I wish to use. The
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (3 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

differences between the two are completely arbitrary - I just like keep my own classes and third party libs separate. I also keep the Zend Framework itself here unless it's already installed on the server in a more central location and accessible from the include_path. The remaining directories are no mystery. The config directory holds any configuration files. Following emerging standards, this may also be located at /application/configs. The data directory allows for the storage of data as files. These could be caches or other information. The public directory is where all files accessible by a visitor to our website will reside. The scripts directory holds any general use scripts such as tasks to run using cron.

5.4. Step 3: Implement Application Bootstrapping


Bootstrapping is when we setup the initial environment, configuration and Zend Framework initialisation of our application. It's not a difficult file to write but it can grow ever larger as your application grows in complexity. To manage this I strongly suggest implementing it as a class with bitesize methods. Breaking it up does wonders for your sanity. Later in the book we'll introduce the Zend Framework's solution to this complexity, Zend_Application, which was introduced with Zend Framework 1.8. Since the bootstrap is a class, the obvious location to maintain it is in /library. Sure, you can put it in /application, but that's another include path (unless you are using Zend_Application which uses the absolute path to the class) and if we keep sticking classes all over the place we'll end up with the include_path from Hell! So far, and assuming you are not using Zend_Loader_Autoloader, managing Models in /library along with our bootstrap class removes two entries on the include_path. We'll store the new bootstrap as an application specific file under the ZFExt namespace, meaning it will be stored as /library/ZFExt/Bootstrap.php.
q q q q q q q q

<?php /** * When storing the ZF within /vendor, use an absolute path. */ require_once 'Zend/Loader.php'; class ZFExt_Bootstrap
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (4 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

{ public static $root = ''; public static $frontController = null; public static function run() { self::setupEnvironment(); self::prepare(); $response = self::$frontController->dispatch(); self::sendResponse($response); } public static function setupEnvironment() { error_reporting(E_ALL|E_STRICT); ini_set('display_startup_errors', true); ini_set('display_errors', true); date_default_timezone_set('Europe/London'); self::$root = realpath('..'); define('APP_ROOT', self::$root); spl_autoload_register(array(__CLASS__, 'autoload')); } public static function autoload($class) { include str_replace('_', '/', $class) . '.php'; return $class; } public static function prepare() { self::setupFrontController(); self::setupView(); } public static function setupFrontController() { self::$frontController = Zend_Controller_Front:: getInstance(); self::$frontController->throwExceptions(true); self::$frontController->returnResponse(true); self::$frontController->setControllerDirectory( realpath(self::$root . '/application/controllers') ); $response = new Zend_Controller_Response_Http; $response->setHeader('Content-Type', 'text/html; charset=UTF-8', true); self::$frontController->setResponse($response); } public static function setupView() { $view = new Zend_View; $view->setEncoding('UTF-8'); $view->doctype('XHTML1_STRICT'); $view->headMeta()->appendHttpEquiv('Content-Type',

q q q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (5 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End
q q q

'text/html;charset=utf-8'); $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer ($view); Zend_Controller_Action_HelperBroker::addHelper ($viewRenderer); } public static function sendResponse (Zend_Controller_Response_Http $response) { $response->sendResponse(); } }

q q q

q q q q q

Well, you may now realise why this chapter is the Not So Simple Tutorial! Presumably you're in the business of learning right now however, so the bootstrap above is far more complex than the minimal example you'll find in the Reference Guide. What I have done above is two-fold. I've structured the bootstrap as a class with discrete methods for different bootstrap tasks and stages. Secondly, I've setup default assumptions for any application output by modifying settings on Zend_View (which generates application output using templates) and
Zend_Controller_Response_Http (which handles headers and the mechanics of actually sending

responses back to a client). The initial setup stages are fairly simple to see. Our environment setup which in the future should be configuration driven, sets up PHP for the application with some configuration values, error reporting level, timezone information (not needed if defined in php.ini), and an autoloader function so we skip using require_once in our source code. The custom autoloader is in contrast to the more common reliance on Zend_Loader, but it's a lot leaner than Zend_Loader's implementation which performs a lot of, usually, unnecessary tasks (see Appendix B). The current environment setup is geared towards development so errors will be displayed. The next step, within the prepare() method, is setting up the Front Controller. As discussed, in Model-View-Controller there is usually a single entry point into the application, a point through which all requests must pass. For the Zend Framework, this is the Front Controller defined by the
Zend_Controller_Front class. Zend's Front Controller class is a singleton (its most annoying

"feature" - singletons should be avoided when not necessary). In the setupFrontController() method we set some flags to ensure it returns a Response object instead of simply echoing it
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (6 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

beyond our control, and that it allows Exceptions to be visibly thrown instead of searching for an
ErrorController which at this point does not exist. The next important step is telling the Front

Controller where to find our application's collection of Controllers and Views. The final step where we create a discrete instance of the Request object, i.e.
Zend_Controller_Http_Response, exists to set a default Content-Type header for application

responses. This ensures that, unless otherwise specified, all output will automatically send clients a Content-Type header for text/html with a character set of UTF-8. This is simply good practice to avoid any possible confusion where we don't set this explicitly in the application. If this is not set, the default Content-Type set in php.ini may be used instead. Once the environment and Front Controller are configured, the final task is adding similar defaults to our Views. At present, Zend_View, in stark contrast to other components, uses a default character set of ISO-8859-1 (as do View Helpers) which simply won't do if you are going to output multibyte characters (like the one in my name!). This is something of an annoyance and a long standing bug preserved to avoid backwards compatibility problems with older Zend Framework versions. To change the default, we should instantiate a new instance of Zend_View, set a more appropriate default character encoding of UTF-8, and implant this altered View object into the ViewRenderer Action Helper. This helper is used automatically by all Controllers to contain and manage Views, so by explicitly setting a View object to use we avoid having an unmodified View instantiated instead. Under the presumption we will output HTML by default, it's also strongly recommended to set a default HTML DOCTYPE since this setting feeds into other components (Zend_Form, for example) when they are being rendered. Again, this is an annoyance if you forget it since you can have forms being generated under a different doctype, for example. Obviously, we'd prefer that anything rendered to HTML follows the application View's preferred DOCTYPE. As you can see, writing a Bootstrap is a bit like waging war. It never survives first contact, and you will continually find yourself adding paths, configurations and other tweaks over time. The above presents one possible baseline but in the next chapter we'll meet Zend_Application which allows for a more standard approach.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (7 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

5.5. Step 4: The Only Way In, The Index File


We have a Bootstrap in place but it's only useful if we execute it somewhere. As we covered previously, all Model-View-Controller (MVC) applications have a single point of entry into an application. In the case of PHP, this is invariably a file called index.php, the Index file. Since it's loaded automatically by nearly every web server supporting PHP, if the path to no other file is given, it's the place where our application is first loaded. It's also the place where we may make any manual PHP include_path changes needed for the local server environment (other than those paths our bootstrap can autoload by design). I don't intend breaking the PEAR Convention however without good cause, so the only thing we must do in index.php is run the Bootstrap! Create index.php in your project's /public directory containing:
q q q q q

<?php require realpath('..') . '/library/ZFExt/Bootstrap.php'; ZFExt_Bootstrap::run();

If you are using libraries with non-conventional include paths, you would need to add those include paths here or configure them from the bootstrap class. For the moment though, everything we are using can be autoloaded by the bootstrap.

Note The PEAR Convention is a simple one. It enforces a rule where the name of a class reflects its relative path. So Zend_Controller_Front should be a class contained in a file whose path matches the relative path Zend/Controller/
Front.php somewhere on your include_path.

We have one small problem with our theoretical single entry point - it defies how HTTP works in practice. How can we communicate the details of where in the application we are trying to reach, if the only file available is index.php? We need to be able to use a URL which, by default, follows the form http://domain.tld/controllername/actionname so the application's Router can call the

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (8 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

correct action method on the correct Controller class. On any web server this is doomed to failure, because they will believe we are trying the reach an actual filepath matching the URL's path element, i.e. the directory controllername/actionname/. To get around this, we need to pass the URL path to index.php, and ensure the web server does understand we are indeed always requesting through index.php. Luckily web servers offer a feature called URL Rewriting which allows us to transform all URLs (matching certain criteria) into a new URL which is mapped to index.php to create a final URL more along the lines of http://domain.tld/index.php/controllername/actionname. Obviously this criteria must exclude all resource URLs we don't want passed to the application as a mere parameter like our stylesheets, javascript and images. These must always be served directly. Assuming you're using Apache as your web server, you may need to turn on URL Rewriting by enabling the rewrite module. This module is disabled by default on many newly installed Apache instances. This involves editing Apache's configuration file, or on Ubuntu simply by using the a2enmod command (which is pretty handy). If you are using Ubuntu try this:

sudo a2enmod rewrite


The command simply plays with a symlink and adds the rewrite configuration file so it's autoloaded as part of Apache's overall configuration. You need to reload Apache for any module changes to take effect which is performed on Ubuntu using:

sudo /etc/init.d/apache2 reload


If you need to manually edit the configuration file, you normally just need to add something like the following line (reload Apache afterwards):

LoadModule rewrite_module modules/mod_rewrite.so


http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (9 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

For those not using Apache, please consult the Reference Guide section on Zend_Controller to find instructions specific to your setup. To make use of URL Rewriting, add the following file called .htaccess to /public:

RewriteEngine On RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^.*$ - [NC,L] RewriteRule ^.*$ /index.php [NC,L]
If you intend hosting your application within a subdirectory, you should also add a RewriteBase line to ensure the rules exclude this portion of the URL, and only take the path element after that point when rewriting it onto index.php.

RewriteEngine On RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteBase /subdirectory/ RewriteRule ^.*$ - [NC,L] RewriteRule ^.*$ /index.php [NC,L]
The .htaccess file sets up some rewrite rules which ensure all requests are mapped onto our application's index file when appropriate. It's designed to map all URLs except those dictated by the preceding collection of rewrite conditions - namely if the URL refers to a file (with size greater than zero), directory or symbolic link. This makes sure your javascript, css, and other non-PHP files can be served directly even if centralised elsewhere and referenced only by symbolic links.

Note

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (10 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

If you want to gain a tiny bit of speed, it's often a good idea to enter the contents of the .htaccess file into your Apache configuration for that Directory (we configured this Directory when adding a new Virtual Host earlier). Then you can delete the .htaccess file, and Apache won't have to parse it with every request since it's loaded permenantly when Apache starts. You will however need to restart Apache whenever you want to make changes to the rewrite rules.

5.6. Step 5: Adding A Default Controller and View


When we were discussing Model-View-Controller (MVC), we noted that the Controller was the part responsible for the wiring of applications allowing user input to be mapped to the Model and Models mapped to the View as needed. The Controller is therefore one of the first components you'll deal with for every new feature you add to an application. In the Zend Framework, all Controllers are ultimately subclasses of Zend_Controller_Action. This base class enforces a few default conventions which you should keep in mind. For example, it was decided to make automated rendering of Views the default behaviour of all Controllers. This means you don't need to worry about manually ordering View templates to render - instead an Action Helper called the View Renderer (Zend_Controller_Action_Helper_ViewRenderer) is called upon. In our previous bootstrap class we adjusted the default behaviour further by ensuring the View Renderer utilises a Zend_View instance which has a default character encoding of UTF-8 (rather than ISO-8859-1). There are ways to disable automated View rendering as documented in the Reference Guide which is often useful when you need to bypass Zend_View (e.g. to output final JSON or XML documents which aren't template based and don't need further processing). Determining which Controller to use is the job of the Router (Zend_Controller_Router_Route and related classes) which uses the value of the URL string which contains the Controller classname and method to be used, or which maps to a configured Route which supplies the Controller and Action names to default to. In the absence of any such information, the Router will assume a default

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (11 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

"index" value for each. Since we only intend requesting to the root URL of http://helloworld.tld, we'll need an Index Controller containing an Index Action, i.e. a Controller which can service the Router's defaults which assumes the URL is actually identical to http://helloworld.tld/index/index Here's the source code for /application/controllers/IndexController.php:
q q q q q q q q q q q q q q

<?php class IndexController extends Zend_Controller_Action { public function init() { } public function indexAction() { } }

The init() method can be ignored since it's only needed for setup tasks specific to this Controller, and we have none of those. The more relevant indexAction() method has no content, which is fine, since we only need content if interacting with a Model or some other collection of classes. Right now, there is none of that. All that this does really is act as a stub method which tells the ViewRenderer Action Helper to render a View with a similar name. To explain the mapping, an Index Controller with an indexAction() method maps to a View template located at /application/views/scripts/index/index.phtml. So ErrorController::
indexAction() would map to /application/views/scripts/error/index.phtml and so forth.

It's a simple convention. Let's create a template for IndexController::indexAction() located at /


application/views/scripts/index/index.phtml:
q q q

q q

q q q q q q q

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="language" content="en" /> <title><?php echo $this->escape($this->title) ?></title> </head> <body> <p>Hello, World!</p> </body> </html>
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (12 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

In Zend Framework, templates work by virtue of the fact they are included into Zend_View's object scope. So all properties and methods accessible by Zend_View, are accessible from the templates using $this (to reference the current Zend_View object whose variable scope applies) This is a perfectly valid template that works but something we will meet in greater detail later is the concept of View Helpers. In brief, a View Helper encapulates commonly used presentation related tasks in helper classes. For example, are we going to add the Doctype string to dozens of templates? If we do that, and it changes, we'll have dozens of templates to edit. No, we're not.
Zend_View has a helper which we used in the bootstrap to set a default Doctype. The same way

that we used another helper in the bootstrap to append a http-equiv header with a default ContentType to the head section's meta information. Using these two helpers, which can be echoed out directly since they indirectly implement PHP's magic __string() method, we can reduce the template to:
q q

q q q q q q q q q

<?php echo $this->doctype() ?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <?php echo $this->headMeta() ?> <meta name="language" content="en" /> <title><?php echo $this->escape($this->title) ?></title> </head> <body> <p>Hello, World!</p> </body> </html>

Even now, we still have "en" in three different places that we could also automate with a View Helper to ensure they are updated depending on our content's language. This demonstrates why View Helpers are useful - we can offload tasks concerning changes to the View to them so they are reusable many times across different Views. It's not important to understand View Helpers in full just yet, you really only need to know that they exist! Now also edit IndexController.php to pass the title of the page (referenced in the view script as
$this->title) to the View.
q q q q

<?php class IndexController extends Zend_Controller_Action {

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (13 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End
q q q q q q q

public function indexAction() { $this->view->title = 'Hello World!'; } }

All controllers must extend Zend_Controller_Action, since it contains all later referenced internal methods and properties common to all Controllers. If you need to tweak the basic Controller to add new methods or properties you may do so using a subclass of Zend_Controller_Action and by then making all application Controllers extend this subclass instead. Be very careful however in subclassing this way - it's useful for adding new properties to the class, but additional methods are often better added as discrete Action Helpers to prevent the development of a parent Controller class that is little more than a hard to maintain heap of loosely related spaghetti strands. Our new Action method ignores rendering; it's done automatically as discussed earlier using the View Renderer that's enabled by default. All we need to do is set the View data we need. You can add almost anything to a View in this manner, since it just becomes a property in a View template. So feel free to add Objects and Arrays. If using Smarty or PHPTAL, this might be different. This flows back to the concept of the View using Models discussed earlier - a Model is an object, usually, so there's no reason to restrict Views to mere arrays when it's more convenient to simply pass in an object. Above we've told the View that templates might refer to a local public class property "title" and given it a value of "Hello, World!". Once the Action method is done, the View Renderer will kick in and try to render a template located at /application/views/scripts/index/index.phtml. Inside the view script we also escape the title on the basis that it could be unsafe. It's pretty much obligatory to escape everything in this manner if being output as HTML. Internally, Zend_View uses
htmlspecialchars() by default and passes it the current instance's character encoding setting. It's

also one of those little annoyances - in Zend Framework 2.0 this escaping will be done automatically without the need to continually call the escape() method.

5.7. Conclusion
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (14 de 15)28/10/2011 12:53:15

Chapter 5. A Not So Simple Hello World Tutorial - Zend Framework Book: Surviving The Deep End

In this chapter we've covered a relatively simple (aha!) example of how the Zend Framework works when building an application. It also emphasises the importance of the bootstrapping since this is where nearly all our setup happens, including the setting of defaults and any configuration work needed for Zend Framework components is centered. Throughout this book, managing custom plugins, action helpers and other similar non-standard Zend Framework classes occurs in the Bootstrap since this is the most logical place to drive their configuration and inclusion in an application. In the next chapter we'll cover one obvious omission to anyone keeping up to the date with recent Zend Framework versions: how to manage this bootstrap complexity using Zend_Application, before moving on to explaining how we can handle application errors. Shortly after, we'll start delving into an actual application since I desperately need a replacement for my blog and it so happens to be a simple application where we can explore other Zend Framework components in greater depth.

Prev Chapter 4. Installing The Zend Framework Home

Next Chapter 6. Standardise The Bootstrap Class With Zend_Application

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/a.not.so.simple.hello.world.tutorial (15 de 15)28/10/2011 12:53:15

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 6. Standardise The Bootstrap Class With Zend_Application


Table of Contents 6.1. Introduction 6.2. Step 1: Editing the ZFExt_Bootstrap Class 6.3. Step 2: Editing The Index and htaccess Files 6.4. Step 3: Adding The Application Configuration File 6.5. Step 4: Handling Setting Of Standard Component Defaults 6.6. Step 5: Fixing ZFExt_Bootstrap 6.7. Step 6: Integrating Application Configuration Into Resource Methods 6.8. Step 7: Optimising Autoloading Code 6.9. Allowing Zend_Loader_Autoload Load Namespaced Classes

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (1 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

6.10. Conclusion

6.1. Introduction
Up until recently, creating and managing the bootstrapping of a Zend Framework application was strictly a job for the developer's imagination. This encouraged a diverse set of practices from using monolithic procedural scripts to classes of any structure or complexity, not to mention it doubtlessly made designing a command line tool very difficult. With the introduction of Zend_Application, the practice of bootstrapping now has a common base usable in any application by any developer reinforcing a move towards standardisation of this application need. Now if only they had made that obvious called it Zend_Bootstrap...
Zend_Application was designed to be modular, customisable and configurable with a minimum of

trouble. To get us underway, since we'll be using Zend_Application throughout the book (a lot!), lets revisit our previous Hello World example, and rearchitect the ZFExt_Bootstrap class. One thing I will not change is the location of our bootstrap which will remain at /library/ZFExt/
Bootstrap.php. There's no particular reason why we should keep it here other than the ZFExt

namespace will form the basis for our generic application library where we will keep any Zend Framework specific custom classes for potential reuse across other applications in the future.

6.2. Step 1: Editing the ZFExt_Bootstrap Class


The edits needed for our previous bootstrap class start off simple:
q

q q

class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap { }

We will need to make some changes to reflect what we were achieving with the original
ZFExt_Bootstrap class, but if you had no changes your requirements for editing a bootstrap could

stop here. Without any additional code, Zend_Application can setup everything needed using each necessary component's defaults. Here, we're extending Zend_Application_Bootstrap_Bootstrap which extends
Zend_Application_Bootstrap_BootstrapAbstract and offers everything needed for a typical

bootstrap setup, such as loading Resources, dispatching through the Front Controller, and some

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (2 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

checking to ensure we have a proper mode of operation (i.e. production or development) set.

6.3. Step 2: Editing The Index and htaccess Files


As before, our index.php file in /public is where we first include and execute bootstrapping. We need to add a little more complexity here. This includes setting constants to hold some application paths, loading of a configuration to drive the bootstrapping of our environment setup, and running the bootstrapping itself.
q q q q q q q q q

<?php if (!defined('APPLICATION_PATH')) { define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application')); } if (!defined('APPLICATION_ROOT')) { define('APPLICATION_ROOT', realpath(dirname(__FILE__) . '/..')); } if (!defined('APPLICATION_ENV')) { if (getenv('APPLICATION_ENV')) { $env = getenv('APPLICATION_ENV'); } else { $env = 'production'; } define('APPLICATION_ENV', $env); } set_include_path( APPLICATION_ROOT . '/library' . PATH_SEPARATOR . APPLICATION_ROOT . '/vendor' . PATH_SEPARATOR . get_include_path() ); require_once 'Zend/Application.php'; $application = new Zend_Application( APPLICATION_ENV, APPLICATION_ROOT . '/config/application.ini' ); $application->bootstrap()->run();

q q q q q q q q q q q q q q q q q q q q q q q q q

Here we discover a continued focus on using index.php as the location for environmental specific setup. In index.php we create a number of constants, setup our include_path and run the bootstrapping process. Internally, Zend_Application loads up our ZFExt_Bootstrap class which we will configure in an application configuration file at /config/application.ini to be executed.
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (3 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

Even though our class is empty, it still extends an Abstract class holding all the needed methods. One thing to note is that these constants need to be treated with some responsibility. Constants are global values accessible from anywhere in the application, and like any global variable their use must be minimised and handled with care because there is always the temptation to use them everywhere. Other applications may not even create these constants, so any code relying on them may not be reusable. A far better solution outside of index.php is to keep on using dirname() and
realpath() and/or passing in these variables via a registry object or as Front Controller

parameters accessible from your Controller actions. In a slight departure from the Reference Guide, I maintain index.php according to the Zend Framework Coding Standard. It's preferably not to use shorthand or abbreviated statement forms since they are not very readable. You will notice that our index.php file now appears to default to a "production" configuration in the absence of an environmental value assigned to the constant APPLICATION_ENV. Obviously, we don't want to develop using a production configuration unless we enjoy debugging applications that mysteriously fail to display their errors, but we can solve this by adding the necessary environmental value to our .htaccess file:

SetEnv APPLICATION_ENV development RewriteEngine On RewriteCond %{REQUEST_FILENAME} -s [OR] RewriteCond %{REQUEST_FILENAME} -l [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^.*$ - [NC,L] RewriteRule ^.*$ /index.php [NC,L]
This works for Apache, for other web servers please consult their documentation.

6.4. Step 3: Adding The Application Configuration File


http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (4 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

One of the main advantages of using Zend_Application, is that once you remove the need for custom code you can focus your efforts only on setting up component defaults and driving the rest through configuration. As our index.php content above suggests, we should add a configuration file at /config/application.ini which contains:

[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_ROOT "/library/ZFExt/ Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1
Here, the configuration file is split into four distinct modes: production, staging, testing and development. All modes inherit all values from production, but may set exceptions. For example, production settings disable display_errors while testing and development modes ensure they are enabled. You can be sure that Zend_Application will load the correct mode based on the value we've given it for the APPLICATION_ENV constant set in index.php. The INI values used may seem a bit strange since they show our PHP constants followed by a space and then a double quoted relative path from the absolute path these constants define.
Zend_Application will handle the concatenation of these two parts internally so just be aware that

you can setup paths in this manner for the component. In fact you can also setup arrays from INI configuration files also, as we'll see in later chapters.
Zend_Application divides all configurations into a few categories shown on the configuration file

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (5 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

as prefixes. "phpSettings" refers to anything you can set using ini_set() from PHP. "includePaths" contains any path you want to add to the PHP include_path value (besides those set from index.php). For example, I could use:

includePaths.foolib = APPLICATION_ROOT "/foolib/lib"


Instead I set the include path from index.php more directly for the standard /library and /vendor directories but you may have others that need to be added. "bootstrap" tells Zend_Application where our Bootstrap class is located, and what its classname is (remember we still need our
ZFExt_Bootstrap class to change how bootstrapping sets up some components).

Finally we have "resource" which refers to Zend_Application Resources. Basically a Zend_Application Resource is any class Zend_Application is aware of and which it will configure for use during bootstrapping. Above we set options for a FrontController (the option name lowercases the first letter) to configure the instance of Zend_Controller_Front that
Zend_Application will utilise when running our application. For now, we're only telling Zend_Controller_Front where to find our default directory containing Controller classes, and

whether it should throw Exceptions when in the development or testing modes. If the configuration convention is confusing, "controllerDirectory" maps to Zend_Controller_Front::
setControllerDirectory(). Just as if we were using an array of configuration values, using the

same convention to map array keys to the setter methods that each key corresponds to.

6.5. Step 4: Handling Setting Of Standard Component Defaults


In our original ZFExt_Bootstrap class, I made a point that some components are configured by default in a way we may find unsuitable for use. For example, Zend_View uses a default character encoding of ISO-8859-1 which is great, so long as you avoid accented or multibyte characters. You can customise the default Zend_View instance just as we did originally using Zend_Application, and in much the same way too!
q q q

<?php class ZFExt_Bootstrap extends


http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (6 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

q q q q q q q q q q q q q q q q q q q

Zend_Application_Bootstrap_Bootstrap { protected function _initView() { $view = new Zend_View(); $view->setEncoding('UTF-8'); $view->doctype('XHTML1_STRICT'); $view->headMeta()->appendHttpEquiv( 'Content-Type', 'text/html;charset=utf-8' ); $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' ); $viewRenderer->setView($view); return $view; } }

Here we see that our revised Bootstrap class can configure a Zend_View instance by calling
_initView(), something referred to a Resource Method, since it sets up or modifies a Resource

for use. Here we are setting up a new Zend_View instance for use by the ViewRenderer Action Helper (we'll meet Action Helpers later) where it acts as the application's default Zend_View instance for rendering templates. All Resource Methods to be run must follow the format "_initResourceName()" if a protected method. You can do the same using public methods by defining such methods in the form "bootstrapResourceName()". Whether you use protected or public methods is your decision. Inside these Resource Methods we can do a bit more by using Zend_Application's Resource system. Basically, Zend_Application defines a number of classes called Resource Plugins which it uses to setup Resources as an alternative to these simple Resource Methods. So, what is a Resource after all this? Resource is something of a confusing term since it doesn't tell the entire story. The best way to think about it is in terms of configuring unique objects. Our application generally only requires one of each object when bootstrapping. It needs one instance of Zend_View, one instance of Zend_Controller_Front, one instance of a Router, etc. So a Resource is unique - one of a kind. The set of actions needed to create, configure and register these unique resources with Zend_Application (and the bootstrap class) come in two distinct alternative forms: Resource Methods, and Resource Plugins. Both of these follow a convention whereby the

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (7 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

Resource they apply to is used in the method name for a Resource Method or the classname for a Resource Plugin. So, _initView() creates a Resource called View. But wait, there's also a Resource Plugin (Zend_Application_Resource_View) which may also create a Resource called View. Can we have two View Resources? The answer is no - we can have one and one only. By defining a Resource Method, we effectively override any Resource Plugin applicable to the same Resource. A Resource Plugin is any class which extends Zend_Application_Resource_ResourceAbstract (or Zend_Application_Resource_Resource) which carries out the actual initialisation, configuration and passing of such classes as objects to the Front Controller. A Resource Method is like a Resource Plugin, but it's defined in the bootstrap class instead of extracted into its own separate class. To create new objects for use during bootstrapping, you may therefore add custom Resource Plugin classes for this purpose or Resource Methods as a short alternative. Of course, Zend_Application will automatically handle some standard classes your application may require without interference. The Resource Plugins pre-packaged with Zend_Application include:

Zend_Application_Resource_Db Zend_Application_Resource_FrontController Zend_Application_Resource_Router Zend_Application_Resource_Modules Zend_Application_Resource_Navigation Zend_Application_Resource_Session Zend_Application_Resource_View Zend_Application_Resource_Layout Zend_Application_Resource_Locale Zend_Application_Resource_Translate

Note

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (8 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

Several of these are omitted from the Reference Guide as of Zend Framework 1.9.0. Only those absolutely necessary lead to initialised Resources, so Zend_Application_Resource_Db will not immediately go and complain about a missing configuration setting because it's not run by default until you configure it.

In our _initView() method we were creating a replacement or substitute Zend_View object, because Zend_Application_Resource_View will just create a default instance with the ISO-8859-1 character encoding and other default options. By returning the new Zend_View instance from
_initView(), Zend_Application will accept the replacement and will not attempt to overwrite our

changes by running Zend_Application_Resource_View to set up a standard default Zend_View instance with the flaws we just corrected. When creating a new Resource to replace another one, we don't need to retrieve the original one setup by the default Resource Plugin in Zend_Application unless it's needed. Let's now add another resource method, _initFrontController(), which only changes some settings on an existing resource as setup by Zend_Application_Resource_Frontcontroller.
q q q

<?php class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap { protected function _initView() { $view = new Zend_View(); $view->setEncoding('UTF-8'); $view->doctype('XHTML1_STRICT'); $view->headMeta()->appendHttpEquiv( 'Content-Type', 'text/html;charset=utf-8' ); $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' ); $viewRenderer->setView($view); return $view; } protected function _initFrontController()

q q q q q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (9 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q

{ $this->bootstrap('FrontController'); $front = $this->getResource('FrontController'); $response = new Zend_Controller_Response_Http; $response->setHeader('Content-Type', 'text/html; charset=UTF-8', true); $front->setResponse($response); } }

In our original bootstrap class, we wanted to ensure all responses defaulted to using a ContentType header value of "text/html; charset=UTF-8". Here, we do the same thing by using the
getResource() method to retrieve an instance of Zend_Controller_Front created and configured

by Zend_Application_Resource_Frontcontroller and merely ensure it uses our own Response object instead of creating a default one when needed. Before retrieving the FrontController Resource, i.e. an object of type Zend_Controller_Front, we first need to make sure Zend_Application does everything needed to create it in the first place. This is done by passing the Resource Name (i.e. the final term in it's corresponding Resource Plugin class name) to the bootstrap() method. This forces Zend_Application to bootstrap just this Resource. The bootstrap() method is pretty flexible and accepts an array of Resource Names you need to have available right now without waiting for an overall bootstrap execution. You can take this a step further by simply replacing the default Resource Plugins. I won't cover it here since we'll be covering adding custom Resource Plugins for other objects used in our application later in the book. For now compare the original ZFExt_Bootstrap class with its revised version. Less code, configuration driven, easy to work with, and extendable via Resource Plugins. The new version is an obvious improvement. Of course, understanding how it works is yet another hurdle on the way to becoming a Zend Framework guru, but that's always the cost when using more abstract classes which are nevertheless more extendable and reusable than their monolithic alternatives.

6.6. Step 5: Fixing ZFExt_Bootstrap


If you just try accessing http://helloworld.tld once more, you will notice something odd. There is now a rather anonymous Exception being thrown stating "Circular resource dependency
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (10 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

detected". This occurs because we defined a resource method _initFrontController() which conflicts with Zend_Application_Resource_Frontcontroller when calling bootstrap('FrontController'). Effectively we broke the rule about Resources being unique - by attempting to use the Resource Method and forcing the use of the Resource Plugin (which bootstrap() runs) Zend_Application interpreted this as an attempt to create two similar objects when we only needed one. Until we get used to thinking about bootstrap work in terms of unique Resources, this will be a common mistake. This is really to emphasis the importance of Resource Methods vs Resource Plugins (as well as to explain this anonymous little gem when starting with Zend_Application). You can use one or the other, but never both with the same Resource Name at the same time. Our previous _initView() method worked because there is no reference to Zend_Application_Resource_View used - our Resource Method replaces it. If we had been trying to merely change the object generated by
Zend_Application_Resource_View using the bootstrap() and getResource() methods to

generate and retrieve the object, we would have had to rename the Resource Method to reflect that we were building on the Plugin initialised Resource by creating a modified version of it (effectively a different Resource even if the difference was mere configuration). This illustrates an important point - even though Zend_Application speaks about Resources, you can have a single object represented by two or more named Resources. It seems a bit silly, since at the end of the day just that one object is used regardless of how many Resource names apparently refer to it. This is really a simple contrivance though - in order to modify objects after their initial setup from Plugins or Methods (not recommended - method order is tricky), we must use a different Resource name when doing so. It doesn't need to make sense, it's just the way it works. In _initFrontController(), we made the mistake of believing we could modify an existing Front Controller by retrieving it from Zend_Application_Resource_FrontController, without realising we were doing this in a Resource Method using the same resource name. The moment one Resource unit tries to use another similar named Resource unit, confusion reigns since we cannot have two objects representing the same Resource, so Zend_Application throws an Exception complaining about the dependency mess we tried creating. This is a deliberate safety net.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (11 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

To solve this conflict, we should rename any Resource Methods not intended to replace a Resource Plugin to something a bit more unique. In this case I've decided to prefix the existing Resource Name "FrontController" with "Modified" indicating that our Resource Method does not override a Resource Plugin, i.e. we may use the results of that Resource Plugin when it runs rather than do our own separate creation work. We also do not return the object since the Resource Plugin will register the original object for use anyway.
q q q

<?php class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap { protected function _initView() { $view = new Zend_View(); $view->setEncoding('UTF-8'); $view->doctype('XHTML1_STRICT'); $view->headMeta()->appendHttpEquiv( 'Content-Type', 'text/html;charset=utf-8' ); $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper( 'ViewRenderer' ); $viewRenderer->setView($view); return $view; } protected function _initModifiedFrontController() { $this->bootstrap('FrontController'); $front = $this->getResource('FrontController'); $response = new Zend_Controller_Response_Http; $response->setHeader('Content-Type', 'text/html; charset=UTF-8', true); $front->setResponse($response); } }

q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

Now we can try http://helloworld.tld once more without meeting any errors or exceptions.

6.7. Step 6: Integrating Application Configuration Into Resource Methods


In our application configuration file, application.ini, we noted that we can define a configuration for Resources using the "resources" prefix. These are automatically used by the Zend_Application
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (12 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

Resource Plugins to pass options into the objects they create. So for our FrontController Resource, we can add entries like:

resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"


Here the Resource name is camel cased with the first letter in lower case, so FrontController becomes frontController. This is not an immediate worry but we can clean up our replacement
Zend_View Resource Method a little by ensuring any configuration options added to application.ini

are passed to the new instance. If nothing else, it also lets us push the setting of a character encoding back into the configuration file.
q q q

<?php class ZFExt_Bootstrap extends Zend_Application_Bootstrap_Bootstrap { public static function autoload($class) { include str_replace('_', '/', $class) . '.php'; return $class; } protected function _initView() { $options = $this->getOptions(); if (isset($options['resources']['view'])) { $view = new Zend_View($options['resources'] ['view']); } else { $view = new Zend_View; } if (isset($options['resources']['view']['doctype'])) { $view->doctype($options['resources']['view'] ['doctype']); } if (isset($options['resources']['view'] ['contentType'])) { $view->headMeta()->appendHttpEquiv('Content-Type', $options['resources']['view']['contentType']); } $viewRenderer = Zend_Controller_Action_HelperBroker:: getStaticHelper( 'ViewRenderer' ); $viewRenderer->setView($view); return $view; }
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (13 de 18)28/10/2011 12:53:26

q q q q q q q q q q q q

q q q q q

q q

q q q q

q q q q q

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End
q q q q q

q q q q q q q

q q q q

protected function _initModifiedFrontController() { $options = $this->getOptions(); if (!isset($options['resources'] ['modifiedFrontController']['contentType'])) { return; } $this->bootstrap('FrontController'); $front = $this->getResource('FrontController'); $response = new Zend_Controller_Response_Http; $response->setHeader('Content-Type', $options['resources']['modifiedFrontController'] ['contentType'], true); $front->setResponse($response); } }

Now we can delegate setting the character encoding and any custom configuration options that we require to our application.ini settings.

[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_ROOT "/library/ZFExt/ Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.view.doctype = "XHTML1_STRICT" resources.view.contentType = "text/html;charset=utf-8" resources.modifiedFrontController.contentType = "text/ html;charset=utf-8" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1

6.8. Step 7: Optimising Autoloading Code


http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (14 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

In our previous bootstrap class, we created a custom autoload method for use which did no file checking and simply attempted an include() operation assuming all classnames mapped to their filenames using the PEAR Convention. This was to avoid a method Zend_Loader::loadClass() from the more typical autoloader used in Zend Framework applications which does a lot of mostly unnecessary error checking. Zend_Application is designed to also implement autoloading using another new component, Zend_Loader_Autoloader. Unfortunately, Zend_Loader_Autoloader (which adds vastly improved class autoloading features) uses the same Zend_Loader::loadClass
() method we wished to avoid.

We can still implement our lighter method, but not from within the bootstrap class anymore. Rather we need to reset the Zend_Loader_Autoloader's default loading method before
Zend_Application is initialised, and this can only be accomplished from index.php.
q q q q q q q q q

<?php if (!defined('APPLICATION_PATH')) { define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application')); } if (!defined('APPLICATION_ROOT')) { define('APPLICATION_ROOT', realpath(dirname(__FILE__) . '/..')); } if (!defined('APPLICATION_ENV')) { if (getenv('APPLICATION_ENV')) { $env = getenv('APPLICATION_ENV'); } else { $env = 'production'; } define('APPLICATION_ENV', $env); } set_include_path( APPLICATION_ROOT . '/library' . PATH_SEPARATOR . APPLICATION_ROOT . '/vendor' . PATH_SEPARATOR . get_include_path() ); require_once 'Zend/Loader/Autoloader.php'; $autoloader = Zend_Loader_Autoloader::getInstance(); $autoloader->setDefaultAutoloader(create_function('$class', "include str_replace('_', '/', \$class) . '.php';" )); $application = new Zend_Application( APPLICATION_ENV,

q q q q q q q q q q q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (15 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End
q q q q

APPLICATION_ROOT . '/config/application.ini' ); $application->bootstrap()->run();

Since we can't even reference the bootstrap before Zend_Application is initialised, we may instead pass setDefaultAutoloader() an anonymous function we create on the spot.

6.9. Allowing Zend_Loader_Autoload Load Namespaced Classes


In PHP, where libraries are following the PEAR convention, all class names carry a common prefix commonly referred to as the "namespace" (this is not true namespacing as PHP 5.3 introduces). Our autoloader is configured to recognise class name namespaces including "Zend_" and "ZendX_". In order to load other namespaced classes, we first need to tell the autoloader that they exist. Before that we can ask the question why is this even required in the first place? The problem with the original autoloading method was simple. It would load anything. At a glance this seems really great, it's simple, easy to understand and requires no configuration. So how is this not a good thing? The problem with the load anything behaviour is that it affords no control. Sometimes you want to make sure only certain libraries can be loaded. As an added benefit, the restriction also leads to faster lookups. The new namespacing feature isn't perfect but that's mostly because some libraries do not have a top level namespace prefix. For example, PEAR components do not begin with PEAR_ so you can have HTTP_Client, Crypt_RSA, etc. which share no common prefix. A similar problem exists with any "root" classes, for example HTMLPurifier uses the namespace prefix HTMLPurifer_ except for a root class in HTMLPurifier.php. In these circumstances, you may need to return to the original behaviour where namespaces are untracked and unrestricted using:
q

Zend_Loader_Autoloader::getInstance()->setFallbackAutoloader (true);

In other circumstances, a library may use its own autoloader which you can register as an alternative to the default one implemented by Zend_Loader_Autoloader.
q

$autoloader = Zend_Loader_Autoloader::getInstance();
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (16 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End
q

$autoloader->pushAutoloader('HTMLPurifier_Bootstrap', 'autoload');

Note that this, because of HTMLPurifier's lack of a namespace, adds a new global autoloader a bit like the fallback option makes the default autoloader operate globally without any restriction. You can pass in a namespace restricted autoloader in a similar fashion so it's only called when a specific class namespace is detected.
q q

$autoloader = Zend_Loader_Autoloader::getInstance(); $autoloader->pushAutoloader('XLib_Autoloader', 'autoload', 'XLib_');

However, let's assume for now this fallback and other autloaders are not needed. The outcome of the new autoloader is that we need to register all class namespaces we intend using. While we can do this in code from index.php, it's easier to track it in our application.ini file which makes it easy to add namespaces as needed. Here's an example where we intend using a library (our future application library actually) where all its class members are prefixed with "ZFExt_". As you can probably see, you can keep adding namespaces this way as the square brackets [], indicate they are added to an array once the INI file is parsed by Zend_Config (which handles all configuration parsing and loading within the framework.

[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_ROOT "/library/ZFExt/ Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.view.doctype = "XHTML1_STRICT" resources.view.contentType = "text/html;charset=utf-8" resources.modifiedFrontController.contentType = "text/ html;charset=utf-8" autoloaderNamespaces[] = "ZFExt_" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (17 de 18)28/10/2011 12:53:26

Chapter 6. Standardise The Bootstrap Class With Zend_Application - Zend Framework Book: Surviving The Deep End

[development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1

6.10. Conclusion
This was a quick introduction (well, you could always learn to speed read) to Zend_Application. As someone who has been doing my own thing quietly I'm very happy to adopt it for use. It ensures other developers can follow my bootstrapping process without learning a whole new approach (from the hundreds probably existing in the wild). This is the strength of finally having a standard approach to bootstrapping applications. We will see more of Zend_Application as the book progresses since there are lots of objects and other pieces of altered behaviour we may require in our applications, and the bootstrap is where initialising these for use is the best approach. On a final note, Zend_Tool which is not covered by the book yet, uses Zend_Application when managing applications.

Prev Chapter 5. A Not So Simple Hello World Tutorial Home

Next Chapter 7. Handling Application Errors Gracefully

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/standardise.the.bootstrap.class.with.zend.application (18 de 18)28/10/2011 12:53:26

Chapter 7. Handling Application Errors Gracefully - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 7. Handling Application Errors Gracefully


Table of Contents 7.1. Introduction 7.2. The ErrorController and Error View 7.3. Well, That Didn't Work... 7.4. Not All Errors Are Equal 7.5. Conclusion

7.1. Introduction
Our simple little application is growing up! To finish off this section of the book, designed to familiarise you with the Zend Framework and its basic nuts and bolts when setting up applications,

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/handling.application.errors.gracefully (1 de 7)28/10/2011 12:53:36

Chapter 7. Handling Application Errors Gracefully - Zend Framework Book: Surviving The Deep End

let's take a look at error handling. As we configured in the last chapter, Zend_Controller_Front will only throw exceptions when in development or testing mode. In production all errors and exceptions should not be displayed to users. However, when something does go wrong, we can't just leave a user with a blank browser screen and a bad case of bemusement steadily degrading into irritation. We should instead inform the user that a problem does exist, that we're extremely sorry, will torture the developer responsible as soon as possible and hopefully resolve this little glitch as soon as the torture session has had its desired effect. To accomplish this, Zend_Controller_Front assumes that we will create a new Controller, ErrorController to be exact, to handle these situations. Failure to create an ErrorController will result in our application throwing a fit about it being missed, especially when the error was from a URL that points to a non existing location that should return a 404. This handling of Exceptions is performed by a Front Controller plugin called
Zend_Controller_Plugin_ErrorHandler.

7.2. The ErrorController and Error View


We'll keep our error reporting to the user as simple as possible with this minimal class.
q q q q q q q q q q

<?php class ErrorController extends Zend_Controller_Action { public function errorAction() { } }

As we covered previously, all Controller actions will automatically render a related view. Since we have an errorAction() method on an ErrorController, this means a view script at /
application/views/scripts/error/error.phtml will be rendered. If it exists - so add it now!
q q

<?php echo $this->doctype() ?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head>


http://www.survivethedeepend.com/zendframeworkbook/en/1.0/handling.application.errors.gracefully (2 de 7)28/10/2011 12:53:36

Chapter 7. Handling Application Errors Gracefully - Zend Framework Book: Surviving The Deep End
q q q q q q q

q q

q q q

<?php echo $this->headMeta() ?> <meta name="language" content="en" /> <title>Oops! There's Been A Problem!</title> </head> <body> <h1>An Error Has Occurred</h1> <p>We're truly sorry, but we cannot complete your request at this time.</p> <p>If it's any consolation, we have scheduled a new appointment for our development team leader in Torture Chamber #7 to encourage his colleagues to investigate this incident.</p> </body> </html>

Ah, if only it were so easy... Head back to the browser and attempt to call http://helloworld.tld/route/does/not/exist. Because this URL matches no route (and therefore no Controller) it should trigger this error message.

7.3. Well, That Didn't Work...


Instead of our error page, we instead got an actual Exception message and its details sent to the browser. The ErrorController was designed to trigger only if the Front Controller is told never to throw an Exception - except for Exceptions you have to throw because someone forgot to add
ErrorController! Actually, the infamously irritating Exception about not finding an ErrorController

comes from this situation. To test the ErrorController we need to make a temporary edit to /
config/application.ini to disable the throwing of Exceptions from Zend_Controller_Front by

changing the very last line "resources.frontController.throwExceptions" to equal zero (i.e. false). This ensures the necessary ErrorHandler plugin is allowed to serve an Exception free error page as we have created.

[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_ROOT "/library/ZFExt/ Bootstrap.php" bootstrap.class = "ZFExt_Bootstrap" resources.frontController.controllerDirectory =
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/handling.application.errors.gracefully (3 de 7)28/10/2011 12:53:36

Chapter 7. Handling Application Errors Gracefully - Zend Framework Book: Surviving The Deep End

APPLICATION_PATH "/controllers" resources.view.encoding = "UTF-8" resources.view.doctype = "XHTML1_STRICT" resources.view.contentType = "text/html;charset=utf-8" resources.modifiedFrontController.contentType = "text/ html;charset=utf-8" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 0
Important Do not forget to revert to the original setting of 1 when finished with this chapter!

Now, let's try that URL again...

7.4. Not All Errors Are Equal


You might think we can stop here and quietly escape Torture Chamber #7, but you would be wrong. If you look under the hood, our error is extremely vague and, worse, we're still responding to HTTP requests with a HTTP/1.1 200 OK header. Now call me stupid, but didn't we just admit to a failure by daring to give a user an error message? If our application experiences an error it should never, ever return such a status code and message. While this may not seem very important, it is. Not everything in our application may be served to Joe Bloggs through a browser. Some requests may come from other applications which may rely on the HTTP response code rather than your blatant cruelty to developers to detect any errors. In any case, you must contend with your own internal reviewing of applications. Will you be able to track problems when every single request is an apparent success according to your web server logs? Then there is also the great problem called RFC 2616 "Hypertext Transfer Protocol --

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/handling.application.errors.gracefully (4 de 7)28/10/2011 12:53:36

Chapter 7. Handling Application Errors Gracefully - Zend Framework Book: Surviving The Deep End

HTTP/1.1" which explains the 2xx range of status codes with "This class of status code indicates that the client's request was successfully received, understood, and accepted". In other words, the W3C have a special souped up room called Torture Chamber From Hell #1 waiting for those whose applications claim to be omnipotent. A failed routing is not a success, it means that either our routing has broken down or, far more likely, the user requested using an invalid URL. Since using the wrong URL is the user's fault, we probably shouldn't send our Team Leader to the torture chamber just yet. Instead we should explain to the user that they are wrong (politely) and include a 404 Not Found status in case the user is a machine. To accomplish this, we need to do some checking in our ErrorController.
q q q q q q q q q q q

<?php class ErrorController extends Zend_Controller_Action { public function errorAction() { $error = $this->_getParam('error_handler'); switch ($error->type) { case Zend_Controller_Plugin_ErrorHandler:: EXCEPTION_NO_CONTROLLER: case Zend_Controller_Plugin_ErrorHandler:: EXCEPTION_NO_ACTION: $this->getResponse()->setHttpResponseCode(404); $this->view->statusCode = 404; break; default: $this->getResponse()->setHttpResponseCode(500); $this->view->statusCode = 500; } } }

q q q q q q q q q q

Above, we grab the ErrorHandler plugin and retrieve its error type value. If the error corresponds to a routing problem we'll set the HTTP status code to 404 reflecting that this is not our fault and the path simply doesn't exist for our application. Otherwise, we set a HTTP status code of 500 for all other errors. The status code 500 is accompanied by the message "Internal Server Error" in the header. Let's now update our existing error template.
q q

<?php echo $this->doctype() ?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"


http://www.survivethedeepend.com/zendframeworkbook/en/1.0/handling.application.errors.gracefully (5 de 7)28/10/2011 12:53:36

Chapter 7. Handling Application Errors Gracefully - Zend Framework Book: Surviving The Deep End

q q q q q q q q q

q q

q q q

q q

q q q q

lang="en"> <head> <?php echo $this->headMeta() ?> <meta name="language" content="en" /> <title>Oops! There's Been A Problem!</title> </head> <body> <h1>An Error Has Occurred</h1> <?php if ($this->statusCode == 500): ?> <p>We're truly sorry, but we cannot complete your request at this time.</p> <p>If it's any consolation, we have scheduled a new appointment for our development team leader in Torture Chamber #7 to encourage his colleagues to investigate this incident.</p> <?php else: ?> <p>We're truly sorry, but the page you are trying to locate does not exist.</p> <p>Please double check the requested URL or consult your local Optometrist.</p> <?php endif; ?> </body> </html>

Obviously, user retention is not a primary goal of this chapter. If you check with a browser using any bad URL, you should get the relevant message along with a 404 status code in the headers. If you do something to throw a different Exception, you should receive the status code 500 response and related HTML message.

7.5. Conclusion
Handling errors is not a difficult task but it is a necessary one, especially when our applications may be visited by other application driven clients which rely on the communication of appropriate status codes and headers, and not human readable HTML pages, to assert whether a HTTP request was a success or otherwise. It is strongly recommended that you ensure your application always sends appropriate headers and codes. While the ErrorController seems fairly simple, it's easy to see that we could expand it to log errors or even send out email notifications.

Prev

Next

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/handling.application.errors.gracefully (6 de 7)28/10/2011 12:53:36

Chapter 7. Handling Application Errors Gracefully - Zend Framework Book: Surviving The Deep End

Chapter 6. Standardise The Bootstrap Class With Zend_Application

Home

Chapter 8. Developing A Blogging Application

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/handling.application.errors.gracefully (7 de 7)28/10/2011 12:53:36

Chapter 8. Developing A Blogging Application - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 8. Developing A Blogging Application


Table of Contents 8.1. Introduction 8.2. Planning 8.3. Incremental Development and YAGNI 8.4. Checking Our Toolbox 8.5. This Is Not The Reference Guide

8.1. Introduction
Now that you are familiarised with the Zend Framework, it's time to stop with the shallow end adventures and hit the deep end. Don't worry, we'll allow you to keep the floats, for now. The subject of Part 2 of the book is writing a blog application. The reason I selected a blog is
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/developing.a.blogging.application (1 de 8)28/10/2011 12:53:56

Chapter 8. Developing A Blogging Application - Zend Framework Book: Surviving The Deep End

because it's an extremely familiar application to most of us. By eliminating uncertainty over what is being developed, we can focus more on the implementation of well understood features. Nonetheless, jumping into any project blindfolded is still not a good idea. I've wanted a personal little blog I built myself (I find these things fun) for a while so let's get started.

8.2. Planning
To plan our little project, we must first ask the most obvious question. What is the goal of the project?

Our goal is to design and build a blogging application allowing the user to write articles and publish them online.

I like to keep my goals short and to the point. If its obvious, all the better. It's primary purpose is to ourselves reminded of why we're doing this and offer context in any future discussions over features. While a short goal keeps you focused on what you're trying to achieve at the end of the day, we still need to add more detail so we know where to start. Rather than get mired down in a lengthy specification document, we just need a few sketched out lines further defining how the application will operate. 1. The author writes entries through the blog interface. 2. Entries are published, once completed by the Author, to HTML pages. 3. Published entries will be distributed through aggregated RSS 2.0 and Atom 1.0 feeds. 4. Only an author may write and publish entries. 5. Readers may write comments to be displayed below entries on their HTML pages.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/developing.a.blogging.application (2 de 8)28/10/2011 12:53:56

Chapter 8. Developing A Blogging Application - Zend Framework Book: Surviving The Deep End

6. Reader comments will be distributed through aggregated RSS 2.0 and Atom 1.0 feeds. At this stage we enough material to play with. We could keep adding to the list until it's pages long but this is the core functionality that achieves our initial goal. Given our list of required features, illustrated as simple statements of what various users can do, we can start identifying the building blocks of the application. Since we are using the Zend Framework, we can take a few shortcuts and focus on what really matters - the application specific needs. The is where we find ourselves looking down the throat of Domain Modelling. Keeping it simple, we can start to identify the entities in our system, and the data associated with them. Note, it's important as this point to clarify that we are not looking at databases. Databases are a tool allowing a Model to store itself between requests, but at this stage we are not designing tables or writing SQL. We'll why this is not so in the next chapter where we delve in far greater depth into the question of how to implement the Model. The entities we could identify (and which may become our domain objects in the future) include: Author Entry Reader Comment

We might possibly eliminate Reader because in our application the Reader is an anonymous user. The only thing we might want to maintain for anonymous users is some session data. Then again, if using a database is optional in a Model, perhaps having a Reader object to encapsulate an anonymous user's session data is not a bad idea - afterall, where else will we handle this. The Controller? As we've seen in a previous chapter - the Controller is NOT the Model. The other three however are obvious candidates. We can detail these candidate entities to flesh out what data they might need at a minimum. I should note that when discussing the Model, I am
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/developing.a.blogging.application (3 de 8)28/10/2011 12:53:56

Chapter 8. Developing A Blogging Application - Zend Framework Book: Surviving The Deep End

assuming all entities will be objects, so there is no reference to foreign keys (this is a database concern) since each object can contain another object referencing any related or linked entities. Author fullname username email url password Entry title content published date author Comment name title content published date entry

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/developing.a.blogging.application (4 de 8)28/10/2011 12:53:56

Chapter 8. Developing A Blogging Application - Zend Framework Book: Surviving The Deep End

There's probably lots more we could add, but these are unimportant for the moment. We only need to see the basic data set and how it may related to other Models in our domain. The final part of this initial analysis may take a look at what sort of "pages" our application will need to generate. This is a tricky part in a sense, depending on how we defined the term "page". Do we include pages or URLs that only response to Ajax requests? What about any web service API, for example some future implementation of the AtomPub protocol so we can blog outside of the application? Again, if we're uncertain about anything, we'll defer it until a later time. So the main pages we know for certain are needed might include: Blog Index Entry Page Author Login Entry Editing Comment Approval RSS Entry Feed Atom Entry Feed RSS Comment Feed Atom Comment Feed

We now have at least some idea of what we need to do. No longer blind and staring blankly at an IDE, we can settle down and do some coding. In the next chapter, we'll start by getting our Model ready. Thereafter we can work on the application presentation.

8.3. Incremental Development and YAGNI


Any blog will boast of any number of features, tempting us to over specify our new application.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/developing.a.blogging.application (5 de 8)28/10/2011 12:53:56

Chapter 8. Developing A Blogging Application - Zend Framework Book: Surviving The Deep End

Doing so has its problems since a) we're dividing our attention, b) we're attempting to predict needs that are not confirmed, and c) extra specifications are worthless unless the core Blog application makes an appearance. So we will develop the Blog incrementally, assigning required features to iterations we will complete in discrete measured steps, all the while ensuring the application is stable and unit tested before we start another outstanding iteration. This all runs with a partner from the eXtreme Programming world, "You Ain't Gonna Need It" (or YAGNI). YAGNI teaches that dwelling too much on features that are not currently necessary has a number of severe disadvantages. First you are spending time on it, which means you either have too many developers (I never said that!) or you are having existing developers spend time on future features at the expense of currently needed features. This time can be expended on development, testing, requirements gathering, documentation, etc. All for something we do not need now. Secondly, we're ignoring that the future is uncertain. What if it turns out we never needed the future feature? What if a client changes their mind about something more basic and completely renders a whole set of these future features redundant? What if implementing the future feature actually prevents another feature, which we discover is needed later, from being added? In other circumstances, adding the new feature requires additional support work to make it possible, perhaps even snowballing into more new not-currently-needed features to support whatever we're adding. Incremental development works here since I am the client (arguably being one's own client is the worst possible case). However, many projects will have other people as the client and their requirements may vary substantially over time. Incremental development is an excellent process but it still involves writing an application before its requirements are fully known, understood and documented. Not all clients will be capable or willing to go along with such an uncertain process, or will find it difficult to work closely with you to ensure it works. Another effect of incremental development is that we need to pay some attention to the atomicity of our unit tests. A common mistake is trying to test too much by layering assertion after assertion in a single test. In a very limited scenario this does little harm, but once it expands to page views and workflow, we can find ourselves constantly editing existing tests to reflect the current requirements. Editing old tests is not

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/developing.a.blogging.application (6 de 8)28/10/2011 12:53:56

Chapter 8. Developing A Blogging Application - Zend Framework Book: Surviving The Deep End

a good sign - it signals that they were not focused enough to begin with. This process is more than suitable for our blog. It's a simple application where we can afford to meet new requirements as they materialise in future chapters.

8.4. Checking Our Toolbox


While it would be great to do everything with Zend Framework, the truth is we'll need a few extra tools from time to time. Since I am a self confessed jQuery lover who doesn't have a clue about Dojo, you should keep the most recent jQuery version on standby when we get around to playing with Ajax requests and UI manipulation. I will also be using the Blueprint CSS Framework to keep my CSS editing to a minimum and maintain a simple layout without struggling for hours over grids. Another thing I rarely do without is a good HTML filtering library, something the Zend Framework does not currently offer. I will be using HTMLPurifier 4.0.0 for this purpose. Last, but by no means least, I will be using PHPUnit for all unit testing so be sure you have it installed from the PHPUnit PEAR channel. I'll note this and their installation procedure in more detail as they are needed.

8.5. This Is Not The Reference Guide


This is a very short chapter, and I'll conclude by emphasising the book's introduction. Since the purpose of the book is to assist those in the "deep end", I do assume you have a passing familiarity with the Zend Framework. I also assume you are capable of reading the Reference Guide independently, and therefore I will not spend an exhaustive amount of time on every single component we meet unless it is used consistently. One example is Zend_Db - which has a massive entry in the Reference Guide detailing its API, examples, and methods of use. Rather than take a whole chapter (or five) to explain Zend_Db and its component classes, the next chapter is far more interested in showing how to use Zend_Db's classes to create a Model. Our Model does not even need a lot of Zend_Db knowledge to follow. If you need Zend_Db specifically in some detail, refer to the Reference Guide. Eventually I'll add a

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/developing.a.blogging.application (7 de 8)28/10/2011 12:53:56

Chapter 8. Developing A Blogging Application - Zend Framework Book: Surviving The Deep End

supplementary chapter/appendix to the book if its warranted but there is little I could offer over what the Reference Guide gives you (it's very detailed and clear). It should also be obvious that this will not be a simple blog tutorial. Web application framework examples are rife with simple blog examples but this is geared towards a more fully featured application which will need to make extensive use of Zend Framework components.

Prev Chapter 7. Handling Application Errors Gracefully Home

Next Chapter 9. Implementing The Domain Model: Entries and Authors

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/developing.a.blogging.application (8 de 8)28/10/2011 12:53:56

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

Home | Book Index | Comment Help

Support The Deep End Rescue Effort!


Enviar consulta
There's a new Macbook Pro in it for the author!

Chapter 9. Implementing The Domain Model: Entries and Authors


Table of Contents 9.1. Introduction 9.2. The Domain Model and Database Access Patterns 9.3. Exploring The Domain Objects 9.4. Exploring The Entry Data Mapper 9.5. Assessing Implementation Tools 9.5.1. Domain Objects 9.5.2. Validation And Filtering Rules 9.5.3. Database Access 9.6. Implementation

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (1 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

9.6.1. Adding Unit Tests For Execution 9.6.2. The Domain Objects 9.6.3. The Data Mappers 9.6.4. Lazy Loading Domain Objects 9.6.5. Preventing Duplicate Entities With An Identity Map 9.7. Conclusion

9.1. Introduction
Back in Chapter 3: The Model I discussed some of the concepts surrounding the Model, a representation of the state and business rules of an entity, such as those governing entries in a blog. We're lucky in this chapter because our entities are extremely simple and with that simplicity we can ignore a lot of abstract ideas concerning the Model that are more relevant to web applications dealing with far more complex systems. However, even in our current state of simplicity we meet a number of design challenges to get us started. Let me start by acknowledging that this chapter does not teach Zend_Db, Zend_Db_Table or
Zend_Db_Table_Row in any great depth. Uses of these classes will be explained throughout the

book as they are encountered, however the focus of this chapter is using Zend_Db_Table as the basis of designing and implementing our Model for this blog. Specifically I look at one area of the Model - entries.

9.2. The Domain Model and Database Access Patterns


When we discuss a Model there are some standard terms we can apply. Any Model must first of all belong to a domain, basically the overall system in which the Model operates. In our current application this is simply "blogging". Within this domain, the Model is composed of one or more domain objects. A domain object is a representation of an entity, its properties, and the business rules (also known as the domain logic) applied to it. So, for our blogging domain, we may have domain objects representing Entry entities. For the sake of completeness, an entity can be defined as a uniquely identifiable member of the domain with a set of behaviours. All entries, for example,

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (2 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

should have a unique title and content but the main unique property will be its id. All entries also have a set of procedural behaviours - they are written, validated, and published. If you check the explanation, you'll notice that nowhere is it stated that a Model is a singular object. In a planetary climate model we would have thousands of interacting entities, factors, behaviours, constraints, etc. So when we speak of a Model, we're actually referring to all of the entities contained by that Model, within its domain, and how they behave and interact with each other. You may find a lot of Zend Framework terminology that refers to entities as Models, e.g. an Entry Model, an Author Model, etc. In nearly all cases these are domain objects within a single domain model. We mentioned domain logic which is a more general term referring to business rules and behaviour - not all applications are business related. Often our domain logic will describe constraints on the properties of domain objects. i.e. rules for validation and filtering. I mention this to emphasise that validation is a Model concern, and not a concern for either the Controller or View. You'll see where this comes into play in a future chapter when we talk about forms. The main problem developers may face when designing the Model layer of their application is exactly how close the Model's storage layer is to the surface. In very simple applications, we may directly run SQL queries using Zend_Db or create domain objects which, for the sake of expediency, extend Zend_Db_Table (which implements Martin Fowler's Table Data Gateway pattern from his book, "Patterns Of Enterprise Application Architecture" (or POEAA)). These bring the storage mechanism, a relational database, to the surface as something our application can access directly. The same can be said of
Zend_Db_Table_Row, which implements Fowler's Row Data Gateway pattern, or Ruby On Rails'

ActiveRecord, which implements (with some improvements which move it a bit closer to a Data Mapper) Fowler's Active Record pattern. All three of these patterns have something in common, they each encapsulate database access directly, often by inheriting from a base class bound to a single database table or row. However, in many non-simple applications the domain model can grow in complexity. We may find that the domain model, which is expressed in terms of objects and object properties, cannot be

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (3 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

easily mapped to database tables. A simple example of this is a blog entry. While the entry seems like a simple entity which can be assigned an entries table in a database schema, it also contains a reference (e.g. a foreign key) for an author. From the perspective of our domain objects, this would mean the entry object contains an author object. Why? Because the Model is designed from an object oriented perspective and this is the most obvious natural design. From a database schema perspective, authors would be stored on a different table. What this means, is that our entry object does not map exactly to one single table, but should be mapped to two tables. We can resolve this by perhaps using an SQL table join when retrieving data. Using a join, we would need to filter the author data into an author object or, alternatively, fall back on two separate SQL queries to match the two objects. Either way, we have a problem as our domain object inherits from a class bound to one database table but actually needs to query two. This serves to demonstrate an inescapable fact of life in application development - objects only map to databases one for one in very simple domain models. Eventually any complex Model needs more complex logic to perform a mapping between the two, and this leads into one of the primary patterns used outside of simple scenarios, the Data Mapper. Guess who defined it by the way? Martin Fowler - and yes, you really should read his book if you can get a copy... So where am I going with this? Well, if we leave database access at the surface (for example, extending from Zend_Db_Table which represents only one table) we will quickly find ourselves stuck in an inflexible design which is incapable of easily dealing with any mismatch between the object domain and the database table domain. For example, if our entry domain object extends
Zend_Db_Table it is directly tied to an entries table. So where does the author come from? Authors

cannot be retrieved by querying an entries table, so somehow we need to drag in additional code, or run direct SQL queries (back to joins or two separate queries), etc. All of which will make it clear that inheriting from a class bound to only one table isn't working. The alternative is leaving our domain objects completely free of any database related methods, and instead hide all database access behind a Data Mapper which performs any object to table mapping, and which can be made aware of any number of tables (just throw more Mappers at it). Now our domain objects are decoupled from the database schema.
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (4 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

Let's take a look at Fowler's definition of a Data Mapper.

A layer of Mappers that moves data between objects and a database while keeping them independent of each other and the mapper itself.

The definition confirms our suspicions then, domain objects will not even be aware that a database exists when a Data Mapper is implemented. If there is anything to take from this section it's the following. Extending database access classes like Zend_Db_Table is fine, for very simple domain models. It's easy to do, easy to understand and requires little code to accomplish. However, in more complex domain models where domain objects can contain other domain objects, the inheritance route will not work well. At this point the need for a better solution like a Data Mapper will become obvious and justify the need for a slightly more complex class design. I've deliberately stuck to Fowler's patterns for a reason. The more that simple inheritance manifests itself as a problem, the more likely it is that developers will begin changing their designs in an attempt to remove the inflexibility. In the Zend Framework community this has often become a debate over whether Models (or rather domain objects) should have an is-a (via inheritance) or hasa (via composition) relationship to the Zend_Db classes. At other times there are departures into the realm of Data Containers and Gateways. All of these are talking or coding around an obvious concept - they are intermediary or near complete steps towards the Data Mapper solution. It's so obvious as a solution that many developers will create a complete Data Mapper without ever realising it has a formal name and a massive body of implementation knowledge in the literature!

9.3. Exploring The Domain Objects


With all these ideas to hand, let's actually get around to examining the requirements of an actual domain object to represent each of our blog entries. We know from the last section that, at a

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (5 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

minimum, we need an Entry domain object. At this point we are still not concerned over database schemas and we have only a basic idea of what properties our entries need anyway, so we'll focus on the core needs only. At this point it is also important to keep in mind the definition of a domain object. It represents a uniquely identifiable entity with a set of behaviours. The key word is "uniquely" since every domain object represents one single entity. If we wish to represent many similar entities we will need a class responsible for containing a collection of entries. At a minimum, our domain object will expose the following properties: id title content published_date author

These are not all the properties we'll need, but they are enough to get started with. In applying incremental development we'll worry about additional features only when they become a requirement. We can describe these properties by setting out the constraints that should apply to these in order for our domain object to represent a validated entry. Although we won't immediately deal with validation in this chapter, we'll see these integrated a little later. The id is a positive integer greater than zero uniquely identifying the current entry. The title is a non-empty string containing plain text or XHTML representing a unique entry title. Content is a XHTML 1.0 Strict or plain text formatted non-empty string.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (6 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q

Both title and content may only contain a subset of XHTML tags and attributes specified by a whitelist. The author is a valid author domain object representing the author of the entry. The published_date property is a date which can be interpreted using the ISO 8601 standard.

These constraints are how a domain object can tell whether or not its assigned properties make it a valid entry. As we note, our entry will reference an author. Since all of these entities are represented by domain objects, we create a similar profile of an author object. This object's properties may include: id username fullname email url

9.4. Exploring The Entry Data Mapper


Where the Entry domain object deals with a single entry, its properties and its validity as an entry, the Data Mapper is concerned more with the persistence of these objects between requests. Its function is to create, read, update and delete (collectively referred to as CRUD operations) domain object data on the database through a database access layer, and of course mapping the properties on those domain objects to their correct tables and column names. The Mapper must also do this without exposing the database schema, the access method or even the mapping logic to the domain objects. As the Mapper is responsible for retrieving an entities data from the database in order to return a domain object created from that data, it stands to reason it also hosts any utility methods

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (7 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

concerning CRUD operations and their selection criteria, i.e. which may relate to the SQL WHERE part of queries or the column name part where only certain details are required for an entity. With all of this going on it becomes more clear that domain objects probably will not be making calls to the Data Mapper. Instead we'll use the Data Mapper within our application and pass it any domain objects to work on. This isn't necessarily the easiest design to use - for obvious reasons it means we need more code in our application's Controller layer since have double the number of objects by introducing Data Mappers. One common method of alleviating this object count problem is to allow domain objects be aware of their Data Mappers, but ensuring that this awareness does not extend beyond the Data Mapper API. Remember that in applying object oriented programming we should always code to the interface, never the implementation. We won't take any shortcuts here though - our domain model for blogging is simple enough that getting any more complex about our solution really would be pressing past the boundary of how much time we should be spending developing this Data Mapper.

9.5. Assessing Implementation Tools


With our Model fleshed out in more detail, we can start to identify some of the Model functionality we can pass out to existing Zend Framework components.

9.5.1. Domain Objects


Implementing domain objects in simple forms requires only one thing. PHP5. All domain objects are just plain old PHP objects with nothing particularly special to note. It's disappointing, I know! The Model is supposed to be complex, impossible to understand, and require a PhD. Instead we've boiled it into a system of objects that are not particularly complicated in isolation.

9.5.2. Validation And Filtering Rules


Before our Model can be stored anywhere, it needs to ensure the data it's holding follows any constraints and rules we define, i.e. that it is valid and any values have been filtered as required. While we could start throwing in validators using Zend_Validate and filters using Zend_Filter, we already know that any of our generated forms for the Model will then duplicate these rules.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (8 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

Duplication is bad, so the logical step is to use Zend_Form instances as the basis for implementing this. Using Zend_Form does not come without a question mark. Since it represents a form, it's obviously something we will use a lot in our View. Isn't this mixing Model and View together inappropriately? This is one way of looking at it. The other is that any instance of Zend_Form may perform a dual role as a presentational element and as a container for Model derived validation/filtering rules. This may not always be the case, and indeed in complex Models this may only work for a small part of the Model where forms correlate fully to the data held by a Model. Our blog is pretty simple though so there's little need to worry. Technically, an ideal theoretical solution would be a Form solution which maintains two independent parts: a data container with validators and filters (much closer to a domain object) and a set of renderers capable of transforming these containers into a View included form. Wishful thinking aside (even the theoretically perfect solutions in this area tend be as complex if not more so than Zend_Form), we have to go with the hand we're dealt and adapt it to our needs. We will be looking at Zend_Form and how this is implemented in a subsequent chapter.

9.5.3. Database Access


Since this is a Zend Framework book, we will of course be using Zend_Db. To be exact,
Zend_Db_Table_Abstract which implements the Table Data Gateway pattern from Martin Fowler's

POEAA. This Gateway pattern can be defined as a class which provides access to a table on the database, allowing us to perform inserts, selects, updates, and deletes for any row or group of rows on that table. Fowler defines this pattern as follows:

An object that acts as a Gateway to a database table. One instance handles all the rows in the table.

Zend_Db also offers an implementation of the Row Data Gateway pattern via Zend_Db_Table_Row

which is similar to the Table Data Gateway except it deals with an individual row on a database

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (9 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

table. In either case, Zend_Db offers abstracted access via its public API which allows you to construct SQL queries by chaining together object methods and is used for either pattern implementation. For the purposes of our Model, we will be creating Data Mappers which access the database via
Zend_Db_Table (i.e. the Table Data Gateway option). This fits in with the purpose of a Data Mapper

which can be used by many domain objects but remains independent of them all, i.e. it's not concerned with just one specific domain object but may offer specific concrete subclasses for each to handle any domain object specific mapping logic. It is important to note that at no point will our domain objects or Mappers extend from Zend_Db_Table as suggested by the Reference Guide. This forces our Model into one implementation based on database access. It also forces Models to be aware of their storage backend, and encourages developers to freely mix database and non-database code everywhere. Overall it's poor object oriented design unless your objective really is to only use database abstraction. The result is that we emphasising the use of composition over inheritance, a fundamental best practice in object oriented programming. All of our domain objects will have a "has-a" or "has-many" relationship with other classes, except perhaps any abstract parents or interfaces which help ensure all domain objects at least share a similar approach in their API.

9.6. Implementation
As I warned you at the start of the book, outside of short articles on my blog I always develop code using Test Driven Design. Therefore all code below is presented with unit tests at each step. Look on the bright side, at least you have something to put in the /tests directory! To setup the initial testing framework, please take a look at Appendix C: Unit Testing and Test Driven Design (to be added shortly).

9.6.1. Adding Unit Tests For Execution


The tests for our Model will be stored at /tests/ZFExt/Model, for example a test for our entry domain object will exist at /tests/ZFExt/Model/EntryTest.php. This will require adding an
AllTests.php file in the same directory containing:

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (10 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

q q q q

<?php if (!defined('PHPUnit_MAIN_METHOD')) { define('PHPUnit_MAIN_METHOD', 'ZFExt_Model_AllTests:: main'); } require_once 'TestHelper.php'; require_once 'ZFExt/Model/EntryTest.php'; class ZFExt_Model_AllTests { public static function main() { PHPUnit_TextUI_TestRunner::run(self::suite()); } public static function suite() { $suite = new PHPUnit_Framework_TestSuite('ZFSTDE Blog Suite: Models'); $suite->addTestSuite('ZFExt_Model_EntryTest'); return $suite; } } if (PHPUnit_MAIN_METHOD == 'ZFExt_Model_AllTests::main') { ZFExt_Model_AllTests::main(); }

q q q q q q q q q q q q q q q q

q q q q q q q q q q

As we implement our Model, you will need to add additional tests to this file in order for them to be executed. They can be added by following the pattern used for the ZFExt_Model_EntryTest suite. Since this is not the top level AllTests.php file, you should add this one to the root /tests/
AllTests.php using:
q q q q q q q q q q q q q q q q

<?php if (!defined('PHPUnit_MAIN_METHOD')) { define('PHPUnit_MAIN_METHOD', 'AllTests::main'); } require_once 'TestHelper.php'; require_once 'ZFExt/Model/AllTests.php'; class AllTests { public static function main() { PHPUnit_TextUI_TestRunner::run(self::suite()); }
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (11 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q

public static function suite() { $suite = new PHPUnit_Framework_TestSuite('ZFSTDE Blog Suite'); $suite->addTest(ZFExt_Model_AllTests::suite()); return $suite; } } if (PHPUnit_MAIN_METHOD == 'AllTests::main') { AllTests::main(); }

q q q q q q q q q q

As described in the Appendix, tests are run by navigating to /tests/ZFExt/Model (or /tests to run every single test across the application) in a console and running:

phpunit AllTests.php

9.6.2. The Domain Objects


Since our Entry domain object is just an ordinary object, we can start if off as a simple data container. We should always namespace our classes (the old pre-5.3 type of namespacing) so we'll be using a ZFExt_Model namespace for any Model related classes (which also applies to the test files). For now, everything will be stored within the /library directory. Let's start with an initial test checking we can set properties on the domain objects, and instantiate an entry domain object with an array of data. This is the initial content of /tests/ZFExt/Model/EntryTest.php:
q q q q q q q q q q q q q q q q q

<?php require_once 'ZFExt/Model/Entry.php'; class ZFExt_Model_EntryTest extends PHPUnit_Framework_TestCase { public function testSetsAllowedDomainObjectProperty() { $entry = new ZFExt_Model_Entry; $entry->title = 'My Title'; $this->assertEquals('My Title', $entry->title); } public function testConstructorInjectionOfProperties() { $data = array(
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (12 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q

'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author' => new ZFExt_Model_Author ); $entry = new ZFExt_Model_Entry($data); $expected = $data; $expected['id'] = null; $this->assertEquals($expected, $entry->toArray()); } }

We can now implement this (the tests fail anyway without the class written!) at /library/ZFExt/
Model/Entry.php:
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

<?php class ZFExt_Model_Entry { protected $_data = array( 'id' => null, 'title' => '', 'content' => '', 'published_date' => '', 'author' => null ); public function __construct(array $data = null) { if (!is_null($data)) { foreach ($data as $name => $value) { $this->{$name} = $value; } } } public function toArray() { return $this->_data; } public function __set($name, $value) { $this->_data[$name] = $value; } public function __get($name) { if (array_key_exists($name, $this->_data)) { return $this->_data[$name]; } } }

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (13 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

You may ask why we don't make all properties public. The benefit of using a protected array, and using PHP's magic methods (like __set()) to provide access, is that it creates a gateway through which access passes. Now we can use any magic methods to run checks on properties and throw Exceptions on any errors. Our new object is pretty basic. Let's add the rest of the standard magic methods so we can check if properties in the protected array are set and unset them if need be. Only the new tests are shown below.
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

<?php require_once 'ZFExt/Model/Entry.php'; class ZFExt_Model_EntryTest extends PHPUnit_Framework_TestCase { // ... public function testReturnsIssetStatusOfProperties() { $entry = new ZFExt_Model_Entry; $entry->title = 'My Title'; $this->assertTrue(isset($entry->title)); } public function testCanUnsetAnyProperties() { $entry = new ZFExt_Model_Entry; $entry->title = 'My Title'; unset($entry->title); $this->assertFalse(isset($entry->title)); } } <?php class ZFExt_Model_Entry { protected $_data = array( 'id' => null, 'title' => '', 'content' => '', 'published_date' => '', 'author' => null ); public function __construct(array $data = null) { if (!is_null($data)) {
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (14 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

foreach ($data as $name => $value) { $this->{$name} = $value; } } } public function toArray() { return $this->_data; } public function __set($name, $value) { $this->_data[$name] = $value; } public function __get($name) { if (array_key_exists($name, $this->_data)) { return $this->_data[$name]; } } public function __isset($name) { return isset($this->_data[$name]); } public function __unset($name) { if (isset($this->_data[$name])) { unset($this->_data[$name]); } } }

Our domain object is now better defined. At the moment it offers unrestricted access when setting properties but our domain objects only needs those defined as keys on the initial data array. We can remove the ability to set out of bounds properties, and throw an Exception when it occurs, by adding an additional check to the __set() method as follows.
q q q q q q q q q q

<?php require_once 'ZFExt/Model/Entry.php'; class ZFExt_Model_EntryTest extends PHPUnit_Framework_TestCase { // ... public function testCannotSetNewPropertiesUnlessDefinedForDomainObject() {
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (15 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q

q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

$entry = new ZFExt_Model_Entry; try { $entry->notdefined = 1; $this->fail('Setting new property not defined in class should' . ' have raised an Exception'); } catch (ZFExt_Model_Exception $e) { } } } <?php class ZFExt_Model_Entry { protected $_data = array( 'id' => null, 'title' => '', 'content' => '', 'published_date' => '', 'author' => null ); public function __construct(array $data = null) { if (!is_null($data)) { foreach ($data as $name => $value) { $this->{$name} = $value; } } } public function toArray() { return $this->_data; } public function __set($name, $value) { if (!array_key_exists($name, $this->_data)) { throw new ZFExt_Model_Exception('You cannot set new properties' . 'on this object'); } $this->_data[$name] = $value; } public function __get($name) { if (array_key_exists($name, $this->_data)) { return $this->_data[$name]; } } public function __isset($name) {
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (16 de 52)28/10/2011 12:54:12

q q q q q q q q q q q q q q

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q

return isset($this->_data[$name]); } public function __unset($name) { if (isset($this->_data[$name])) { unset($this->_data[$name]); } } }

Next up, our entry domain object will contain an author object. Since any of these domain objects will likely duplicate code we just wrote into ZFExt_Model_Entry, we should refactor our class to inherit from a parent containing any potentially reusable methods. Here, we add a new parent class called ZFExt_Model_Entity to fill this role.
q q q q q q q q q q q q q q q q q q q q q q q

<?php class ZFExt_Model_Entity { public function __construct(array $data = null) { if (!is_null($data)) { foreach ($data as $name => $value) { $this->{$name} = $value; } } } public function toArray() { return $this->_data; } public function __set($name, $value) { if (!array_key_exists($name, $this->_data)) { throw new ZFExt_Model_Exception('You cannot set new properties' . ' on this object'); } $this->_data[$name] = $value; } public function __get($name) { if (array_key_exists($name, $this->_data)) { return $this->_data[$name]; } } public function __isset($name)
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (17 de 52)28/10/2011 12:54:12

q q q q q q q q q q q q q

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q

{ return isset($this->_data[$name]); } public function __unset($name) { if (isset($this->_data[$name])) { unset($this->_data[$name]); } } } <?php class ZFExt_Model_Entry extends ZFExt_Model_Entity { protected $_data = array( 'id' => null, 'title' => '', 'content' => '', 'published_date' => '', 'author' => null ); }

Running our tests once more will confirm that this was a successful refactoring. Now, let's add a similar class for authors. First, to accommodate the new test, edit your /tests/
ZFExt/Model/AllTests.php file to include a new test located at /tests/ZFExt/Model/ AuthorTest.php. The tests and the class which contains them will be very similar to that for the

entry domain object. Here are the initial tests which reflect those for the entry object but with the properties for an author object.
q q q q q q q q q q q q q q q q q q

<?php class ZFExt_Model_AuthorTest extends PHPUnit_Framework_TestCase { public function testSetsAllowedDomainObjectProperty() { $author = new ZFExt_Model_Author; $author->fullname = 'Joe'; $this->assertEquals('Joe', $author->fullname); } public function testConstructorInjectionOfProperties() { $data = array( 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com',
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (18 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q

'url' => 'http://www.example.com' ); $author = new ZFExt_Model_Author($data); $expected = $data; $expected['id'] = null; $this->assertEquals($expected, $author->toArray()); } public function testReturnsIssetStatusOfProperties() { $author = new ZFExt_Model_Author; $author->fullname = 'Joe Bloggs'; $this->assertTrue(isset($author->fullname)); } public function testCanUnsetAnyProperties() { $author = new ZFExt_Model_Author; $author->fullname = 'Joe Bloggs'; unset($author->fullname); $this->assertFalse(isset($author->fullname)); } public function testCannotSetNewPropertiesUnlessDefinedInClass() { $author = new ZFExt_Model_Author; try { $author->notdefinedinclass = 1; $this->fail('Setting new property not defined in class should' . ' have raised an Exception'); } catch (ZFExt_Model_Exception $e) { } } }

q q q q q

q q q q q q

Here's the implementation which passes all of these new tests.


q q q q q q q q q q q q q q

<?php class ZFExt_Model_Author extends ZFExt_Model_Entity { protected $_data = array( 'id' => null, 'username' => '', 'fullname' => '', 'email' => '', 'url' => '' ); }

As a final gesture to ensure our interface is bound to these domain objects, let's ensure that

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (19 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

ZFExt_Model_Entry only accepts a ZFExt_Model_Author object when setting an author property.

As usual, the test first, and then the code which makes that test pass.
q q q q q q q q

<?php class ZFExt_Model_EntryTest extends PHPUnit_Framework_TestCase { // ... public function testThrowsExceptionIfAuthorNotAnAuthorEntityObject() { $entry = new ZFExt_Model_Entry; try { $entry->author = 1; $this->fail('Setting author should have raised an Exception' . ' since value was not an instance of ZFExt_Model_Author'); } catch (ZFExt_Model_Exception $e) { } } } <?php class ZFExt_Model_Entry extends ZFExt_Model_Entity { protected $_data = array( 'id' => null, 'title' => '', 'content' => '', 'published_date' => '', 'author' => null ); public function __set($name, $value) { if ($name == 'author' && !$value instanceof ZFExt_Model_Author) { throw new ZFExt_Model_Exception('Author can only be set using' . ' an instance of ZFExt_Model_Author'); } parent::__set($name, $value); } }

q q q q q

q q q q q q q q q q q q q q q q q q q q q

q q q q q q

All we've done here, is to override the parent class' __set() method to add a new check making sure our object only accepts an author value that is an object of type ZFExt_Model_Author. Otherwise, we pass control back to the parent to set the property.
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (20 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

We're done for now! Let's turn our attention to our Data Mapper implementation so can save these objects to a database or retrieve them.

9.6.3. The Data Mappers


Our Data Mapper will utilise Zend_Db_Table in the background, so its function in this design is to carry out typical CRUD operations. Later, we will also see that it can carry methods with more specific uses such as conditional fetching operations. For the moment, let's concentrate on setting it up. In the first test we'll add, our Data Mapper will be instantiated, and in turn create a configured instance of Zend_Db_Table_Abstract with which to work. You will note that I am not using an actual database. Although it takes a chunk of code at the start, I am using a mock object (a type of test double) in place of a real Zend_Db_Table_Abstract object. This will allow me to control everything this object does, including return values and setting expectations on what methods should be called, with what arguments, etc. The main reason I do this is because actually on a real database offers nothing new - our tests do not need one. If we did use a database it would work fine also, but then we are testing Zend_Db_Table_Abstract in addition to everything else since it's actually called upon. The Zend Framework already has tests for this component. As before, to add this test to your suite, add the file and class to /tests/ZFExt/Model/AllTests.
php, the tests themselves are written to /tests/ZFExt/Model/EntryMapperTest.php.
q q q

<?php class ZFExt_Model_EntryMapperTest extends PHPUnit_Framework_TestCase { protected $_tableGateway = null; protected $_adapter = null; protected $_rowset = null; protected $_mapper = null; public function setup() { $this->_tableGateway = $this->_getCleanMock( 'Zend_Db_Table_Abstract' );

q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (21 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q

q q

q q q q q q q q q q q q q q q q q q q q q q q

$this->_adapter = $this->_getCleanMock( 'Zend_Db_Adapter_Abstract' ); $this->_rowset = $this->_getCleanMock( 'Zend_Db_Table_Rowset_Abstract' ); $this->_tableGateway->expects($this->any())->method ('getAdapter') ->will($this->returnValue($this->_adapter)); $this->_mapper = new ZFExt_Model_EntryMapper($this>_tableGateway); } protected function _getCleanMock($className) { $class = new ReflectionClass($className); $methods = $class->getMethods(); $stubMethods = array(); foreach ($methods as $method) { if ($method->isPublic() || ($method->isProtected() && $method->isAbstract())) { $stubMethods[] = $method->getName(); } } $mocked = $this->getMock( $className, $stubMethods, array(), $className . '_EntryMapperTestMock_' . uniqid(), false ); return $mocked; } }

All Data Mapper tests will rely on mocking out Zend_Db_Table_Abstract - after all it's already tested by the Zend Framework team so it's pointless using an actual object connected to a database. Typically we won't pass in a real instance through the constructor when we use this in the application, we can instead rely on the constructor creating a suitable instance. This test skeleton is setup to create a fully mocked version of Zend_Db_Table_Abstract. While its hard to pinpoint from the PHPUnit manual without delving into the code, the protected
_getCleanMock() method I'm using creates a completely clean mock object with all methods

resolved and mocked. It creates a unique name for the mock on every call ensuring the mocked class names do not conflict. The only step necessary at the moment is ensuring all
Zend_Db_Table_Abstract mock objects return a mocked Adapter also. Typically the only reason

for mocking the Adapter is because it has one commonly used method, quoteInto() for escaping values in an SQL expression or condition.

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (22 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

Here's our initial (as yet untested) implementation showing why testing the real instantiation is not worth the trouble - it's dead simple, and again, testing it is really only testing
Zend_Db_Table_Abstract.
q q q q q q q q q q

<?php class ZFExt_Model_EntryMapper { protected $_tableGateway = null; protected $_tableName = 'entries'; public function __construct(Zend_Db_Table_Abstract $tableGateway) { if (is_null($tableGateway)) { $this->_tableGateway = new Zend_Db_Table($this>_tableName); } else { $this->_tableGateway = $tableGateway; } } protected function _getGateway() { return $this->_tableGateway; } }

q q q

q q q q q q q q q q q

In the code above we setup an instance of Zend_Db_Table with which to access the database. Although this class is called Abstract, it actually contains no abstract methods. The only configuration needed for now is to tell this instance which database table to use. We don't need to pass in any database connection settings because we can set a default database adapter from our bootstrap later. Now lets add some useful methods. We'll start with a method to save a new domain object. Since we have mocked Zend_Db_Table_Abstract we are not making any direct assertions for the moment. Our assertions are actually setup by setting expectations on any mock objects, checking that our Mapper calls the expected Zend_Db_Table_Abstract method insert() with the correct array of data.
q q

<?php

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (23 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q

q q q q q q q q q q q q q q q q q q q q

class ZFExt_Model_EntryMapperTest extends PHPUnit_Framework_TestCase { // ... public function testSavesNewEntryAndSetsEntryIdOnSave() { $author = new ZFExt_Model_Author(array( 'id' => 2, 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); $entry = new ZFExt_Model_Entry(array( 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author' => $author )); // set mock expectation on calling Zend_Db_Table:: insert() $insertionData = array( 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author_id' => 2 ); $this->_tableGateway->expects($this->once()) ->method('insert') ->with($this->equalTo($insertionData)) ->will($this->returnValue(123)); $this->_mapper->save($entry); $this->assertEquals(123, $entry->id); } // ... }

q q q q q q q q q q q q q q q q q q

Here's the implementation which passes this test.


q q q q q q q q q q

<?php class ZFExt_Model_EntryMapper { protected $_tableGateway = null; protected $_tableName = 'entries'; public function __construct(Zend_Db_Table_Abstract $tableGateway) { if (is_null($tableGateway)) {
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (24 de 52)28/10/2011 12:54:12

q q

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q

$this->_tableGateway = new Zend_Db_Table($this>_tableName); } else { $this->_tableGateway = $tableGateway; } } protected function _getGateway() { return $this->_tableGateway; } public function save(ZFExt_Model_Entry $entry) { $data = array( 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $entry->id = $this->_getGateway()->insert($data); } }

q q q q q q q q q q q q q q q q q q q q q q q

Saving a new record to the database involves calling Zend_Db_Table_Abstract::insert() with an array of the column names and values to insert in the database table, "entries". The table name is set within the constructor of the Data Mapper. The id is omitted since it will be set from the return value of Zend_Db_Table_Abstract::insert(). As you can see, our Mapper most definitely knows about the database schema - it maps the author object's id property to a table field called author_id. The domain object is not aware that this database column exists. The rest of the author data is ignored since it is stored on a different table, and it's not new. Actually this is a not so subtle point, you cannot save an author in this manner since author objects can only be saved through a future author Data Mapper. We may also want to update entries, and these should be easy to spot since they will already have an existing id value. Let's add a test for the updating behaviour and its implementation. Once again, we'll use Mock Object expectations instead of assertions for this one. You should note that mock expectations are checked, of course, so if any of the constraints on method call count and similar are broken by how our implementation works, the test will fail.
q

<?php
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (25 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q

q q q q q q q q q q q q q q q q q q q q

class ZFExt_Model_EntryMapperTest extends PHPUnit_Framework_TestCase { // ... public function testUpdatesExistingEntry() { $author = new ZFExt_Model_Author(array( 'id' => 2, 'name' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); $entry = new ZFExt_Model_Entry(array( 'id' => 1, 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author' => $author )); // set mock expectation on calling Zend_Db_Table:: update() $updateData = array( 'id' => 1, 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author_id' => 2 ); // quoteInto() is called to escape parameters from the adapter $this->_adapter->expects($this->once()) ->method('quoteInto') ->will($this->returnValue('id = 1')); $this->_tableGateway->expects($this->once()) ->method('update') ->with($this->equalTo($updateData), $this->equalTo ('id = 1')); $this->_mapper->save($entry); } // ... } <?php class ZFExt_Model_EntryMapper { protected $_tableGateway = null; protected $_tableName = 'entries'; public function __construct(Zend_Db_Table_Abstract
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (26 de 52)28/10/2011 12:54:12

q q q q q q q q

q q q q q q

q q q q q q q q q q q q q q q q q

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

q q q

q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

$tableGateway) { if (is_null($tableGateway)) { $this->_tableGateway = new Zend_Db_Table($this>_tableName); } else { $this->_tableGateway = $tableGateway; } } protected function _getGateway() { return $this->_tableGateway; } public function save(ZFExt_Model_Entry $entry) { if (!$entry->id) { $data = array( 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $entry->id = $this->getGateway()->insert($data); } else { $data = array( 'id' => $entry->id, 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $where = $this->getGateway()->getAdapter() ->quoteInto('entry_id = ?', $entry->id); $this->getGateway()->update($data, $where); } } }

Let's add one more method before we break off - we'll add others over the course of our blogging application. Besides saving and updating, at a bare minimum we will also need to delete and retrieve entries. This poses at least one problem in that entries may contain authors. To allow the EntryMapper to retrieve an author object, we must first add a Data Mapper for authors. Here's the full set of tests and an implementation for the class ZFExt_Model_AuthorMapper (most of which is very similar to the tests we've written so far - and includes a few we'll explore for the Entry Data Mapper soon).
q

<?php
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (27 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q

q q q q q q q q q q q q q q q q q q q q q q

class ZFExt_Model_AuthorMapperTest extends PHPUnit_Framework_TestCase { protected $_tableGateway = null; protected $_adapter = null; protected $_rowset = null; protected $_mapper = null; public function setup() { $this->_tableGateway = $this->_getCleanMock( 'Zend_Db_Table_Abstract' ); $this->_adapter = $this->_getCleanMock( 'Zend_Db_Adapter_Abstract' ); $this->_rowset = $this->_getCleanMock( 'Zend_Db_Table_Rowset_Abstract' ); $this->_tableGateway->expects($this->any())->method ('getAdapter') ->will($this->returnValue($this->_adapter)); $this->_mapper = new ZFExt_Model_AuthorMapper($this>_tableGateway); } public function testCreatesSuitableTableDataGatewayObjectWhenInstantiated() { $mapper = new ZFExt_Model_EntryMapper($this>_tableGateway); $this->assertTrue($mapper->getGateway() instanceof Zend_Db_Table_Abstract); } public function testSavesNewAuthorAndSetsAuthorIdOnSave() { $author = new ZFExt_Model_Author(array( 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); // set mock expectation on calling Zend_Db_Table:: insert() $insertionData = array( 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' ); $this->_tableGateway->expects($this->once())

q q

q q q

q q

q q q q q q q q q q q q q

q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (28 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q

->method('insert') ->with($this->equalTo($insertionData)) ->will($this->returnValue(123)); $this->_mapper->save($author); $this->assertEquals(123, $author->id); } public function testUpdatesExistingAuthor() { $author = new ZFExt_Model_Author(array( 'id' => 2, 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); // set mock expectation on calling Zend_Db_Table:: update() $updateData = array( 'id' => 2, 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' ); $this->_adapter->expects($this->once()) ->method('quoteInto') ->will($this->returnValue('id = 2')); $this->_tableGateway->expects($this->once()) ->method('update') ->with($this->equalTo($updateData), $this->equalTo ('id = 2')); $this->_mapper->save($author); } public function testFindsRecordByIdAndReturnsDomainObject() { $author = new ZFExt_Model_Author(array( 'id' => 1, 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); // expected rowset result for found entry $dbData = new stdClass; $dbData->id = 1; $dbData->fullname = 'Joe Bloggs'; $dbData->username = 'joe_bloggs'; $dbData->email = 'joe@example.com'; $dbData->url = 'http://www.example.com'; // set mock expectation on calling Zend_Db_Table::find ()

q q q q q q q q q q q q q

q q q q q q q q q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (29 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

$this->_rowset->expects($this->once()) ->method('current') ->will($this->returnValue($dbData)); $this->_tableGateway->expects($this->once()) ->method('find') ->with($this->equalTo(1)) ->will($this->returnValue($this->_rowset)); $entryResult = $this->_mapper->find(1); $this->assertEquals($author, $entryResult); } public function testDeletesAuthorUsingEntryId() { $this->_adapter->expects($this->once()) ->method('quoteInto') ->with($this->equalTo('id = ?'), $this->equalTo(1)) ->will($this->returnValue('author_id = 1')); $this->_tableGateway->expects($this->once()) ->method('delete') ->with($this->equalTo('id = 1')); $this->_mapper->delete(1); } public function testDeletesAuthorUsingEntryObject() { $author = new ZFExt_Model_Author(array( 'id' => 1, 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); $this->_adapter->expects($this->once()) ->method('quoteInto') ->with($this->equalTo('id = ?'), $this->equalTo(1)) ->will($this->returnValue('author_id = 1')); $this->_tableGateway->expects($this->once()) ->method('delete') ->with($this->equalTo('id = 1')); $this->_mapper->delete($author); } protected function _getCleanMock($className) { $class = new ReflectionClass($className); $methods = $class->getMethods(); $stubMethods = array(); foreach ($methods as $method) { if ($method->isPublic() || ($method->isProtected() && $method->isAbstract())) { $stubMethods[] = $method->getName(); } } $mocked = $this->getMock( $className, $stubMethods, array(),

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (30 de 52)28/10/2011 12:54:12

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q

$className . '_AuthorMapperTestMock_' . uniqid(), false ); return $mocked; } }

The implementation of this class reflects that for the Entry Mapper, and adds an additional find() and delete() method.
q q q q q q q q q q q q

<?php class ZFExt_Model_AuthorMapper { protected $_tableGateway = null; protected $_tableName = 'authors'; protected $_entityClass = 'ZFExt_Model_Author'; public function __construct(Zend_Db_Table_Abstract $tableGateway) { if (is_null($tableGateway)) { $this->_tableGateway = new Zend_Db_Table($this>_tableName); } else { $this->_tableGateway = $tableGateway; } } protected function _getGateway() { return $this->_tableGateway; } public function save(ZFExt_Model_Author $author) { if (!$author->id) { $data = array( 'fullname' => $author->fullname, 'username' => $author->username, 'email' => $author->email, 'url' => $author->url ); $author->id = $this->_getGateway()->insert($data); } else { $data = array( 'id' => $author->id, 'fullname' => $author->fullname, 'username' => $author->username, 'email' => $author->email, 'url' => $author->url );
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (31 de 52)28/10/2011 12:54:13

q q q

q q q q q q q q q q q q q q q q q q q q q q q q q q q q

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

$where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $author->id); $this->_getGateway()->update($data, $where); } } public function find($id) { $result = $this->_getGateway()->find($id)->current(); $author = new $this->_entityClass(array( 'id' => $result->id, 'fullname' => $result->fullname, 'username' => $result->username, 'email' => $result->email, 'url' => $result->url )); return $author; } public function delete($author) { if ($author instanceof ZFExt_Model_Author) { $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $author->id); } else { $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $author); } $this->_getGateway()->delete($where); } }

With this new Author Data Mapper in tow, we can use it withing our Entry Data Mapper to retrieve any author object to be included in the resulting entry object returned from a new find() method. We'll also add a similar delete() method. Here are the tests for finding an entry using its id property, and deleting an entry by its id.
q q q

<?php class ZFExt_Model_EntryMapperTest extends PHPUnit_Framework_TestCase { // ... public function testFindsRecordByIdAndReturnsDomainObject() { $author = new ZFExt_Model_Author(array( 'id' => 1, 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com',
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (32 de 52)28/10/2011 12:54:13

q q q q q q q q q q q

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q

'url' => 'http://www.example.com' )); $entry = new ZFExt_Model_Entry(array( 'id' => 1, 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author' => $author )); // expected rowset result for found entry $dbData = new stdClass; $dbData->id = 1; $dbData->title = 'My Title'; $dbData->content = 'My Content'; $dbData->published_date = '2009-08-17T17:30:00Z'; $dbData->author_id = 1; // set mock expectation on calling Zend_Db_Table::find () $this->_rowset->expects($this->once()) ->method('current') ->will($this->returnValue($dbData)); $this->_tableGateway->expects($this->once()) ->method('find') ->with($this->equalTo(1)) ->will($this->returnValue($this->_rowset)); // mock the AuthorMapper - it has separate tests $authorMapper = $this->_getCleanMock ('ZFExt_Model_AuthorMapper'); $authorMapper->expects($this->once()) ->method('find')->with($this->equalTo(1)) ->will($this->returnValue($author)); $this->_mapper->setAuthorMapper($authorMapper); $entryResult = $this->_mapper->find(1); $this->assertEquals($entry, $entryResult); } public function testDeletesEntryUsingEntryId() { $this->_adapter->expects($this->once()) ->method('quoteInto') ->with($this->equalTo('id = ?'), $this->equalTo(1)) ->will($this->returnValue('entry_id = 1')); $this->_tableGateway->expects($this->once()) ->method('delete') ->with($this->equalTo('id = 1')); $this->_mapper->delete(1); } public function testDeletesEntryUsingEntryObject() { $author = new ZFExt_Model_Author(array( 'id' => 2,

q q q q q q q q q q

q q q q q q q q q q q q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (33 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q

'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); $entry = new ZFExt_Model_Entry(array( 'id' => 1, 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author' => $author )); $this->_adapter->expects($this->once()) ->method('quoteInto') ->with($this->equalTo('id = ?'), $this->equalTo(1)) ->will($this->returnValue('entry_id = 1')); $this->_tableGateway->expects($this->once()) ->method('delete') ->with($this->equalTo('id = 1')); $this->_mapper->delete($entry); } // ... }

Here's our implementation with these two new methods. As the tests suggest, we can delete entries by either passing an integer id value or the domain object itself.
q q q q q q q q q q q q q q q q

<?php class ZFExt_Model_EntryMapper { protected $_tableGateway = null; protected $_tableName = 'entries'; protected $_entityClass = 'ZFExt_Model_Entry'; protected $_authorMapperClass = 'ZFExt_Model_AuthorMapper'; protected $_authorMapper = null; public function __construct(Zend_Db_Table_Abstract $tableGateway) { if (is_null($tableGateway)) { $this->_tableGateway = new Zend_Db_Table($this>_tableName); } else { $this->_tableGateway = $tableGateway; } }

q q q

q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (34 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

protected function _getGateway() { return $this->_tableGateway; } public function save(ZFExt_Model_Entry $entry) { if (!$entry->id) { $data = array( 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $entry->id = $this->_getGateway()->insert($data); } else { $data = array( 'id' => $entry->id, 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $entry->id); $this->_getGateway()->update($data, $where); } } public function find($id) { $result = $this->_getGateway()->find($id)->current(); if (!$this->_authorMapper) { $this->_authorMapper = new $this>_authorMapperClass; } $author = $this->_authorMapper->find($result>author_id); $entry = new $this->_entityClass(array( 'id' => $result->id, 'title' => $result->title, 'content' => $result->content, 'published_date' => $result->published_date, 'author' => $author )); return $entry; } public function delete($entry) { if ($entry instanceof ZFExt_Model_Entry) { $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $entry->id); } else { $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $entry); }

q q

q q q q q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (35 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q

$this->_getGateway()->delete($where); } public function setAuthorMapper(ZFExt_Model_AuthorMapper $mapper) { $this->_authorMapper = $mapper; } }

q q q q q

And finally...we have something of a working Data Mapper implementation! Here's what the final test tally should look like from PHPUnit.

PHPUnit 3.3.17 by Sebastian Bergmann. ....................... Time: 0 seconds OK (23 tests, 51 assertions)

9.6.4. Lazy Loading Domain Objects


Our Data Mapper implementation in ZFExt_Model_EntryMapper takes the route of needing two SQL queries to create a full domain object, one for the entry itself, and another for the referenced author. There may be times when we really just don't need the author details and at these times the extra query is pointless. It would make more sense if we altered the Data Mapper to lazy load the author data on demand, potentially saving us trips to the database. We've already seen how we can override the __set() method to validate a property being set and we can use the __get() method to achieve similar functionality by intercepting any attempt to access the author object in our entry domain object, and only then firing an request through
ZFExt_Model_AuthorMapper to retrieve that object.

Since this obviously alters existing tested behaviour, we need to amend at least one test for the Entry Data Mapper. We also need some way of storing the author's id value in the entry domain object so we have something to lazy load, and finally we need to make sure the lazy loading actually works. Here are the new/revised tests for both the Entry Mapper and Entry domain object:

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (36 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q

<?php class ZFExt_Model_EntryMapperTest extends PHPUnit_Framework_TestCase { // ... public function testFindsRecordByIdAndReturnsDomainObject() { $entry = new ZFExt_Model_Entry(array( 'id' => 1, 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z' )); // expected rowset result for found entry $dbData = new stdClass; $dbData->id = 1; $dbData->title = 'My Title'; $dbData->content = 'My Content'; $dbData->published_date = '2009-08-17T17:30:00Z'; $dbData->author_id = 1; // set mock expectation on calling Zend_Db_Table::find () $this->_rowset->expects($this->once()) ->method('current') ->will($this->returnValue($dbData)); $this->_tableGateway->expects($this->once()) ->method('find') ->with($this->equalTo(1)) ->will($this->returnValue($this->_rowset)); $entryResult = $this->_mapper->find(1); $this->assertEquals('My Title', $entryResult->title); } public function testFoundRecordCausesAuthorReferenceIdToBeSetOnEntryObject() { $entry = new ZFExt_Model_Entry(array( 'id' => 1, 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z' )); // expected rowset result for found entry $dbData = new stdClass; $dbData->id = 1; $dbData->title = 'My Title'; $dbData->content = 'My Content'; $dbData->published_date = '2009-08-17T17:30:00Z'; $dbData->author_id = 5;

q q q q q q q q q q q q q q q q q q q q q q

q q q q q q q q q q q q q q

q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (37 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q

// set mock expectation on calling Zend_Db_Table::find () $this->_rowset->expects($this->once()) ->method('current') ->will($this->returnValue($dbData)); $this->_tableGateway->expects($this->once()) ->method('find') ->with($this->equalTo(1)) ->will($this->returnValue($this->_rowset)); $entryResult = $this->_mapper->find(1); $this->assertEquals(5, $entryResult->getReferenceId ('author')); } // ... } <?php class ZFExt_Model_EntryTest extends PHPUnit_Framework_TestCase { // ... public function testAllowsAuthorIdToBeStoredAsAReference() { $entry = new ZFExt_Model_Entry; $entry->setReferenceId('author', 5); $this->assertEquals(5, $entry->getReferenceId ('author')); } public function testLazyLoadingAuthorsRetrievesAuthorDomainObject() { $author = new ZFExt_Model_Author(array( 'id' => 5, 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); $entry = new ZFExt_Model_Entry; $entry->setReferenceId('author', 5); $authorMapper = $this->_getCleanMock ('ZFExt_Model_AuthorMapper'); $authorMapper->expects($this->once()) ->method('find') ->with($this->equalTo(5)) ->will($this->returnValue($author)); $entry->setAuthorMapper($authorMapper);

q q q q q q q q q q q

q q q q q q q q q q q q q q q q q

q q q

q q q q q q q q q q q q q

q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (38 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q

q q q q q q q q q q q q q q q q q q q q q q q q q

$this->assertEquals('Joe Bloggs', $entry->author>fullname); } protected function _getCleanMock($className) { $class = new ReflectionClass($className); $methods = $class->getMethods(); $stubMethods = array(); foreach ($methods as $method) { if ($method->isPublic() || ($method->isProtected() && $method->isAbstract())) { $stubMethods[] = $method->getName(); } } $mocked = $this->getMock( $className, $stubMethods, array(), $className . '_EntryTestMock_' . uniqid(), false ); return $mocked; } // ... }

Our starting point for implementation, is amending the ZFExt_Model_Entry class to accept the reference ID for an author for later use. Since the lazy loading occurs within this object, we also need the transfer the original awareness of ZFExt_Model_AuthorMapper within
ZFExt_Model_EntryMapper over to the domain object itself. Technically, references can occur in

any domain object requiring them, so we can add this feature to the parent ZFExt_Model_Entity class. The ZFExt_Model_Entry can use these methods from the parent class to set and retrieve reference information.
q q q q q q q q q q q q q q q q

<?php class ZFExt_Model_Entity { protected $_references = array(); // ... public function setReferenceId($name, $id) { $this->_references[$name] = $id; } public function getReferenceId($name) {
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (39 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q

if (isset($this->_references[$name])) { return $this->_references[$name]; } } } <?php class ZFExt_Model_Entry extends ZFExt_Model_Entity { protected $_data = array( 'id' => null, 'title' => '', 'content' => '', 'published_date' => '', 'author' => null ); protected $_authorMapperClass = 'ZFExt_Model_AuthorMapper'; protected $_authorMapper = null; public function __set($name, $value) { if ($name == 'author' && !$value instanceof ZFExt_Model_Author ) { throw new ZFExt_Model_Exception('Author can only be set using' . ' an instance of ZFExt_Model_Author'); } parent::__set($name, $value); } public function __get($name) { if ($name == 'author' && $this->getReferenceId ('author') && !$this->_data['author'] instanceof ZFExt_Model_Author) { if (!$this->_authorMapper) { $this->_authorMapper = new $this>_authorMapperClass; } $this->_data['author'] = $this->_authorMapper ->find($this->getReferenceId('author')); } return parent::__get($name); } public function setAuthorMapper(ZFExt_Model_AuthorMapper $mapper) { $this->_authorMapper = $mapper; } }
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (40 de 52)28/10/2011 12:54:13

q q q q q q q q

q q

q q q q q q q q

q q q q q

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

Notice the new __get() method. This intercepts any attempt to access the author property of the domain object. Unless the object already includes an author object, it will attempt to load one from the database, but only if a reference id (i.e. the author's id) has been set, for example when the entry was originally loaded. Otherwise it will still return null which it should in case this is a new object without any author set. Here's the revised ZFExt_Model_EntryMapper class. The only change is to remove the automatic loading of author objects and instead set the value of author_id as a reference on the resulting entry object.
q q q q q q q q q q q q

<?php class ZFExt_Model_EntryMapper { protected $_tableGateway = null; protected $_tableName = 'entries'; protected $_entityClass = 'ZFExt_Model_Entry'; public function __construct(Zend_Db_Table_Abstract $tableGateway) { if (is_null($tableGateway)) { $this->_tableGateway = new Zend_Db_Table($this>_tableName); } else { $this->_tableGateway = $tableGateway; } } protected function _getGateway() { return $this->_tableGateway; } public function save(ZFExt_Model_Entry $entry) { if (!$entry->id) { $data = array( 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $entry->id = $this->_getGateway()->insert($data); } else { $data = array(
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (41 de 52)28/10/2011 12:54:13

q q q

q q q q q q q q q q q q q q q q q q q q q q

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

'id' => $entry->id, 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $entry->id); $this->_getGateway()->update($data, $where); } } public function find($id) { $result = $this->_getGateway()->find($id)->current(); $entry = new $this->_entityClass(array( 'id' => $result->id, 'title' => $result->title, 'content' => $result->content, 'published_date' => $result->published_date )); $entry->setReferenceId('author', $result->author_id); return $entry; } public function delete($entry) { if ($entry instanceof ZFExt_Model_Entry) { $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $entry->id); } else { $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $entry); } $this->_getGateway()->delete($where); } }

Et voil! We have altered our Data Mapper to support lazy loading of objects where appropriate. Now, I admit that this is a technically a form of premature optimisation - we have no idea if this helps performance in any way because we have no way of measuring any improvement just yet. But, since I've done this before, I can assume it will help with performance. Database operations are expensive, often the most expensive operation.

9.6.5. Preventing Duplicate Entities With An Identity Map


Another improvement point, not completely performance related, is the use of an Identity Map. To explain what I mean by this, imagine a scenario where you have retrieved 20 entries. Each entry is

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (42 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

retrieved from the our Entry Data Mapper, and leaves the author unset so it can be lazy loaded as we have just implemented. How are authors loaded? By using the Author Data Mapper to retrieve them from the database. With our implementation, this means every entry we load, may also load an author. This sounds reasonable until you look at the relationship between entries and author. Any author may write many entries, so many entries will share the exact same author. This means we can conceivably load the same author from the database many times. This is obviously a problem - our domain objects should be as unique as possible. From the outside looking in, this has no serious side effect other than lots of unnecessary database calls. So what happens if we are changing the author entity? We have lots of them! Changing one will not change the others, so entries within the same process will be using out of date author information. This lack of synchronicity must be eliminated. An obvious solution is to make each unique entity shareable. If we load an author in one entry, and another entry needs the same author, they can somehow locate the first entry's instance of
ZFExt_Model_Author for use. The most common solution is known as the Identity Map pattern.

Yes, it's another Martin Fowler defined design pattern... Here's what Fowler has to say.

Ensures that each object gets loaded only once by keeping every loaded object in a map. Looks up objects using the map when referring to them.

You the man, Martin! While its not that clear from the definition, the Identity Map is also a form of cache in one respect. Once a domain object with a unique id is created or retrieved for the first time, it's registered on the Identity Map so other domain objects can use that instance, if they try to retrieve one with the same id, without making additional database calls through the Data Mapper. Since our Data Mapper are already handling the retrieval and creation of domain objects, it seems they are the most logical place to put the implementation. Of course, since this will be a general map - there's no implementation specific to a Mapper - it's best added to a common parent class to avoid code duplication. This makes me happy for another reason! It's the perfect excuse to make all

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (43 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

Data Mapper share a common class type and push any code duplication from the two Data Mappers to their shared parent class. While we're at it, we can shift an code duplication across our two Data Mappers into this parent class. But, new tests first!
q q q

<?php class ZFExt_Model_EntryMapperTest extends PHPUnit_Framework_TestCase { // ... public function testFindsRecordByIdAndReturnsMappedObjectIfExists() { $entry = new ZFExt_Model_Entry(array( 'id' => 1, 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z' )); // expected rowset result for found entry $dbData = new stdClass; $dbData->id = 1; $dbData->title = 'My Title'; $dbData->content = 'My Content'; $dbData->published_date = '2009-08-17T17:30:00Z'; $dbData->author_id = 1; // set mock expectation on calling Zend_Db_Table::find () $this->_rowset->expects($this->once()) ->method('current') ->will($this->returnValue($dbData)); $this->_tableGateway->expects($this->once()) ->method('find') ->with($this->equalTo(1)) ->will($this->returnValue($this->_rowset)); $mapper = new ZFExt_Model_EntryMapper($this>_tableGateway); $result = $mapper->find(1); $result2 = $mapper->find(1); $this->assertSame($result, $result2); } public function testSavingNewEntryAddsItToIdentityMap() { $author = new ZFExt_Model_Author(array( 'id' => 2,

q q q q q

q q q q q q q q q q q q q q q q q

q q q q q q q q q

q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (44 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q

'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); $entry = new ZFExt_Model_Entry(array( 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author' => $author )); // set mock expectation on calling Zend_Db_Table:: insert() $insertionData = array( 'title' => 'My Title', 'content' => 'My Content', 'published_date' => '2009-08-17T17:30:00Z', 'author_id' => 2 ); $this->_tableGateway->expects($this->once()) ->method('insert') ->with($this->equalTo($insertionData)) ->will($this->returnValue(123)); $mapper = new ZFExt_Model_EntryMapper($this>_tableGateway); $mapper->save($entry); $result = $mapper->find(123); $this->assertSame($result, $entry); } // ... }

q q q q q q q q q q q q

q q q q q q q q

The new test is just like the one we wrote testing the operation of the Data Mapper's find() method. The difference is that this time, we make a second call (without changing the mock object expectations that Zend_Db_Table_Abstract will only be used once) and check that the resulting objects are the same. PHPUnit will go so far as to the check the object ids so this does ensure both results reference the exact same object. We also instantiate a new Mapper object for each test rather than use the one stored in the test class $_mapper property. This prevents calls from other tests setting up a member of the Identity Map and creating a false positive result. Here's the additional test, this time for ZFExt_Model_AuthorMapper.
q q q

<?php class ZFExt_Model_AuthorMapperTest extends PHPUnit_Framework_TestCase {


http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (45 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q

// ... public function testFindsRecordByIdAndReturnsMappedObjectIfExists() { $author = new ZFExt_Model_Author(array( 'id' => 1, 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); // expected rowset result for found entry $dbData = new stdClass; $dbData->id = 1; $dbData->fullname = 'Joe Bloggs'; $dbData->username = 'joe_bloggs'; $dbData->email = 'joe@example.com'; $dbData->url = 'http://www.example.com';; // set mock expectation on calling Zend_Db_Table::find () $this->_rowset->expects($this->once()) ->method('current') ->will($this->returnValue($dbData)); $this->_tableGateway->expects($this->once()) ->method('find') ->with($this->equalTo(1)) ->will($this->returnValue($this->_rowset)); $mapper = new ZFExt_Model_AuthorMapper($this>_tableGateway); $result = $mapper->find(1); $result2 = $mapper->find(1); $this->assertSame($result, $result2); } public function testSavingNewAuthorAddsItToIdentityMap() { $author = new ZFExt_Model_Author(array( 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' )); // set mock expectation on calling Zend_Db_Table:: insert() $insertionData = array( 'username' => 'joe_bloggs', 'fullname' => 'Joe Bloggs', 'email' => 'joe@example.com', 'url' => 'http://www.example.com' ); $this->_tableGateway->expects($this->once())

q q q q q q q q q q q q q q q q q q

q q q q q q q q q

q q q q q q q q q q q q q q q

q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (46 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q

->method('insert') ->with($this->equalTo($insertionData)) ->will($this->returnValue(123)); $mapper = new ZFExt_Model_AuthorMapper($this>_tableGateway); $mapper->save($author); $result = $mapper->find(123); $this->assertSame($result, $author); } // ... }

q q q q q q q q

Our implementation starts by adding the common parent class, ZFExt_Model_Mapper. Both
ZFExt_Model_EntryMapper and ZFExt_Model_AuthorMapper will extend this class.
q q q q q q q q q q

<?php class ZFExt_Model_Mapper { protected $_tableGateway = null; protected $_identityMap = array(); public function __construct(Zend_Db_Table_Abstract $tableGateway) { if (is_null($tableGateway)) { $this->_tableGateway = new Zend_Db_Table($this>_tableName); } else { $this->_tableGateway = $tableGateway; } } protected function _getGateway() { return $this->_tableGateway; } protected function _getIdentity($id) { if (array_key_exists($id, $this->_identityMap)) { return $this->_identityMap[$id]; } } protected function _setIdentity($id, $entity) { $this->_identityMap[$id] = $entity; } }

q q q

q q q q q q q q q q q q q q q q q q q q q q q

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (47 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

All that now remains are making the necessary changes to both Data Mappers to set newly retrieved objects in the Identity Map and preferentially retrieve them from the Map instead of wasting time on another visit to the database. Note that the above methods: _getGateway and
__construct() should be removed from the Data Mapper classes since they will inherit them from

the new parent class.


q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

<?php class ZFExt_Model_EntryMapper extends ZFExt_Model_Mapper { // ... public function save(ZFExt_Model_Entry $entry) { if (!$entry->id) { $data = array( 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $entry->id = $this->_getGateway()->insert($data); $this->_setIdentity($entry->id, $entry); // add new } else { $data = array( 'id' => $entry->id, 'title' => $entry->title, 'content' => $entry->content, 'published_date' => $entry->published_date, 'author_id' => $entry->author->id ); $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $entry->id); $this->_getGateway()->update($data, $where); } } public function find($id) { if ($this->_getIdentity($id)) { return $this->_getIdentity($id); } $result = $this->_getGateway()->find($id)->current(); $entry = new $this->_entityClass(array( 'id' => $result->id, 'title' => $result->title, 'content' => $result->content, 'published_date' => $result->published_date )); $entry->setReferenceId('author', $result->author_id); $this->_setIdentity($id, $entry); // add retrieved return $entry;

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (48 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End
q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q q

} // ... } <?php class ZFExt_Model_AuthorMapper extends ZFExt_Model_Mapper { // ... public function save(ZFExt_Model_Author $author) { if (!$author->id) { $data = array( 'fullname' => $author->fullname, 'username' => $author->username, 'email' => $author->email, 'url' => $author->url ); $author->id = $this->_getGateway()->insert($data); $this->_setIdentity($author->id, $author); } else { $data = array( 'id' => $author->id, 'fullname' => $author->fullname, 'username' => $author->username, 'email' => $author->email, 'url' => $author->url ); $where = $this->_getGateway()->getAdapter() ->quoteInto('id = ?', $author->id); $this->_getGateway()->update($data, $where); } } public function find($id) { if ($this->_getIdentity($id)) { return $this->_getIdentity($id); } $result = $this->_getGateway()->find($id)->current(); $author = new $this->_entityClass(array( 'id' => $result->id, 'fullname' => $result->fullname, 'username' => $result->username, 'email' => $result->email, 'url' => $result->url )); $this->_setIdentity($id, $author); return $author; } // ... }
http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (49 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

This is as far as I'll take our domain model implementation in this chapter. There are more methods that can be added, and other problems that could be resolved. We'll add to the foundation we've created here as we go through the application.

9.7. Conclusion
This has been the first chapter in the book to start delving into source code. As you can tell, the focus is less on teaching Zend_Db and its child classes (the Reference Guide does this very well) and more on how to utilise these database access classes when designing a Model. I've also introduced one of the books other focuses - using testing to drive all development. While code examples that are prepared in advance work well, I do hope the slightly longer route of developing code within chapters using unit tests assists in understanding why and how we are making design decisions. I'm also hoping you have spotted a potential problem. Why are we building a Data Mapper from scratch? I said in the book's introduction that I have few qualms about crying foul when it's needed, and this is a case in point. PHP offers libraries for this sort of thing. There are some great Data Mapper libraries out there, lots of ORM libraries, and even the ZF Incubator itself has a full Data Mapper solution in progress. We should use be using them unless there are specific reasons for not doing so. Zend_Db implements the Row Data Gateway and Table Data Gateway patterns but by itself it is time consuming to implement for anything other than a very simple application. In short, if you want to remain sane, save time on development, and by extension save money on projects, replace it with an external library (or wait for ZF's own Data Mapper solution) on anything more complex than a blog. I know this sounds harsh, and probably it's not what you expected to hear, but it needs saying while you're still in the shallow end of this pond. Does this make Zend Framework somehow less than its alternatives like Symfony or Ruby On Rails? No! Symfony uses an external ORM library itself that just happens to be distributed with the framework - nothing stops you using similar (or the same - Doctrine is very good and I use it myself)

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (50 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

with your applications. Ruby On Rails uses an ActiveRecord implementation bound to the database layer but that hasn't stopped the Ruby community developing solutions like merb's datamapper so objects are not as closely tied to the database schema. It will be interesting to see how this influences Rails 3.0 since merb remained agnostic to any one solution preferring a pluggable system. If you remember nothing else, always remember that a framework offers you lots of features but you should never be required to use all of them if a more suitable alternative library for that feature exists. In a future chapter I'll spend more time on one of these Zend_Db alternatives. So what was the point of this chapter? Just because ready to rock solutions exist, it doesn't mean we can't understand them and implement them ourselves. A project may be too simple (you wouldn't use an ORM library in a simple transaction script), carry too much legacy code to replace with anything other than simple abstraction, you might simply be told what to use by someone higher up the food chain or you may just prefer doing it yourself out of some other concern. A good example here is when the backend storage is not a relational database - something becoming more common as document and object based alternatives materialise. For whatever reason, the chapter's intent is to show you how to implement a better solution than vanilla Zend_Db.

Prev Chapter 8. Developing A Blogging Application

Next Chapter 10. Setting The Design With Home Zend_View, Zend_Layout, HTML 5 and Yahoo! User Interface Library

Copyright 2009 Pdraic Brady Text is licensed under a Creative Commons Attribution-Non-Commercial-No Derivative Works 3.0 Unported License and source code under a New BSD License

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (51 de 52)28/10/2011 12:54:13

Chapter 9. Implementing The Domain Model: Entries and Authors - Zend Framework Book: Surviving The Deep End

http://www.survivethedeepend.com/zendframeworkbook/en/1.0/implementing.the.domain.model.entries.and.authors (52 de 52)28/10/2011 12:54:13

Вам также может понравиться