Zend Framework, A Beginner's Guide Framework Beginners
User Manual:
Open the PDF directly: View PDF .
Page Count: 465
Download | ![]() |
Open PDF In Browser | View PDF |
Zend Framework: A Beginner’s Guide Vikram Vaswani New York Chicago San Francisco Lisbon London Madrid Mexico City Milan New Delhi San Juan Seoul Singapore Sydney Toronto Copyright © 2010 by The McGraw-Hill Companies. All rights reserved. Except as permitted under the United States Copyright Act of 1976, no part of this publication may be reproduced or distributed in any form or by any means, or stored in a database or retrieval system, without the prior written permission of the publisher. ISBN: 978-0-07-163940-8 MHID: 0-07-163940-3 The material in this eBook also appears in the print version of this title: ISBN: 978-0-07-163939-2, MHID: 0-07-163939-X. All trademarks are trademarks of their respective owners. Rather than put a trademark symbol after every occurrence of a trademarked name, we use names in an editorial fashion only, and to the benefit of the trademark owner, with no intention of infringement of the trademark. Where such designations appear in this book, they have been printed with initial caps. McGraw-Hill eBooks are available at special quantity discounts to use as premiums and sales promotions, or for use in corporate training programs. To contact a representative please e-mail us at bulksales@mcgraw-hill.com. Information has been obtained by McGraw-Hill from sources believed to be reliable. However, because of the possibility of human or mechanical error by our sources, McGraw-Hill, or others, McGraw-Hill does not guarantee the accuracy, adequacy, or completeness of any information and is not responsible for any errors or omissions or the results obtained from the use of such information. TERMS OF USE This is a copyrighted work and The McGraw-Hill Companies, Inc. (“McGrawHill”) and its licensors reserve all rights in and to the work. Use of this work is subject to these terms. Except as permitted under the Copyright Act of 1976 and the right to store and retrieve one copy of the work, you may not decompile, disassemble, reverse engineer, reproduce, modify, create derivative works based upon, transmit, distribute, disseminate, sell, publish or sublicense the work or any part of it without McGraw-Hill’s prior consent. You may use the work for your own noncommercial and personal use; any other use of the work is strictly prohibited. Your right to use the work may be terminated if you fail to comply with these terms. THE WORK IS PROVIDED “AS IS.” McGRAW-HILL AND ITS LICENSORS MAKE NO GUARANTEES OR WARRANTIES AS TO THE ACCURACY, ADEQUACY OR COMPLETENESS OF OR RESULTS TO BE OBTAINED FROM USING THE WORK, INCLUDING ANY INFORMATION THAT CAN BE ACCESSED THROUGH THE WORK VIA HYPERLINK OR OTHERWISE, AND EXPRESSLY DISCLAIM ANY WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. McGraw-Hill and its licensors do not warrant or guarantee that the functions contained in the work will meet your requirements or that its operation will be uninterrupted or error free. Neither McGraw-Hill nor its licensors shall be liable to you or anyone else for any inaccuracy, error or omission, regardless of cause, in the work or for any damages resulting therefrom. McGraw-Hill has no responsibility for the content of any information accessed through the work. Under no circumstances shall McGraw-Hill and/or its licensors be liable for any indirect, incidental, special, punitive, consequential or similar damages that result from the use of or inability to use the work, even if any of them has been advised of the possibility of such damages. This limitation of liability shall apply to any claim or cause whatsoever whether such claim or cause arises in contract, tort or otherwise. For Tonka, who keeps asking "Why?", and Farah, who always knows the answer. About the Author Vikram Vaswani is the founder and CEO of Melonfire (http:// www.melonfire.com/), a consultancy firm with special expertise in open-source tools and technologies. He has 12 years of experience working with PHP and MySQL as a Web application developer and product manager, and has created and deployed a variety of PHP applications for corporate intranets, high-traffic Internet Web sites, and mission-critical thin-client applications. Vikram is also a passionate proponent of the open-source movement and is a regular contributor of articles and tutorials on PHP, MySQL, XML, and related tools to the community through his regular columns on the Zend Developer Zone and IBM DeveloperWorks. He is the author of Zend Technologies’ well-regarded PHP 101 series for PHP beginners, and his previous books include MySQL: The Complete Reference (http://www.mysql-tcr.com/), How to Do Everything with PHP & MySQL (http://www .everythingphpmysql.com/), PHP Programming Solutions (http://www.php-programmingsolutions.com/), and PHP: A Beginner’s Guide (http://www.php-beginners-guide.com/). A Felix Scholar at the University of Oxford, England, Vikram combines his interest in Web application development with various other activities. When not dreaming up plans for world domination, he amuses himself by reading crime fiction, watching movies, playing squash, blogging, and keeping a wary eye out for Agent Smith. Read more about him and Zend Framework: A Beginner’s Guide at http://www.zf-beginners-guide.com. About the Technical Editor Ryan Mauger is the Lead Developer for Lupimedia (http://www.lupimedia.com/), a multimedia design agency in Somerset, England that specializes in bespoke content management systems for design-oriented Web sites. Ryan is a keen Zend Framework supporter and contributor, and can often be found answering questions and guiding people on IRC (#channel). When not evangelizing the Zend Framework, Ryan is a proud father, and enjoys escaping to the lakes for a spot of fly fishing. Read more about him and his work at http:// www.rmauger.co.uk/. Contents FOREWORD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ACKNOWLEDGMENTS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . INTRODUCTION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii xiv xv 1 Introducing the Zend Framework ................................................................. Overview ........................................................................................................................ Features .......................................................................................................................... Standards Compliance and Best Practices ............................................................ Reusability ............................................................................................................ Internationalization ............................................................................................... Open Source .......................................................................................................... Community Support .............................................................................................. Unique Advantages ......................................................................................................... Loose Coupling ..................................................................................................... Rapid Release Cycle ............................................................................................. Unit Testing Policy ............................................................................................... Code-Generation Tools ......................................................................................... Market Credibility ................................................................................................ Third-Party Application Interoperability .............................................................. Commercial Support Options ............................................................................... Extensive Documentation ..................................................................................... Application Environment ............................................................................................... Installing the Zend Framework ...................................................................................... Try This 1-1: Starting a New Project ............................................................................. Understand Application Requirements ................................................................. Create the Application Directory .......................................................................... Create the Application Skeleton ........................................................................... Add Zend Framework Libraries ........................................................................... Define Virtual Host Settings ................................................................................. Using the Command-Line Tool ...................................................................................... Summary ........................................................................................................................ 3 4 4 4 5 5 5 5 5 6 6 6 6 6 7 7 7 8 10 11 11 11 13 13 14 17 2 Working with Models, Views, Controllers, and Routes ............................... Understanding Basic Concepts ....................................................................................... Models .................................................................................................................. Views .................................................................................................................... Controllers ............................................................................................................ v 1 19 20 21 22 23 vi Zend Framework: A Beginner’s Guide Modules ................................................................................................................ Routes ................................................................................................................... Layouts ................................................................................................................. Understanding Component Interaction .......................................................................... Looking Behind the Default Index Page .............................................................. Understanding the Modular Directory Layout ............................................................... Try This 2-1: Using a Modular Directory Layout .......................................................... Creating the Default Module ................................................................................ Updating the Application Configuration File ....................................................... Understanding Master Layouts and Custom Routes ...................................................... Updating the Application Index Page ................................................................... Setting a Master Layout ........................................................................................ Using a Custom Route .......................................................................................... Try This 2-2: Serving Static Content .............................................................................. Defining Custom Routes ....................................................................................... Defining the Controller ........................................................................................ Defining the View ................................................................................................. Updating the Master Layout ................................................................................. Summary ........................................................................................................................ 24 25 25 26 28 30 32 33 33 33 35 36 37 39 39 40 41 42 44 3 Working with Forms ....................................................................................... Understanding Form Basics ........................................................................................... Creating Forms and Form Elements ............................................................................... Working with Form Elements ............................................................................... Setting Required and Default Values .................................................................... Filtering and Validating Form Input ............................................................................... Using Input Filters ................................................................................................ Using Input Validators .......................................................................................... Retrieving and Processing Form Input ................................................................. Try This 3-1: Creating a Contact Form .......................................................................... Defining the Form ................................................................................................. Using a Custom Namespace ................................................................................. Defining a Custom Route ..................................................................................... Defining Controllers and Views ........................................................................... Updating the Master Layout ................................................................................. Customizing Form Appearance ...................................................................................... Using Custom Error Messages ............................................................................. Using Display Groups ........................................................................................... Using Decorators .................................................................................................. Summary ........................................................................................................................ 47 4 Working with Models ...................................................................................... Understanding Models ................................................................................................... Model Patterns ...................................................................................................... Model Scope ......................................................................................................... 48 53 55 65 70 70 73 81 82 82 85 85 86 88 90 91 93 94 99 101 102 105 105 Contents Installing Doctrine .......................................................................................................... Try This 4-1: Generating and Integrating Doctrine Models ........................................... Initializing the Application Database .................................................................... Generating Doctrine Models ................................................................................. Setting Model Relationships ................................................................................. Autoloading Doctrine ........................................................................................... Working with Doctrine Models ...................................................................................... Retrieving Records ............................................................................................... Adding, Updating, and Deleting Records ............................................................. Try This 4-2: Retrieving Database Records ................................................................... Creating a New Module ........................................................................................ Defining a Custom Route ..................................................................................... Defining the Controller ......................................................................................... Defining the View ................................................................................................. Summary ........................................................................................................................ 107 108 109 113 115 116 118 118 120 122 122 122 122 124 127 5 Handling CRUD Operations .......................................................................... Try This 5-1: Creating Database Records ...................................................................... Defining the Form ................................................................................................. Defining Controllers and Views ........................................................................... Working with Administrative Actions ............................................................................ Structure ................................................................................................................ Routing ................................................................................................................. Layout ............................................................................................................................. Try This 5-2: Listing, Deleting, and Updating Database Records ................................. Setting the Administrative Layout ........................................................................ Defining Custom Routes ....................................................................................... Defining the List Action and View ....................................................................... Defining the Delete Action ................................................................................... Defining the Update Form .................................................................................... Defining the Update Action and View .................................................................. Updating the Display Action ................................................................................ Adding User Authentication ........................................................................................... Try This 5-3: Creating a Login/Logout System ............................................................. Defining Custom Routes ....................................................................................... Defining the Login Form ...................................................................................... Defining the Authentication Adapter .................................................................... Defining the Login Action and View .................................................................... Defining the Logout Action .................................................................................. Protecting Administrative Actions ........................................................................ Updating the Master Layout ................................................................................. Summary ........................................................................................................................ 129 130 130 136 140 140 141 142 142 142 144 146 148 150 154 157 158 161 161 161 162 165 167 167 168 170 vii viii Zend Framework: A Beginner’s Guide 6 Indexing, Searching, and Formatting Data ................................................... Try This 6-1:Searching and Filtering Database Records ............................................... Defining the Search Form ..................................................................................... Defining the Controller and View ......................................................................... Updating the Master Layout ................................................................................. Adding Full-Text Search ................................................................................................ Indexing Data ........................................................................................................ Searching Data ...................................................................................................... Try This 6-2: Creating a Full-Text Search Engine ......................................................... Defining the Index Location ................................................................................. Defining Custom Routes ....................................................................................... Defining the Index Action and View .................................................................... Updating the Summary View ................................................................................ Updating the Search Form .................................................................................... Updating the Search Action and View .................................................................. Handling Multiple Output Types .................................................................................... Try This 6-3: Expressing Search Results in XML .......................................................... Enabling the XML Context ................................................................................... Defining the XML View ....................................................................................... Summary ........................................................................................................................ 173 7 Paging, Sorting, and Uploading Data ............................................................ Try This 7-1: Paging and Sorting Database Records ..................................................... Adding Page Numbers to Routes .......................................................................... Updating the Index Controller and View .............................................................. Adding Sort Criteria to Routes ............................................................................. Updating the Controller and View ........................................................................ Working with File Uploads ............................................................................................ Try This 7-2: Enabling Image Uploads .......................................................................... Defining the Upload Destination .......................................................................... Updating the Form Definition .............................................................................. Updating the Create Action .................................................................................. Updating the Display Action and View ................................................................ Updating the Delete Action .................................................................................. Working with Configuration Data .................................................................................. Reading Configuration Files ................................................................................. Writing Configuration Files .................................................................................. Try This 7-3: Configuring Application Settings ............................................................. Defining the Configuration Form ......................................................................... Defining the Configuration File ............................................................................ Defining Custom Routes ....................................................................................... Defining the Controller and View ......................................................................... Updating the Master Layout ................................................................................. Using Configuration Data ..................................................................................... Summary ........................................................................................................................ 199 174 174 177 179 181 182 184 185 185 186 186 188 188 189 192 194 194 194 196 200 201 201 204 204 209 213 213 213 215 216 218 222 222 224 227 227 229 230 231 233 235 239 Contents 8 Logging and Debugging Exceptions .............................................................. Understanding Exceptions .............................................................................................. Understanding the Default Error-Handling Process ....................................................... Using Custom Exception Classes ......................................................................... Controlling Exception Visibility ........................................................................... Try This 8-1: Creating a Custom Error Page .................................................................. Logging Data .................................................................................................................. Writing Log Messages .......................................................................................... Adding Data to Log Messages .............................................................................. Formatting Log Messages ..................................................................................... Try This 8-2: Logging Application Exceptions .............................................................. Defining the Log Location .................................................................................... Defining the Database Log Writer ........................................................................ Updating the Error Controller ............................................................................... Summary ........................................................................................................................ 241 9 Understanding Application Localization ...................................................... Understanding Localization and Locales ....................................................................... Setting the Application Locale .............................................................................. Localizing Numbers ....................................................................................................... Localizing Dates and Times ........................................................................................... Localizing Currencies ..................................................................................................... Localizing Measurements ............................................................................................... Localizing Strings .......................................................................................................... Working with Adapters and Data Sources ............................................................ Using the Application Locale ............................................................................... Using the Translation View Helper ....................................................................... Try This 9-1: Localizing the Example Application ........................................................ Setting the Application Locale .............................................................................. Localizing Numbers and Dates ............................................................................. Defining String Localization Targets .................................................................... Creating Translation Sources ................................................................................ Registering the Translation Object ....................................................................... Supporting Manual Locale Selection .................................................................... Updating the Master Layout ................................................................................. Summary ........................................................................................................................ 271 10 Working with News Feeds and Web Services ............................................... Working with News Feeds ............................................................................................. Understanding News Feed Formats ...................................................................... Consuming News Feeds ....................................................................................... Creating News Feeds ............................................................................................ Accessing Web Services ................................................................................................. Understanding Web Services ................................................................................ Consuming Web Services ..................................................................................... 311 242 246 249 251 252 254 254 259 259 263 263 264 265 268 272 274 277 279 282 285 287 289 291 293 294 294 294 298 300 302 306 307 309 312 313 313 317 319 319 322 ix x Zend Framework: A Beginner’s Guide Try This 10-1: Integrating Twitter and Blog Search Results .......................................... Defining Custom Routes ....................................................................................... Defining the Controller and View ......................................................................... Updating the Master Layout ................................................................................. Creating REST-Based Web Services .............................................................................. Understanding REST Routes ................................................................................ Try This 10-2: Implementing REST-Based Web Services ............................................. Creating a New Module ........................................................................................ Defining the Controller ......................................................................................... Defining the GET Actions .................................................................................... Defining the POST Action .................................................................................... Initializing the REST Routes ................................................................................ Summary ........................................................................................................................ 328 328 328 331 332 332 334 334 334 336 339 340 342 11 Working with User Interface Elements ......................................................... Working with Navigation Structures .............................................................................. Understanding Pages and Containers ................................................................... Rendering Navigational Elements ........................................................................ Try This 11-1: Adding a Navigation Menu .................................................................... Defining Navigation Pages and Containers .......................................................... Registering the Navigation Object ........................................................................ Creating the Navigation Action Helper ................................................................ Using the Menu View Helper ............................................................................... Working with the Dojo Toolkit ....................................................................................... Handling Dojo Data .............................................................................................. Using the Dojo View Helpers ............................................................................... Using Dojo Form Elements .................................................................................. Try This 11-2: Adding a Dojo Autocomplete Widget .................................................... Updating the Contact Form .................................................................................. Initializing the Dojo View Helper ......................................................................... Updating the Master Layout ................................................................................. Updating the Controller ........................................................................................ Try This 11-3: Adding a YUI Calendar Widget ............................................................. Updating the Form ................................................................................................ Updating the Master Layout ................................................................................. Updating the Controller ........................................................................................ Updating the View ................................................................................................ Summary ........................................................................................................................ 345 12 Optimizing Performance ................................................................................ Analyzing Performance .................................................................................................. Benchmarking ....................................................................................................... Code Profiling ....................................................................................................... Query Profiling ..................................................................................................... 381 346 346 351 355 355 357 358 359 361 361 362 365 368 368 369 370 371 372 372 374 375 377 378 382 382 384 387 Contents Caching Data .................................................................................................................. Understanding Cache Operations ......................................................................... Understanding Cache Frontends and Backends ................................................... Using the Cache Manager ..................................................................................... Caching Doctrine Queries ..................................................................................... Optimizing Application Code ......................................................................................... Query Tuning ........................................................................................................ Lazy Loading ........................................................................................................ Try This 12-1: Improving Application Performance ...................................................... Configuring the Application Cache ...................................................................... Caching Translation Strings .................................................................................. Caching Query Results ......................................................................................... Caching Twitter and Blog Feeds ........................................................................... Summary ........................................................................................................................ 392 392 395 398 399 401 401 404 405 405 406 406 408 410 A Installing and Configuring Required Software ............................................ Obtaining the Software ................................................................................................... Installing and Configuring the Software ........................................................................ Installing on UNIX ............................................................................................... Installing on Windows .......................................................................................... Testing the Software ....................................................................................................... Testing MySQL ..................................................................................................... Testing PHP .......................................................................................................... Setting the MySQL Superu-User Password ................................................................... Summary ........................................................................................................................ 413 Index 414 416 416 420 426 426 427 428 428 ................................................................................................................. 429 xi This page intentionally left blank Foreword T he PHP ecosystem has changed dramatically in the past six years. Prior to PHP 5’s advent, we PHP developers were primarily creating our projects on an ad-hoc basis, each project differing from its predecessor; if we paid attention, each project improved on the previous— but there was no guarantee. While tools and practices existed for managing code quality and standards, they were still maturing, and not in widespread use. The idea of using PHP as the basis for a stable, enterprise-worthy application was widely scoffed as a result—despite the fact that it was powering some of the most trafficked sites on the Web. With the advent of PHP 5, we started seeing more of a focus on solid programming practices. With a revised and reworked object model, we now had a solid foundation on which to build our re-usable objects. Tools such as PHPUnit capitalized on the object model to simplify and enable solid testing practices. These in turn led to an increased look at where code quality fit in the PHP application life cycle. It is from this ecosystem that PHP frameworks began to arise. While several began in PHP 4, the idea took off in PHP 5, and a handful of frameworks started taking over the landscape. These frameworks aim to provide best practices to their users, and repeatable, reusable structure for the applications they build. Among these is Zend Framework. Zend Framework’s mission, from its Web site, is simply this: Extending the art and spirit of PHP, Zend Framework is based on simplicity, object-oriented best practices, corporate-friendly licensing, and a rigorously tested agile codebase. Zend Framework is focused on building more secure, reliable, and modern Web 2.0 applications and Web services, and consuming widely available APIs. In this book, you’ll learn how Zend Framework approaches these goals, from an author who is both well-versed in the subject as well as a capable and clear technical writer. You’ll get both thorough and understandable explanations as well as complete examples—and hopefully come away from reading with an appetite to develop your own applications using what has become the de facto standard in the industry: Zend Framework. —Matthew Weier O’Phinney, Project Lead, Zend Framework xiii Acknowledgments T he Zend Framework is a complex piece of software, and writing a book about it is not—as I found out over the last eight months—a particularly simple task. Fortunately, I was aided in this process by a diverse and dynamic group of people, all of whom played an important part in getting this book into your hands. First and foremost, a gigantic thank you to my wife, who supported me through the entire process and made sure I had a comfortable and stress-free working environment. I’m pretty sure this book would never have made it out into the world without her help. Thanks, babe! The editorial and marketing team at McGraw-Hill deserves an honorable mention here as well. This is my sixth book with them and, as usual, they have been an absolute pleasure to work with. Acquisitions coordinator Joya Anthony, editorial supervisor Patty Mon, and executive editors Jane Brownlow and Megg Morin all guided this manuscript through the development process and played a huge role in turning it from pixels on a page to the polished and professional product you hold in your hands. I would like to thank them for their expertise, dedication, and efforts on my behalf. I’d also like to single out Ryan Mauger, the technical editor for this book, for special praise. Ryan reviewed every line of code and applied his extensive knowledge of the Zend Framework to make the sure that the final product was both technically sound and reflective of current best practices. I’d like to thank him for his help and advice throughout the bookwriting process. If you’re ever in the market for a PHP expert, you can't do better than him! Finally, for making the entire book-writing process more enjoyable than it usually is, thanks to: Patrick Quinlan, Ian Fleming, Bryan Adams, the Stones, Peter O’Donnell, MAD Magazine, Scott Adams, Gary Larson, VH1, Britney Spears, George Michael, Kylie Minogue, Buffy the Vampire Slayer, Farah Malegam, Stephen King, Shakira, Anahita Marker, John le Carre, The Saturdays, Barry White, Gwen Stefani, Ping Pong, Robert Crais, Robert B. Parker, Baz Luhrmann, Stefy, Anna Kournikova, John Connolly, Wasabi, Omega, Pidgin, Cal Evans, Ling’s Pavilion, Tonka and his evil twin Bonka, Richelle Mead, Din Tai Fung, HBO, Mark Twain, Tim Burton, Harish Kamath, Madonna, John Sandford, Dollhouse, Iron Man, the London Tube, Dido, Google.com, The Matrix, Lee Child, Michael Connelly, Celio, Antonio Prohias, Quentin Tarantino, Alfred Hitchcock, Woody Allen, Kinokuniya, Percy Jackson, Jennifer Hudson, Mambo’s and Tito’s, Easyjet, Humphrey Bogart, Thai Pavilion, Wikipedia, Amazon.com, U2, Ubuntu, The Three Stooges, Pacha, Oscar Wilde, Hugh Grant, Alex Rider, Punch, Kelly Clarkson, Scott Turow, Slackware Linux, Calvin and Hobbes, Yo! Sushi, Blizzard Entertainment, Alfred Kropp, Otto, Pablo Picasso, Popeye and Olive Oyl, Dennis Lehane, Trattoria, Dire Straits, Bruce Springsteen, David Mitchell, The West Wing, Wagamama, Santana, Rod Stewart, and all my friends, at home and elsewhere. xiv Introduction T he Zend Framework is indeed, in the words of the immortal Ernest Hemingway, a “moveable feast.” Conceived and implemented as a robust, feature-rich component library for PHP developers, it allows you to quickly and efficiently perform a variety of common application development tasks, including creating and validating form input, processing XML, generating dynamic menus, paginating data, working with Web services, and much, much more! Perhaps the most important contribution of the Zend Framework, however, is that it has advanced the art of PHP development by introducing PHP developers to a more standardized and structured approach to PHP programming. This structured approach results in cleaner, more maintainable and more secure applications, and it’s one of the key reasons that more and more developers are switching away from the older “ad-hoc” style of programming to the newer, framework-based approach. For many novice PHP developers, though, the Zend Framework is a scary leap into the unknown. The Model-View-Controller pattern, the loosely coupled architecture, and the large number of available components often serve to befuddle developers who are used to “regular” procedural programming and find framework-based development too complex to understand. That’s where this book comes in. If you’re one of the many millions of users who’ve heard about the Zend Framework and wondered what it could do for you, this is the book for you. It takes a close look at some of the Zend Framework’s most important features—such as ModelView-Controller implementation, routing, input validation, internationalization, and caching— and shows you how to use them in a practical context. It also walks you through the process of building a complete Web application with the Zend Framework, starting with the basics and then adding in more complex features such as data pagination and sorting, user authentication, exception handling, localization, and Web services. In short, it gives you the knowledge you need to supercharge your PHP development by leveraging the power of the Zend Framework. Who Should Read This Book As you might have guessed from the title, Zend Framework: A Beginner’s Guide is intended for users who are new to the Zend Framework. It assumes that you know the basics of PHP programming (including the new object model in PHP 5.x) and have some familiarity with HTML, CSS, SQL, XML, and JavaScript programming. If you’re completely new to PHP, this is probably not the first book you should read—instead, consider working your way through the introductory PHP tutorials at http://www.melonfire.com/community/columns/trog/ or purchasing a beginner guide such as How to Do Everything with PHP & MySQL (http://www .everythingphpmysql.com/) or PHP: A Beginner’s Guide (http://www.php-beginners-guide .com/) and then returning to this book. xv xvi Zend Framework: A Beginner’s Guide In order to work with the example application in this book, you will need a functioning PHP 5.x installation, ideally with an Apache 2.2.x Web server and a MySQL 5.x database server. You’ll also need (obviously!) the latest version of the Zend Framework. Details on how to obtain and configure a PHP development environment are available in the Appendix of this book, while Chapter 1 covers the Zend Framework installation process in detail. What This Book Covers Since Zend Framework: A Beginner’s Guide is aimed at users new to the Zend Framework, the first half of the book starts out by explaining basic concepts and solving fairly easy problems. Once you’ve gained familiarity with the basics of Zend Framework development, the second half of the book brings up more complex problems, such as internationalization and performance optimization, and illustrates possible solutions. This also means that you should read the chapters in order, since each chapter develops knowledge that you will need in subsequent chapters. Here’s a quick overview of what each chapter covers: L Chapter 1, “Introducing the Zend Framework,” introduces the Zend Framework, explaining the benefits of framework-based development and walking you through the process of creating a new Zend Framework project. L Chapter 2, “Working with Models, Views, Controllers, and Routes” discusses the basics of the Model-View-Controller (MVC) pattern and introduces you to important concepts like routing, global layouts, and modules. L Chapter 3, “Working with Forms,” introduces the Zend_Form component, explaining how to programmatically create and validate Web forms, protect forms from attack, and control form error messages. L Chapter 4, “Working with Models,” discusses the role of models in a Zend Framework application and introduces the Doctrine ORM toolkit and the Zend Framework bootstrapper. L Chapter 5, “Handling CRUD Operations,” discusses how to integrate Doctrine models with Zend Framework controllers to implement the four common CRUD operations, add authentication to an application, and build a simple login/logout system. L Chapter 6, “Indexing, Searching, and Formatting Data,” discusses data indexing and searching, and also demonstrates how to add support for multiple output types to a Zend Framework application. L Chapter 7, “Paging, Sorting, and Uploading Data,” discusses how to paginate and sort database query results; filter and process file uploads; and read and write configuration files in INI and XML formats. L Chapter 8, “Logging and Debugging Exceptions,” explains how the Zend Framework handles application-level exceptions and demonstrates how to add exception logging and filtering to a Zend Framework application. Introduction L Chapter 9, “Understanding Application Localization,” discusses the various tools available in the Zend Framework to build a localized, multilingual application that can be easily “ported” to different countries and regions. L Chapter 10, “Working with News Feeds and Web Services,” discusses how to use the Zend Framework to generate and read Atom or RSS news feeds; access third-party Web services using SOAP or REST; and allow developers to access your application using REST. L Chapter 11, “Working with User Interface Elements,” discusses how to improve site navigation with menus, breadcrumbs, and sitemaps, and also explains the Zend Framework’s Dojo integration with examples of an AJAX-enabled autocomplete form field and a pop-up calendar widget. L Chapter 12, “Optimizing Performance,” discusses various techniques for measuring and improving Web application performance, including benchmarking, stress testing, code profiling, caching, and query optimization. L Appendix, “Installing and Configuring Required Software,” guides you through the process of installing and configuring an Apache/PHP/MySQL development environment on Windows and Linux. Conventions This book uses different types of formatting to highlight special advice. Here’s a list: NOTE Additional insight or information on the topic TIP A technique or trick to help you do things better CAUTION Something to watch out for Ask the Expert Q: A: A frequently asked question, . . . . . . and its answer xvii xviii Zend Framework: A Beginner’s Guide In the code listings in this book, text highlighted in bold is a command to be entered at the prompt. For example, in the following listing mysql> INSERT INTO movies (mtitle, myear) VALUES ('Rear Window', 1954); Query OK, 1 row affected (0.06 sec) the line in bold is a query that you would type in at the command prompt. You can use this as a guide to try out the commands in the book. Companion Web Site You can find the code for the example application discussed in this book at its companion Web site, http://www.zf-beginners-guide.com/. Code archives are organized by chapter, and may be directly downloaded and used in your Zend Framework development environment. Chapter 1 Introducing the Zend Framework 1 2 Zend Framework: A Beginner’s Guide Key Skills & Concepts L Learn the benefits of framework-based development L Understand the history and unique advances of the Zend Framework L Understand the structure of a Zend Framework application L Install and start using the Zend Framework I t’s no exaggeration to say that PHP is today one of the most popular programming languages in the world, and the toolkit of choice for millions of Web application developers across the planet. According to recent statistics, the language is in use on more than 22 million Web sites and a third of the world’s Web servers—no small feat, especially when you consider that PHP is developed and maintained entirely by a worldwide community of volunteers with no commercial backing whatsoever! The reasons for PHP’s popularity are not hard to understand. It’s scalable, easily available, and plays well with third-party software. It uses clear, simple syntax and delights in non-obfuscated code, making it easy to learn and use and encouraging rapid application development. And it has a massive advantage over commercial programming toolkits, because it’s available free of charge for a variety of platforms and architectures, including UNIX, Microsoft Windows, and Mac OS, under an open-source license. Developers too report high levels of satisfaction with PHP. In an August 2009 study of ten scripting languages by Evans Data Corporation, PHP developers had the highest user satisfaction levels (followed closely by Ruby and Python users). In particular, PHP ranked highest for cross-platform compatibility, availability and quality of tools, and performance, and second highest for maintainability and readability, extensibility, ease of use, and security. For organizations and independent developers, all these facts add up to just one thing: Using PHP saves both money and time. Building applications with PHP costs less, because the language can be used for a variety of purposes without payment of licensing fees or investment in expensive hardware or software. And using PHP also reduces development time without sacrificing quality, because of the easy availability of ready-made, robust, and communitytested widgets and extensions that developers can use to painlessly add new functions to the language. Now, although it might not seem apparent at first glance, PHP’s much-vaunted ease of use is both good and bad. It’s good because unlike, say, C++ or Java, PHP programs are relatively easy to read and understand, and this encourages novice programmers to experiment with the language and pick up the basics without requiring intensive study. It’s bad because PHP’s corresponding lack of “strictness” can lull those same programmers into a false sense of security and encourage them to write applications for public consumption without awareness of the necessary standards for code quality, security, and reusability. Chapter 1: Introducing the Zend Framework With this in mind, there’s been a concerted and visible effort in the PHP community over the last few years to move from ad-hoc “anything goes” programming to a more standardized, framework-oriented approach. Not only does this approach make it easier to get up and running when building a PHP application from scratch, but it produces cleaner, more consistent, and more secure application code. This chapter, and the remainder of this book, introduces you to one such framework, the Zend Framework, which provides a flexible and scalable approach to building PHP applications for serious developers. Overview In the words of its official Web site (http://framework.zend.com/), the Zend Framework is “an open source framework for developing web applications and services with PHP 5 […] based on simplicity, object-oriented best practices, corporate friendly licensing, and a rigorously tested agile codebase.” It provides a comprehensive set of tools to build and deploy PHP-based Web applications, with built-in APIs for common functions like security, input validation, data caching, database and XML operations, and internationalization. Unlike many other frameworks, the Zend Framework uses a “loosely coupled” architecture. Simply put, this means that although the framework itself consists of numerous components, these components are largely independent and have minimal links to each other. This loosely coupled architecture helps in producing lightweight applications, because developers can choose to use only the specific components they need for the task at hand. So, for example, developers looking to add authentication or caching to their application can directly make use of the Zend_Auth or Zend_Cache components, without needing the rest of the framework. The Zend Framework also provides a complete implementation of the Model-ViewController (MVC) pattern, which allows application business logic to be separated from the user interface and data models. This pattern is recommended for applications of medium to large complexity and is commonly used for Web application development, as it encourages code reusability and produces a more manageable code structure. Zend Framework’s implementation of the MVC pattern is discussed in detail in Chapter 2. The Zend Framework is created and maintained by Zend Technologies, a commercial software vendor whose founders, Andi Gutmans and Zeev Suraski, were also responsible for the first major rewrite of the PHP parser, released as PHP 3.0 in 1997. The first version of the Zend Framework, v1.0, was released in July 2007 and contained 35 core components, including components for caching, authentication, configuration management, database access, RSS and Atom feed generation, and localization. Since then, the framework has been through numerous iterations with the most recent release, v1.10, now containing more than 65 components that support (among other things) Adobe’s Action Message Format (AMF), Google’s GData APIs, and Amazon’s EC2 and SQS Web services. Fortunately, the increase in the number of components has been accompanied by a corresponding increase in documentation—the manual for Zend Framework v1.9 (circa 2009) weighs in at 3.7MB, as compared to the 780KB manual that shipped with Zend Framework v1.0 in 2007. 3 4 Zend Framework: A Beginner’s Guide Although Zend Technologies operates commercially in a number of different markets, it makes the Zend Framework available to the public as an open-source project under the BSD License, thereby allowing it to be freely used in proprietary commercial products without the payment of a license fee. This “business-friendly” licensing policy has made the Zend Framework popular with both corporate and individual users. Startups, Fortune 500 companies, independent developers, and PHP hobbyists are all fans of the project—as of this writing, the Zend Framework has been downloaded more than 10 million times and there are more than 400 open-source projects that are either based on, or extend, the Zend Framework. A vibrant, enthusiastic developer community can be found swapping bug patches and tips on the mailing list and wiki, with additional support coming from the online manual and reference guide. The community is also encouraged to “give back” to the framework by submitting new components—there are currently over 500 independent contributors to the project—so long as the contributions meet Zend’s requirements for documentation and unit testing. Features You might be wondering why using the Zend Framework is a better idea than simply rolling your own code, the way you’re used to right now. Well, here are some reasons. Standards Compliance and Best Practices Unlike some other programming languages, PHP doesn’t enforce a common coding standard. As a result, the manner in which PHP applications are written differs significantly from developer to developer, making it hard to ensure project-wide consistency. PHP’s relative lack of “strictness” can also produce code that fails to adhere to best practices, rendering it vulnerable to attack. The Zend Framework, on the other hand, incorporates current thinking on best practices, provides a standard filesystem layout, and provides built-in support for common application development tasks such as input validation and sanitization. Therefore, using it as the basis for a PHP project automatically produces higher-quality code and an application that’s more forward-leaning on security issues. Additionally, because the Zend Framework is welldocumented, developers joining the project team at a later date will have a much shorter learning curve and can begin contributing to the project faster. Reusability The Zend Framework is completely object-oriented and makes full use of the new object model in PHP 5.x. This object-oriented programming (OOP) architecture encourages code reusability, allowing developers to significantly reduce the time spent writing duplicate code. This fact is particularly important in the context of Web applications, which often need to expose multiple interfaces to their data. Suppose, for example, that you wish to build an XML interface to your application’s existing search engine functionality. With the Zend Framework, this is as simple as defining a new view that takes care of reformatting controller output in XML. It’s not necessary to rewrite any of the existing controller logic, and the entire process is transparent and easy to accomplish. Chapter 1: Introducing the Zend Framework Internationalization As a project that is intended for use in Web application development, it would be unusual indeed if the Zend Framework did not include comprehensive support for application internationalization and localization. The Zend_Locale component allows for application-level control over the user’s locale, while the Zend_Translate component makes it possible to support multilingual applications that include Latin, Chinese, and European character sets. Other useful components include Zend_Date and Zend_Currency, for localized date/time and currency formatting. Open Source The Zend Framework is an open-source project. Although the project is sponsored by Zend Technologies, much of the development is handled by a worldwide team of volunteers who take care of fixing bugs and adding new features. Zend Technologies provides direction to the project, as well as a group of “master engineers” who make decisions on what gets included in the final product. As noted earlier, the framework may be used without payment of licensing fees or investments in expensive hardware or software. This reduces software development costs without affecting either flexibility or reliability. The open-source nature of the code further means that any developer, anywhere, can inspect the code tree, spot errors, and suggest possible fixes; this produces a stable, robust product wherein bugs, once discovered, are rapidly resolved—often within a few hours of discovery! Community Support Looking for a way to integrate Flickr photostreams or Google Maps data into your application? Try the Zend_Service_Flickr or Zend_Gdata components. Need to communicate with a Flash application using Adobe Action Message Format (AMF)? Reach for the Zend_Amf component. Need to quickly integrate an RSS feed into your application? Zend_Feed has everything you need. As these examples illustrate, one of the nice things about a community-supported project like the Zend Framework is the access it offers to the creativity and imagination of hundreds of developers across the world. The Zend Framework is composed of a large number of independent components that developers can use to painlessly add new functionality to their PHP project. Using these components is usually a more time- and cost-efficient alternative to rolling your own code. Unique Advantages Now, one might well argue that the features listed above apply to all PHP frameworks, not just the Zend Framework. However, the Zend Framework does possess some unique features that give it an edge over the competition. Loose Coupling Unlike many other frameworks, where the individual pieces of the framework are closely linked with each other, the components of the Zend Framework can be easily separated and used on an “as needed” basis. So, while the Zend Framework certainly includes everything you need to build a modern, MVC-compliant Web application, it doesn’t force you to do so—you’re just as welcome to pull out any of the individual library components and integrate 5 6 Zend Framework: A Beginner’s Guide them into your non-MVC application. This loose coupling helps reduce the footprint of your application and preserves flexibility for future changes. Rapid Release Cycle The Zend Framework team follows an aggressive release schedule, with an average of between one and three releases each month. In addition to these stable releases, there are also previews and release candidates, which serve to give the community a heads-up on what to expect while the final release is being prepared. These frequent releases serve not only to keep the project moving forward, but to ensure that bugs are picked up and resolved as quickly as possible. Developers can also access “bleeding edge” code from the project’s public Subversion repository. Unit Testing Policy Given that the Zend Framework is a loosely coupled set of components that are subject to ongoing development, unit testing assumes particular importance to ensure that components continue working correctly and the code base remains stable throughout multiple release cycles. The Zend Framework has a strict unit testing policy, which dictates that components can only be added to the framework if they are accompanied by a reasonably complete and working collection of unit tests (written under the PHPUnit testing framework). This policy ensures that backward compatibility is maintained between releases and regressions are readily visible. Code-Generation Tools Zend Framework includes a “tooling” feature that allows developers to get a new Zend Framework project up and running with minimal effort. This feature, implemented as a command-line script built on top of the Zend_Tool component, takes care of creating the base filesystem layout for a new project and populating it with an initial set of controllers, views, and actions. Developers can use this tooling script as a convenient shortcut to quickly create new project objects as development progresses. Market Credibility The Zend Framework is sponsored by Zend Technologies, one of the best-known software companies in the PHP space. The company produces a number of commercial products for enterprise use and has a long track record of creating successful and innovative products for PHP developers, such as Zend Server, a PHP Web application server for business-critical applications, and Zend Studio, an integrated IDE for PHP application development. Zend Technologies’ customers include IBM, McAfee, FOX Interactive Media, Lockheed Martin, SalesForce.com, NASA, and Bell Canada; as such, its support of the Zend Framework ensures immediate credibility in the marketplace and serves as a useful tool when convincing clients and/or senior managers to take the leap into framework-based development. Third-Party Application Interoperability Zend Technologies’ market position as one of the leading vendors of enterprise PHP solutions has allowed it to garner broad industry support for the Zend Framework. Zend Framework Chapter 1: Introducing the Zend Framework includes native support for many third-party tools and technologies, including Adobe Action Message Format (AMF), the Google Data APIs, the Dojo Toolkit, Microsoft CardSpace, and Web services from Amazon, Yahoo!, Twitter, Flickr, Technorati, and Del.icio.us. That’s not all. One of PHP’s strengths has historically been its support for a wide range of different databases, file formats, and protocols. The Zend Framework provides a common API for accessing MySQL, PostgreSQL, Oracle, and Microsoft SQL Server databases (among others) via its Zend_Db components, and also includes components for sending and receiving email using the SMTP, IMAP, and POP3 protocols; building Web services using the SOAP and REST protocols; encoding and decoding JSON data; parsing feeds in Atom and RSS formats; and creating and manipulating PDF documents. Commercial Support Options The Zend Framework is “free”—users can download and use it at no cost under the terms of the BSD License, but by the same token, users are expected to support themselves via community tools such as mailing lists and wikis. For companies and individuals looking for a greater level of support, Zend Technologies offers commercial support and training packages, consultancy services from Zend engineers, and proprietary PHP development and deployment tools that can help speed and optimize Zend Framework development. For many business organizations, this ability to access technical support, albeit at a fee, is a key reason for selecting the Zend Framework as their application toolkit of choice. There’s also the Zend Framework Certification program, which provides a measure of an individual developer’s Zend Framework skills, and is recognized throughout the industry as an indicator of his or her Zend Framework competence. Extensive Documentation The Zend Framework comes with extensive documentation for the 60+ components included with the core distribution. This documentation includes a programmer’s reference guide containing more than 1000 pages; a “quick start” guide for experienced developers; detailed API documents; video tutorials; webinars; and podcasts by well-known Zend engineers. This wide range of learning materials can significantly reduce the learning curve for both novice and experienced programmers and it is, in fact, one of the key areas where Zend Framework surpasses competing PHP frameworks. Application Environment All Zend Framework applications are also PHP applications and can run in any PHP-capable environment. This environment typically consists of at least the following three components: L A base operating system, usually either Linux or Microsoft Windows L A Web server, usually Apache on Linux or Internet Information Services on Microsoft Windows, to intercept HTTP requests and either serve them directly or pass them on to the PHP interpreter for execution 7 8 Zend Framework: A Beginner’s Guide HTTP request HTTP response Apache MySQL PHP Web browser SQL query SQL result set Client Figure 1-1 L Linux OS Server The components of a typical PHP application environment A PHP interpreter to parse and execute PHP code, and return the results to the Web server There’s also often a fourth optional but very useful component: L A database engine, such as MySQL or PostgreSQL, that holds application data, accepts connections from the PHP layer, and modifies or retrieves data from the database Figure 1-1 illustrates the interaction between these components. It’s worth noting that the Linux/Apache/PHP/MySQL combination is extremely popular with developers, and is colloquially referred to as the “LAMP stack.” The LAMP stack is popular because all its components are open-source projects and, as such, can be downloaded from the Internet at no charge. As a general principle, there are also no fees or charges associated with using these components for either personal or commercial purposes, or for developing and distributing applications that use them. If you do intend to write commercial applications, however, it’s a good idea to review the licensing terms that are associated with each of these components; typically, you will find these on the component’s Web site as well as in the product archive. Installing the Zend Framework Now that you know a little bit about the Zend Framework, let’s dive right into actually building applications in it. As a necessary first step, you must first ensure that you have a working Apache/PHP/MySQL development environment. The appendix of this book has detailed instructions for obtaining these components, for installing them, and for testing your development environment to ensure that it’s working correctly, so flip ahead and come back here once you’re ready. All done? The next step is to download and install the Zend Framework to your development environment. Visit the official Zend Framework Web site at http://framework Chapter 1: Introducing the Zend Framework .zend.com/ and get a copy of the most recent release of the software. Zend Technologies makes two versions of the package available: a “minimal” version, which contains just the standard libraries and command-line tools, and a “full” version, which contains additional documentation, examples, unit tests, and third-party toolkits. The full version is recommended. Once you’ve downloaded the code archive, extract its contents to a temporary area on the file system. shell> cd /tmp shell> tar -xzvf ZendFramework-XX.tar.gz You should end up with a directory structure that looks something like Figure 1-2. Of all these directories, the two you’ll need immediately are the library/ and bin/ directories. The library/ directory contains all the Zend Framework components, while the bin/ directory contains command-line tools that are helpful in initializing a new project and adding objects to it. These two directories need to be manipulated as follows: L The contents of the library/ directory should be moved to a location in your PHP “include path” list. On UNIX/Linux systems, good possible locations for this are /usr/local/lib/ php or /usr/local/share/php. On Windows, consider using your PHP or PEAR installation directory, such as C:\Program Files\PHP or C:\Program Files\PHP\PEAR. Note that in case the target directory is not already part of your PHP “include path” list, you must add it before proceeding. L The contents of the bin/ directory should be moved to a location in your system’s executable path. If the directory containing your PHP binary—typically /usr/local/bin on UNIX/Linux or C:\PHP on Windows—is already part of your system’s executable path, then that is usually the ideal location to use. Alternatively, move the contents of the bin/ directory to any other location you find convenient, always remembering to add that location to your system’s executable path list. NOTE The bin/ directory contains three scripts: zf.sh, the command-line interface for UNIX/Linux; zf.bat, the command-line interface for Windows; and zf.php, the main “worker” script. On UNIX/Linux, you will need to make the zf.sh script executable with the chmod command; you may also wish to rename or alias it to make it easier to access. Figure 1-2 The contents of a Zend Framework release archive 9 10 Zend Framework: A Beginner’s Guide Figure 1-3 The output of the zf --help command Here are examples of commands you can use to perform these tasks: shell> shell> shell> shell> shell> cd ZendFramework-XX mv library/* /usr/local/lib/php/ mv bin/* /usr/local/bin/ chmod +x /usr/local/bin/zf.sh ln -s /usr/local/bin/zf.sh /usr/local/bin/zf You should now be able to access the zf command-line script from your shell prompt, on both Linux and Windows. Try this by issuing the following command at your shell prompt: shell> zf —help If all is working as it should, you should be presented with a list of options. Figure 1-3 illustrates the output on Linux. Try This 1-1 Starting a New Project Once you’ve got the Zend Framework installed and the zf command-line script working, you’re ready to start creating applications with it. The following steps discuss how to accomplish this task. Chapter 1: Introducing the Zend Framework Understand Application Requirements Before diving into the code, it’s worthwhile spending a few minutes understanding the example application you’ll be building in the first half of this book. The application is the Web site of a fictional store that specializes in the sale of rare postal stamps to hobbyists and professional philatelists. Unlike other hobbyist stores, though, this one has an interesting twist: It functions as an online stamp sourcing agency, allowing individual collectors to upload pictures and descriptions of stamps they may have for sale into a central database, and letting buyers search this stamp database by country, year, and keyword. In the event of a match, the store will purchase the stamp from the seller and resell it to the buyer…at a hefty commission, naturally! Designated site moderators would have direct access to the uploaded listings, and would manually approve suitable ones for display in search results. Moderators would also have access to a simple content management system for news and press releases; this information would be accessible both via the Web site and as an RSS feed. And just to make things interesting, the stamp database would also be available via a SOAP interface, to facilitate integration with third-party applications. Sounds funky? It is. And it even has a cool name: the Stamp Query and Research Engine or, as its friends like to call it, SQUARE. The SQUARE example application is conceived such that it covers common requirements encountered in day-to-day application development: static pages, input forms, image upload, login-protected administration panel, data paging and sorting, multiple output types, and keyword search. Implementing these features requires one to understand the nitty-gritties of form processing, input validation, session management, authentication and security, CRUD database operations, Web service APIs, and integration with third-party libraries. As such, it should be a good starting point to begin understanding application development with the Zend Framework. Create the Application Directory Let’s get started. Change to the Web server’s document root directory (typically /usr/local/ apache/htdocs on UNIX/Linux or C:\Program Files\Apache\htdocs on Windows) and create a new subdirectory for the application. For reasons that have been explained in the preceding section, name this directory square/. shell> cd /usr/local/apache/htdocs shell> mkdir square This directory will be referenced throughout this book as $APP_DIR. Create the Application Skeleton The next step is to initialize the application and create the basic files and directories needed for a skeletal Zend Framework application. The zf command-line script can do this for you automatically—simply change to $APP_DIR and run the following command (see Figure 1-4): shell> cd /usr/local/apache/htdocs/square shell> zf create project. (continued) 11 12 Zend Framework: A Beginner’s Guide Figure 1-4 The output of the zf create project command The script will now create an empty application container and populate it with an initial set of files. Once the process is complete, you’ll see a number of new subdirectories in the application directory, as shown in Figure 1-5. This is the default directory structure for Zend Framework applications. Each directory serves a different purpose, as discussed in the following list: L $APP_DIR/application/ is the main application directory, which contains all the application code, including controllers, views, and models. L $APP_DIR/library/ holds third-party libraries and classes used by the application. If you decide to bundle the Zend Framework with your application (see the next section), this is where you’ll put it. L $APP_DIR/public/ holds publicly accessible content, such as image and media files, CSS style sheets, JavaScript code, and other static resources. L $APP_DIR/tests/ holds unit tests for the application. Figure 1-5 The default directory structure for a new Zend Framework application Chapter 1: Introducing the Zend Framework Add Zend Framework Libraries At this point, you have an important decision to make. You must decide whether to include the Zend Framework libraries with your application, or leave it up to users to download and install these libraries themselves. There are pros and cons to each option, as follows: L Requiring users to download the Zend Framework libraries themselves ensures that they always have access to the latest code (and bug fixes). However, the process can be intimidating for novice users, and if the newer libraries are not backward-compatible with the original versions used, unusual and hard-to-track bugs could appear. L Bundling the Zend Framework libraries with the application ensures that users can begin using the application out of the box, with no version incompatibilities. However, it also “locks in” users to a particular version of the Zend Framework, possibly making it harder to upgrade to newer versions with additional features or necessary bug fixes. For purposes of this book, I’ll assume that the Zend Framework libraries will be bundled with the application. Therefore, copy the contents of the Zend Framework library/ directory to $APP_DIR/library/, as you did earlier in the chapter, using the following command. The default application settings are to automatically look in this location for libraries to be included. shell> cp -R /usr/local/lib/php/Zend library/ Define Virtual Host Settings To make it easier to access the application, it’s a good idea to define a new virtual Web host and point it to the application’s public directory. This is an optional but recommended step, as it helps simulate a “live” environment and presents application resources (URLs) as they would appear to users in a public environment. Assuming you’re using the Apache Web server, you can set up a named virtual host for the application by editing the Apache configuration file (httpd.conf or httpd-vhosts.conf) and adding the following lines to it: NameVirtualHost *:80quote($_POST['name']); $qty = $pdo->quote($_POST['qty']); 49 50 Zend Framework: A Beginner’s Guide $sql = "INSERT INTO shoppinglist (name, qty) VALUES ($name, $qty)"; $pdo->exec($sql) or die("ERROR: " . implode(":", $pdo>errorInfo())); // close connection unset($pdo); // display success message echo 'Thank you for your submission'; } catch (Exception $e) { die("ERROR: " . $e->getMessage()); } } ?> There’s nothing very clever or complicated here. This script is divided into two parts, split by a conditional test that inspects the $_POST variable to determine if the form has been submitted. The first half displays an input form containing two fields and a submit button; the second half validates the input to ensure that it is in the correct format and then proceeds to escape it and insert it into a database. Figure 3-1 illustrates what the form looks like. Now, while the script and general approach that you’ve just seen work in practice, there’s no denying that it has a couple of problems: ● The same script file contains both HTML interface elements and PHP business logic. As discussed in the previous chapter, this is both messy to look at and hard to maintain. It’s also hard to enforce consistency between forms, since the code required to produce each form is customized to a very high degree. ● Every time you add a new field to the form in the first half of the script, you need to add a corresponding set of validation tests and error messages to the second half of the script. This is annoying, and often repetitive; witness that the first two tests in the previous example do essentially the same thing. ● There’s no way to reuse validation tests from one form in other forms (unless you had the foresight to package them into classes or functions from the get-go). As a result, you often end up writing the same code time and time again, especially when working with forms that perform related or similar operations. Figure 3-1 A form created using standard HTML markup Chapter 3: Working with Forms The Zend Framework comes with a set of components, collectively referred to as Zend_ Form, which addresses these problems. To illustrate, consider the following example, which uses Zend_Form to produce a result equivalent to the previous script: setAction('/item/create') ->setMethod('post'); // create text input for name $name = new Zend_Form_Element_Text('name'); $name->setLabel('Item name:') ->setOptions(array('size' => '35')) ->setRequired(true) ->addValidator('NotEmpty', true) ->addValidator('Alpha', true) ->addFilter('HTMLEntities') ->addFilter('StringTrim'); // create text input for quantity $qty = new Zend_Form_Element_Text('qty'); $qty->setLabel('Item quantity:'); $qty->setOptions(array('size' => '4')) ->setRequired(true) ->addValidator('NotEmpty', true) ->addValidator('Int', true) ->addFilter('HTMLEntities') ->addFilter('StringTrim'); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Submit') ->setOptions(array('class' => 'submit')); // attach elements to form $this->addElement($name) ->addElement($qty) ->addElement($submit); } } class ExampleController extends Zend_Controller_Action { 51 52 Zend Framework: A Beginner’s Guide public function formAction() { $form = new Form_Item_Create; $this->view->form = $form; if ($this->getRequest()->isPost()) { if ($form->isValid($this->getRequest()->getPost())) { $values = $form->getValues(); $pdo = new PDO('mysql:dbname=test;host=localhost', 'user', 'pass'); $sql = sprintf("INSERT INTO shoppinglist (name, qty) VALUES ('%s', '%d')", $values['name'], $values['qty']); $pdo->exec($sql); $this->_helper->getHelper('FlashMessenger') ->addMessage('Thank you for your submission'); $this->_redirect('/index/success'); } } } } Figure 3-2 illustrates what the form looks like. You’ll immediately notice three things about the code that creates the form in Figure 3-2: ● There isn’t a single line of HTML code in the script. Form and form elements are represented as PHP objects, and they are configured using object methods. This ensures consistency and produces a standards-compliant Web form. ● Predefined validators and filters are available for common input validation and sanitization tasks. This reduces the amount of work involved, produces more maintainable code, and avoids repetition. Validators can also be combined or extended to support custom requirements. ● Validators are specified at the same time as form fields. This allows the form to “know” what each field can support and to easily identify the source of input errors. A single field can also be associated with multiple validators for more stringent input validation. It should be clear from these points that Zend_Form provides a convenient, maintainable, and extensible solution for input form creation and data validation. The remainder of this chapter will explore Zend_Form in detail, illustrating it in a practical context. Figure 3-2 A form created using the Zend_Form component Chapter 3: Working with Forms Creating Forms and Form Elements From the previous section, you know that Zend_Form offers an object-oriented API for generating forms and validating user input. Under the Zend_Form approach, forms are represented as instances of, or objects inheriting from, the Zend_Form base class. This base class exposes a number of methods to control the operation of the form, including the setAction() method to set the form’s action URL and the setMethod() method to set the submission method. There’s also a catch-all setAttribs() method, which allows you to set other form attributes. Here’s an example of using these methods: setAction('/my/action') ->setAttribs(array( 'class' => 'form', 'id' => 'example' )) ->setMethod('post'); } } Form elements are added by instantiating objects of the corresponding Zend_Form_ Element_* class, setting element properties via class methods, and then attaching them to the form with the addElement() method. Here’s an example of adding a text input and a submit button to a form: setAction('/my/action') ->setAttribs(array( 'class' => 'form', 'id' => 'example' )) ->setMethod('post'); // create text input for title $title = new Zend_Form_Element_Text('title'); $title->setLabel('Title:') ->setOptions(array( 53 54 Zend Framework: A Beginner’s Guide 'size' => '35' )); // create $submit = 'label' 'class' )); submit button new Zend_Form_Element_Submit('submit', array( => 'Submit', => 'submit' // attach elements to form $this->addElement($title) ->addElement($submit); } } Element objects can be configured either by passing values to the object constructor or by using named object methods. In the previous example, the object constructor for the text input element was passed the element name in the constructor, and the setLabel() and setOptions() methods were then used to set the element label and display properties, respectively. On the other hand, the submit button was configured directly in the object constructor, which was passed an array of options as the second argument. TIP You can also attach descriptions to form fields with the setDescription() method. If you prefer, you can also create form elements using the createElement() method, by passing the element type to the method as its first argument. Here’s an example, which is equivalent to the previous one: setAction('/my/action') ->setAttribs(array( 'class' => 'form', 'id' => 'example' )) ->setMethod('post'); // create text input for title $title = $this->createElement('text', 'title', array( 'label' => 'Title:', 'size' => 35, )); Chapter 3: // create $submit = 'label' 'class' )); Working with Forms submit button $this->createElement('submit', 'submit', array( => 'Submit', => 'submit' // attach elements to form $this->addElement($title) ->addElement($submit); } } TIP In many of the code listings in this chapter, you’ll see examples of method chaining, wherein one method appears to invoke another. This is an example of the Zend Framework’s “fluent interface,” which provides a convenient shortcut to configure form objects with minimal additional coding. The end result is also significantly more readable. You can read more about fluent interfaces in the links at the end of this chapter. Working with Form Elements By default, the Zend Framework ships with definitions for 16 form elements, ranging from simple text input elements to more complex multiple selection lists, and it’s useful to learn more about them. Table 3-1 gives a list of these 16 elements, together with their corresponding class names. The following sections examine these in more detail. Text and Hidden Fields Text input fields, password input fields, and larger text input areas are represented by the Zend_Form_Element_Text, Zend_Form_Element_Password, and Zend_Form_Element_ Textarea classes, respectively, while hidden form fields are represented by the Zend_Form_ Element_Hidden class. The following example demonstrates these elements in action: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for name 55 56 Zend Framework: A Beginner’s Guide Element Class Description Zend_Form_Element_Text Text input field Zend_Form_Element_Hidden Hidden field Zend_Form_Element_Password Password field Zend_Form_Element_Radio Radio button Zend_Form_Element_Checkbox Check box Zend_Form_Element_MultiCheckbox Group of related check boxes Zend_Form_Element_Select Selection list (single) Zend_Form_Element_MultiSelect Selection list (multiple) Zend_Form_Element_Textarea Text input field Zend_Form_Element_File File input field Zend_Form_Element_Image Image Zend_Form_Element_Button Button Zend_Form_Element_Hash Unique string (for session identification) Zend_Form_Element_Captcha CAPTCHA (for spam filtering) Zend_Form_Element_Reset Reset button Zend_Form_Element_Submit Submit button Table 3-1 Form Element Classes Included with the Zend Framework $name = new Zend_Form_Element_Text('name'); $name->setLabel('First name:') ->setOptions(array('id' => 'fname')); // create password input $pass = new Zend_Form_Element_Password('pass'); $pass->setLabel('Password:') ->setOptions(array('id' => 'upass')); // create hidden input $uid = new Zend_Form_Element_Hidden('uid'); $uid->setValue('49'); // create text area for comments $comment = new Zend_Form_Element_Textarea('comment'); $comment->setLabel('Comment:') ->setOptions(array( 'id' => 'comment', Chapter 3: Working with Forms 'rows' => '10', 'cols' => '30', )); // attach elements to form $this->addElement($name) ->addElement($pass) ->addElement($uid) ->addElement($comment); } } Figure 3-3 illustrates the result. Radio Buttons and Checkboxes Radio buttons are represented by the Zend_ Form_Element_Radio class, while check boxes are represented by the Zend_Form_ Element_Checkbox class. Here’s an example of these two classes in action: Figure 3-3 setAction('/sandbox/example/form') ->setMethod('post'); A form with text and hidden input elements // create text input for name $name = new Zend_Form_Element_Text('name'); $name->setLabel('Name:') ->setOptions(array('id' => 'fname')); // create radio buttons for type $type = new Zend_Form_Element_Radio('type'); $type->setLabel('Membership type:') ->setMultiOptions(array( 'silver' => 'Silver', 'gold' => 'Gold', 'platinum' => 'Platinum' )) ->setOptions(array('id' => 'mtype')); // create checkbox for newsletter subscription $subscribe = new Zend_Form_Element_Checkbox('subscribe'); $subscribe->setLabel('Subscribe to newsletter') 57 58 Zend Framework: A Beginner’s Guide ->setCheckedValue('yes') ->setUncheckedValue('no'); // attach elements to form $this->addElement($name) ->addElement($type) ->addElement($subscribe); } } The setMultiOptions() method of the Zend_Form_Element_Radio object accepts an array, and uses it to set the list of available radio button options. The keys of the array represent the form values that will be submitted, while the corresponding values represent the human-readable labels for each option. Similarly, the setCheckedValue() and setUncheckedValue() methods of the Zend_ Form_Element_Checkbox object allow you to customize the value for the element’s checked and unchecked states. By default, these values are set to 1 and 0, respectively. Figure 3-4 illustrates the result. If you’d like the user to select from a set of options, the Zend_Form_Element_MultiCheckbox class is often a better bet than the Zend_Form_ Element_Checkbox class, because it exposes a setMultiOptions() method that allows for multiple items to be selected. The resulting collection is then formatted and submitted as an Figure 3-4 A form with radio buttons array. Here’s an example of it in action: and check boxes setAction('/sandbox/example/form') ->setMethod('post'); // create text input for name $name = new Zend_Form_Element_Text('name'); $name->setLabel('Name:') ->setOptions(array('id' => 'fname')); // create radio buttons for type $type = new Zend_Form_Element_Radio('type'); $type->setLabel('Pizza crust:') Chapter 3: Working with Forms ->setMultiOptions(array( 'thin' => 'Thin', 'thick' => 'Thick' )) ->setOptions(array('id' => 'type')); // create checkbox for toppings $toppings = new Zend_Form_Element_MultiCheckbox('toppings'); $toppings->setLabel('Pizza toppings:') ->setMultiOptions(array( 'bacon' => 'Bacon', 'olives' => 'Olives', 'tomatoes' => 'Tomatoes', 'pepperoni' => 'Pepperoni', 'ham' => 'Ham', 'peppers' => 'Red peppers', 'xcheese' => 'Extra cheese', )); // attach elements to form $this->addElement($name) ->addElement($type) ->addElement($toppings); } } Figure 3-5 illustrates what the result looks like. Selection Lists Single- and multiple-selection lists are supported through the Zend_Form_Element_Select and Zend_Form_Element_MultiSelect classes. Like the Zend_Form_Element_MultiCheckbox class, they too expose a setMultiOptions() method that can be used to set up the list of available options. The following example demonstrates both these element types in action: Figure 3-5 setAction('/sandbox/example/form') ->setMethod('post'); A form with radio buttons and multiple check boxes 59 60 Zend Framework: A Beginner’s Guide // create text input for name $name = new Zend_Form_Element_Text('name'); $name->setLabel('Name:') ->setOptions(array('id' => 'fname')); // create selection list for source country $from = new Zend_Form_Element_Select('from'); $from->setLabel('Travelling from:') ->setMultiOptions(array( 'IN' => 'India', 'US' => 'United States', 'DE' => 'Germany', 'FR' => 'France', 'UK' => 'United Kingdom' )); // create multi-select list for destination countries $to = new Zend_Form_Element_MultiSelect('to'); $to->setLabel('Travelling to:') ->setMultiOptions(array( 'IT' => 'Italy', 'SG' => 'Singapore', 'TR' => 'Turkey', 'DK' => 'Denmark', 'ES' => 'Spain', 'PT' => 'Portugal', 'RU' => 'Russia', 'PL' => 'Poland' )); // attach elements to form $this->addElement($name) ->addElement($from) ->addElement($to); } } Figure 3-6 illustrates the result. File Upload Fields If you’re looking to upload one or more files through a form, you’ll need the Zend_Form_ Element_File class, which provides a browseable file input box. Here’s an example of it in use: setAction('/sandbox/example/form') ->setEnctype('multipart/form-data') ->setMethod('post'); // create file input for photo upload $photo = new Zend_Form_Element_File('photo'); $photo->setLabel('Photo:') ->setDestination('/tmp/upload'); // attach elements to form $this->addElement($photo); } } CAUTION Remember that you must set the form encoding type to 'multipart/form-data' for form uploads to be correctly handled. This can be done using the setEnctype() method of the form object. Figure 3-7 illustrates what it looks like. Figure 3-7 A form with file input fields TIP If you’re trying to upload multiple related files, there’s a convenient setMultiFile() method that generates a sequence of file input fields and saves you the hassle of instantiating multiple Zend_Form_Element_File objects. You’ll see an example of this in the next chapter. Buttons Every form needs a submit button, and some also need a reset button. These two critical form elements are represented by the Zend_Form_Element_Submit and Zend_Form_Element_Reset classes, respectively, and they’re illustrated in the next listing: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for title 61 62 Zend Framework: A Beginner’s Guide $title = new Zend_Form_Element_Text('title'); $title->setLabel('Title:') ->setOptions(array('size' => '35')); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Submit'); // create reset button $reset = new Zend_Form_Element_Reset('reset'); $reset->setLabel('Cancel'); // attach elements to form $this->addElement($title) ->addElement($submit) ->addElement($reset); } } Figure 3-8 illustrates the resulting output. If you’re after a more generic form button, you’ll find it in the Zend_Form_Element_Button class, which provides a simple, clickable form button that is useful for many different purposes. Image buttons can be generated with the Zend_Form_Element_Image class; use the setImage() method to specify the source image for the button. Here’s an example of one such image button: Figure 3-8 A form with submit and reset buttons setAction('/sandbox/example/form') ->setMethod('post'); // create text input for title $title = new Zend_Form_Element_Text('title'); $title->setLabel('Title:') ->setOptions(array('size' => '35')); // create image submit button $submit = new Zend_Form_Element_Image('submit'); $submit->setImage('/images/submit.jpg'); Chapter 3: Working with Forms // attach elements to form $this->addElement($title) ->addElement($submit); } } Figure 3-9 illustrates the resulting output. Hash and CAPTCHA Fields Figure 3-9 A form with an image button The Zend Framework includes two “special” form elements to assist in maintaining input security: the Hash and CAPTCHA elements. These are represented by the Zend_Form_Element_Hash and Zend_Form_Element_Captcha classes, respectively. The Hash element uses a salt value to generate a unique key for the form and store it in the session. When the form is submitted, the hash value submitted with the form is automatically compared to the value stored in the session. If a match is found, the form submission is assumed to be genuine. If there is a mismatch, it’s a reasonable supposition that the form has been hijacked and is being used in a Cross-Site Request Forgery (CSRF) attack. Here’s an example of using this element: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for number $cc = new Zend_Form_Element_Text('ccnum'); $cc->setLabel('Credit card number:') ->setOptions(array('size' => '16')); // create text input for amount $amount = new Zend_Form_Element_Text('amount'); $amount->setLabel('Payment amount:') ->setOptions(array('size' => '4')); // create hash $hash = new Zend_Form_Element_Hash('hash'); $hash->setSalt('hf823hflw03j'); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Submit'); // attach elements to form 63 64 Zend Framework: A Beginner’s Guide $this->addElement($cc) ->addElement($amount) ->addElement($hash) ->addElement($submit); } } The CAPTCHA element automatically generates a CAPTCHA verification input, which is a useful tool to filter out automated form submissions. More and more Web sites are using CAPTCHAs to reduce the number of false registrations and/or spam messages received through online forms. Although manually generating and verifying a CAPTCHA is a tedious process, the Zend_Form_Element_Captcha makes it as simple as adding a few lines of code to your form. Here’s an example: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for user name $name = new Zend_Form_Element_Text('username'); $name->setLabel('Username:') ->setOptions(array('size' => '16')); Ask the Expert Q: A: What is a CSRF attack, and how do I protect against it? Typically, when a user visits a protected Web site and validates his/her access credentials, a user session is generated and the access credentials are revalidated from the session data store on each request. A CSRF attack involves hijacking a validated user session and using the implicit trust relationship that already exists between the user and the host application to invisibly transmit unauthorized requests through input sources such as Web forms. By generating a unique hash value for each Web form and validating this value when the form is submitted, a developer is able to make it harder to perform this type of attack. Using a hash value also provides (limited) protection from automated spam mailers (“spambots”), and is more user-friendly than a CAPTCHA. Chapter 3: Working with Forms // create password input $pass = new Zend_Form_Element_Password('password'); $pass->setLabel('Password:') ->setOptions(array('size' => '16')); // create captcha $captcha = new Zend_Form_Element_Captcha('captcha', array( 'captcha' => array( 'captcha' => 'Figlet', 'wordLen' => 5, 'timeout' => 300, ) )); $captcha->setLabel('Verification:'); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Sign Up'); // attach elements to form $this->addElement($name) ->addElement($pass) ->addElement($captcha) ->addElement($submit); } } Figure 3-10 illustrates what the result might look like. NOTE A number of predefined CAPTCHA adapters are included with the Zend Framework, including adapters for simple string-transposition operations (“Dumb”) and for visual CAPTCHAS (“Image” and “Figlet”). You’ll see another example of an image CAPTCHA a little further along in this chapter. Setting Required and Default Values You can mark a specific input element as required by calling its setRequired() method with a true argument. Here’s an example: setAction('/sandbox/example/form') ->setMethod('post'); 65 66 Zend Framework: A Beginner’s Guide Figure 3-10 A form containing a CAPTCHA // create text input for name $name = new Zend_Form_Element_Text('name'); $name->setLabel('Name:') ->setOptions(array('size' => '35')) ->setRequired(true); Ask the Expert Q: A: What is a CAPTCHA? A CAPTCHA, or Completely Automated Public Turing test to tell Computers and Humans Apart, is a common challenge-response test used to identify whether the entity at the other end of a connection is a human being or a computer. On the Web, the typical form of a CAPTCHA is a distorted sequence of random alphanumeric characters, operating on the principle that a computer would be unable to see past the distortion, but a human, with greater powers of perception, would be able to correctly identify the sequence. Such CAPTCHAs are typically attached to input forms on the Web (for example, user registration forms), and they must be solved correctly before the input will be processed by the host application. CAPTCHAs need not always be visual; audio CAPTCHAs are also possible, and are most appropriate for visually handicapped users. Chapter 3: Working with Forms // create text input for email address $email = new Zend_Form_Element_Text('email'); $email->setLabel('Email address:'); $email->setOptions(array('size' => '50')) ->setRequired(true); // create submit button $submit = new Zend_Form_Element_Submit('submit', array('class' => 'submit') ); $submit->setLabel('Sign Up'); // attach elements to form $this->addElement($name) ->addElement($email) ->addElement($submit); } } When you use the setRequired() method on an input field, Zend_Form automatically attaches a NotEmpty validator to that field. As a result, if the field is empty when the form is submitted, an error message will appear. Figure 3-11 illustrates the result. TIP You can tell Zend_Form not to attach a NotEmpty validator to required elements by explicitly calling the element’s setAutoInsertNotEmptyValidator() method with a false argument. Validators are discussed in detail in the next section. You can attach default values to input elements by calling the element object’s setValue() method with the default value or by calling the form object’s setDefaults() method with an array of default values. For text input fields, this can be any string value; for Figure 3-11 The result of submitting a form without required input values 67 68 Zend Framework: A Beginner’s Guide radio buttons and selection lists, it should be the index of the selected item. Here’s an example, which demonstrates both of these methods: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for name $name = new Zend_Form_Element_Text('name'); $name->setLabel('Name:') ->setOptions(array('size' => '35')) ->setRequired(true) ->setValue('Enter your name'); // create text input for email address $email = new Zend_Form_Element_Text('email'); $email->setLabel('Email address:'); $email->setOptions(array('size' => '50')) ->setRequired(true) ->setValue('Enter your email address'); // create radio buttons for type $type = new Zend_Form_Element_Radio('type'); $type->setLabel('Membership type:') ->setMultiOptions(array( 'silver' => 'Silver', 'gold' => 'Gold', 'platinum' => 'Platinum' )); // create checkbox for newsletter subscription $subscribe = new Zend_Form_Element_Checkbox('subscribe'); $subscribe->setLabel('Subscribe to newsletter') ->setCheckedValue('yes') ->setUncheckedValue('no'); // create selection list for source country $from = new Zend_Form_Element_Select('from'); $from->setLabel('Country:') ->setMultiOptions(array( 'IN' => 'India', 'US' => 'United States', 'DE' => 'Germany', 'FR' => 'France', Chapter 3: 'UK' )); => 'United Kingdom' // create submit button $submit = new Zend_Form_Element_Submit( 'submit', array('class' => 'submit')); $submit->setLabel('Sign Up'); // attach elements to form $this->addElement($name) ->addElement($email) ->addElement($type) ->addElement($from) ->addElement($subscribe) ->addElement($submit); // set default values $this->setDefaults(array( 'type' => 'platinum', 'subscribe' => 'yes', 'from' => 'FR', )); } } Figure 3-12 illustrates what the result looks like. Figure 3-12 A form rendered with default values Working with Forms 69 70 Zend Framework: A Beginner’s Guide Filtering and Validating Form Input As a Web application developer, there’s one unhappy fact that you’ll have to learn to live with: There are always going to be people out there who get their chuckles from finding loopholes in your code and exploiting these loopholes for malicious purposes. Therefore, one of the most important things a developer can do to secure an application is to properly filter and validate all the input passing through it. The following sections discuss the filtering and validation tools available in the Zend Framework, together with examples of how they can be used with Web forms to make your application more secure. Using Input Filters Most of the time, input exploits consist of sending your application cleverly disguised values that “trick” it into doing something it really, really shouldn’t. A common example of this type of exploit is the SQL injection attack, wherein an attacker remotely manipulates your database with an SQL query embedded inside form input. Therefore, one of the most important things a developer must do before using any input supplied by the user is to “sanitize” it by removing any special characters or symbols from it. PHP comes with various functions to assist developers in the task of sanitizing input. For example, the addslashes() function escapes special characters (like quotes and backslashes) in input so that it can be safely entered into a database, while the strip_tags() function strips all the HTML and PHP tags out of a string, returning only the ASCII content. There’s also the htmlentities() function, which is commonly used to replace special characters like ", &, <, and > with their corresponding HTML entity values, rendering them harmless. Here’s an example of sanitizing form input with the htmlentities() function: When it comes to filtering user input, the Zend Framework does a lot of the heavy lifting for you. The Zend_Filter component provides a comprehensive set of input filters, which can either be attached to form elements with the addFilter() method or used on a stand-alone basis for ad-hoc input sanitization. Here’s an example of using the HTMLEntities filter on a text input field: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for user name // filter special characters $name = new Zend_Form_Element_Text('name'); $name->setLabel('Username:') ->setOptions(array('size' => '16')) ->addFilter('HtmlEntities'); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Sign Up'); // attach elements to form $this->addElement($name) ->addElement($submit); } } You can also pass the addFilter() method an instance of the Zend_Filter_* class, as shown in the following equivalent script: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for user name // filter special characters $name = new Zend_Form_Element_Text('name'); $name->setLabel('Username:') ->setOptions(array('size' => '16')) ->addFilter(new Zend_Filter_HtmlEntities()); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Sign Up'); // attach elements to form 71 72 Zend Framework: A Beginner’s Guide $this->addElement($name) ->addElement($submit); } } Some filters support additional options, which can be passed to the addFilter() method as an array or, if you’re using a class instance, as arguments to the object constructor. Consider the next example, which uses the Alpha filter to strip out all non-alphabetic characters from user input. An additional option, passed to the addFilter() method as a second argument, retains whitespace (which is stripped by default). setAction('/sandbox/example/form') ->setMethod('post'); // create text input for name // allow alphabetic characters and whitespace $name = new Zend_Form_Element_Text('name'); $name->setLabel('Name:') ->setOptions(array('size' => '4')) ->setRequired(true) ->addFilter('Alpha', array('allowWhiteSpace' => true)) ->addFilter('HtmlEntities'); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Sign Up'); // attach elements to form $this->addElement($name) ->addElement($submit); } } Table 3-2 gives a list of some important filters that ship with the Zend Framework, together with a brief description of each. You’ll see many of these filters in use in this and subsequent chapters. TIP You can attach multiple filters to a form element in one of two ways: by calling the addFilter() method multiple times, with a different filter name on each invocation, or by using the addFilters() method and passing it an array containing a list of filter names. Chapter 3: Working with Forms Filter Name Description Alnum Removes non-alphanumeric characters from argument Alpha Removes non-alphabetic characters from argument Digits Removes non-numeric characters from argument Int Returns integer value of argument Dir Returns directory name component of argument BaseName Returns filename component of argument RealPath Returns absolute filesystem path for argument StringToLower Converts argument to a lowercase string StringToUpper Converts argument to an uppercase string StringTrim Removes leading and trailing whitespace from argument StripNewlines Removes line break characters from argument HtmlEntities Converts special characters in argument to their HTML entity equivalents StripTags Removes HTML and PHP code from argument Encrypt Returns encrypted version of argument Decrypt Returns decrypted version of argument NormalizedToLocalized Returns argument in standard form LocalizedToNormalized Returns argument in localized form Callback Calls user-defined filter with argument LowerCase Converts contents of uploaded file to lowercase UpperCase Converts contents of uploaded file to uppercase Rename Renames uploaded file Table 3-2 Input Filters Included with the Zend Framework Using Input Validators Filtering input is only part of the puzzle. It’s also extremely important to validate user input to ensure that it is in the correct format before using it for calculations or saving it to the application’s data store. Improperly validated application input can not only cause significant data corruption and loss, but it can also be embarrassing in the extreme to the proud application developer. In order to illustrate the importance of input validation, consider a simple example: an online mortgage calculator that allows a user to enter the desired loan amount, finance term, and interest rate. Now, let’s assume that the application doesn’t include any input validation. And let’s also suppose that the user decides to enter the string 'ten', instead of the number 10, into the term field. 73 74 Zend Framework: A Beginner’s Guide It shouldn’t be too hard to guess what happens next. The application will perform a few internal calculations that will end in it attempt to divide the total amount payable by the specified term. Since the term in this case is a string, PHP will cast it to the number 0, producing a divisionby-zero error. The resulting slew of ugly error messages is likely to leave even the most blasé developer red-faced; more importantly, if the invalid input is also saved to the database as is, the error will recur every time the calculation is repeated on the record. Multiply this by even a few hundred records containing similar errors, scattered throughout the database, and you’ll quickly see how the lack of appropriate input validation can significantly damage an application. PHP comes with various functions to assist developers in the task of validating input. For example, the is_numeric() function tests if a value is numeric, while the ctype_alpha() and ctype_alnum() functions can be used to test for alphabetic and alphanumeric strings. There’s also the filter_var() function, while can be used to test the validity of email addresses and URLs, and the preg_match() function, which allows for pattern validation using regular expressions. Here’s an example of some of these functions in action: As with filters, the Zend Framework ships with a large number of predefined input validators, collectively referred to as Zend_Validate, which can either be attached to form elements with the addValidator() method or used ad hoc. Validator-specific options can Chapter 3: Working with Forms be passed as the third argument to the addFilter() method as an associative array of keyvalue pairs, as shown in the following example: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for age // should contain only integer values between 1 and 100 $age = new Zend_Form_Element_Text('age'); $age->setLabel('Age:') ->setOptions(array('size' => '4')) ->setRequired(true) ->addValidator('Int') ->addValidator('Between', false, array(1,100)); // create text input for name // should contain only alphabetic characters and whitespace $name = new Zend_Form_Element_Text('name'); $name->setLabel('First name:') ->setOptions(array('size' => '16')) ->setRequired(true) ->addValidator('Alpha', false, array('allowWhiteSpace' => true)); // create text input for email address // should contain a valid email address $email = new Zend_Form_Element_Text('email'); $email->setLabel('Email address:') ->setOptions(array('size' => '16')) ->setRequired(true) ->addValidator('EmailAddress'); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Sign Up'); // attach elements to form $this->addElement($age) ->addElement($name) ->addElement($email) ->addElement($submit); } } 75 76 Zend Framework: A Beginner’s Guide As with filters, validators can also be specified as instances of the corresponding Zend_ Validate_* class, with validator options passed as arguments to the object constructor. The next example, which is equivalent to the previous one, illustrates this approach: setAction('/sandbox/example/form') ->setMethod('post'); // create text input for age // should contain only integer values between 1 and 100 $age = new Zend_Form_Element_Text('age'); $age->setLabel('Age:') ->setOptions(array('size' => '4')) ->setRequired(true) ->addValidator(new Zend_Validate_Int()) ->addValidator(new Zend_Validate_Between(1,100)); // create text input for name // should contain only alphabetic characters and whitespace $name = new Zend_Form_Element_Text('name'); $name->setLabel('First name:') ->setOptions(array('size' => '16')) ->setRequired(true) ->addValidator(new Zend_Validate_Alpha(true)); // create text input for email address // should contain a valid email address $email = new Zend_Form_Element_Text('email'); $email->setLabel('Email address:') ->setOptions(array('size' => '16')) ->setRequired(true) ->addValidator(new Zend_Validate_EmailAddress()); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Sign Up'); // attach elements to form $this->addElement($age) ->addElement($name) ->addElement($email) ->addElement($submit); } } Chapter 3: Working with Forms The result of submitting a form with invalid input values Figure 3-13 Figure 3-13 illustrates the result of attempting to submit invalid values through such a form. Table 3-3 provides a list of some important validators available in the Zend Framework, together with a brief description of each. You’ll see many of these validators in use further along in this chapter, as well as in subsequent chapters. Validator Name Description NotEmpty Returns false if argument is empty StringLength Returns false if argument does not conform to specified minimum/maximum length InArray Returns false if argument is not in specified array Identical Returns false if argument does not match specified value Alnum Returns false if argument does not contain only alphanumeric characters Alpha Returns false if argument does not contain only alphabetic characters Int Returns false if argument is not an integer Float Returns false if argument is not a floating-point number Hex Returns false if argument is not a hexadecimal value Digits Returns false if argument does not contain only numbers Between Returns false if argument is not in a specified numeric range Table 3-3 Input Validators Included with the Zend Framework 77 78 Zend Framework: A Beginner’s Guide Validator Name Description GreaterThan Returns false if argument is not greater than a specified value LessThan Returns false if argument is not less than a specified value Date Returns false if argument is not a valid date EmailAddress Returns false if argument does not conform to standard email address conventions Hostname Returns false if argument does not conform to standard host name conventions Ip Returns false if argument does not conform to standard IP address conventions Regex Returns false if argument does not conform to specified regular expression pattern Barcode Returns false if argument is not a valid bar code Ccnum Returns false if argument does not conform to the Luhn algorithm for standard credit card number conventions Iban Returns false if argument is not a valid IBAN number Exists Returns false if argument is not a valid file Count Returns false if number of uploaded files is outside the range specified in argument Size Returns false if uploaded file size is outside the range specified in argument FilesSize Returns false if uploaded file size total is outside the range specified in argument Extension Returns false if uploaded file extension does not match those specified in argument MimeType Returns false if uploaded file MIME type does not match those specified in argument IsCompressed Returns false if uploaded file is not a compressed archive file IsImage Returns false if uploaded file is not an image file ImageSize Returns false if uploaded image dimensions are outside the range specified in argument Crc32, Md5, Sha1, Hash Returns false if uploaded file content does not match the hash value specified in argument (supports crc32, md5, and sha1 hash algorithms) ExcludeExtension Returns false if uploaded file extension matches those specified in argument ExcludeMimeType Returns false if uploaded file MIME type matches those specified in argument WordCount Returns false if number of words in uploaded file is outside the range specified in argument Db_RecordExists Returns false if a particular record does not exist in the database and table specified in argument Db_NoRecordExists Returns false if a particular record exists in the database and table specified in argument Table 3-3 Input Validators Included with the Zend Framework (continued ) Chapter 3: Working with Forms Ask the Expert Q: I’m already validating form input using JavaScript. Why do I also need to validate it using PHP? A: It’s common practice to use client-side scripting languages like JavaScript or VBScript for client-side input validation. However, this type of client-side validation is not foolproof—if a user turns off JavaScript in the client, all your client-side code will become nonfunctional. That’s why it’s a good idea to couple client-side validation (which is faster) with server-side validation (which is more secure). Using Validator and Filter Chains One of the most interesting things about the Zend_Filter and Zend_Validate components is their support for chaining or stacking. Essentially, this means that it is possible to attach multiple filters and validators to a single input element, and have them automatically run, in sequence, once the form is submitted. The following example illustrates this by setting up a chain of four filters: setLabel('First name:') ->setOptions(array('size' => '16')) ->setRequired(true) ->addFilter('StripTags') ->addFilter('HTMLEntities') ->addFilter('StringTrim') ->addFilter('StringToLower'); ?> In this example, the first filter strips HTML and PHP tags from the input, the second encodes entities, the third trims leading and trailing whitespace, and the fourth transforms the result to lowercase. These filters are executed on the input value in their order of appearance in the chain. Validator chains work in a similar manner and come with an additional property. A validator chain can be configured such that a failure in any one validator terminates the entire chain with an error message. This behavior is controlled by the second argument to the addValidator() method which, when set to true, breaks the chain if there is a failure in the corresponding validator. Consider the next example, which illustrates this: setLabel('Age:') ->setOptions(array('size' => '4')) ->setRequired(true) ->addValidator('NotEmpty', true) ->addValidator('Int', true) ->addValidator('Between', true, array(1,100)); ?> In this example, a failure in any one of the validators breaks the chain, and the remaining validators will not be processed. So, for example, if the input is not an integer value, the validation chain will terminate with the error message generated by the Int validator, and the Between validator will not be executed. Contrast this with the next listing: setLabel('Age:') ->setOptions(array('size' => '4')) ->setRequired(true) ->addValidator('NotEmpty', false) ->addValidator('Int', false) ->addValidator('Between', false, array(1,100)); ?> In this version, even if one of the validators fails, the remaining validators will still be run, and error messages generated by any subsequent failures will be added to the message stack. This is illustrated in Figures 3-14 and 3-15, which compare and contrast the difference in behavior of these two listings. Figure 3-14 A validator chain, broken on the first failure Chapter 3: Figure 3-15 Working with Forms A validator chain, processed without any break TIP In case the predefined filters and validators that ship with the Zend Framework don’t meet your needs, remember that you can always write your own. The Zend Framework manual has examples of how to do this. Retrieving and Processing Form Input Within a controller script, you can use a number of Zend_Form methods to retrieve and process form input after submission: ● The isValid() method checks if the submitted input is valid. This method accepts an array of input values and returns Boolean true or false depending on whether these values match the validation rules set up with the various addValidator() calls. ● If the input is invalid, the getMessages() method returns a list of the error messages generated during the validation process. This list can be processed and displayed when the form is re-rendered to give the user a hint about what went wrong. ● If the input is valid, the getValues() method can be used to retrieve the valid, filtered values for further processing. Input values are returned as elements of an associative array, where the array key represents the element name and the array value represents the corresponding input value. There’s also a getUnfilteredValues() method, which returns the original, unfiltered input as entered by the user. TIP The isValid() method automatically verifies CAPTCHA and hash values, with no additional programming required on your part. 81 82 Zend Framework: A Beginner’s Guide The next listing illustrates how these methods are typically used in the context of a controller script: view->form = $form; // check the request // run the validators if ($this->getRequest()->isPost()) { if ($form->isValid($this->getRequest()->getPost())) { // valid data: get the filtered and valid values // do something, save to database or write to file // display a success view $values = $form->getValues(); $this->_redirect('/form/success'); } else { // invalid data: get the error message array // for manual processing (if needed) // redisplay the form with errors $this->view->messages = $form->getMessages(); } } } } ?> Try This 3-1 Creating a Contact Form With all this background information at hand, let’s now look at how it plays out in the context of a practical application. The following section applies everything you’ve learned so far to create an email inquiry form for the SQUARE application. This form will invite the user to enter a message and, on submission, will format the input into an email message and send it to the site administrators for follow-up. Defining the Form To begin, let’s consider the requirements of the input form. They aren’t very complicated—all that’s really needed are three fields for the user to enter his or her name, email address, and message. These values should be validated, particularly the email address, to ensure authenticity and thereby make it possible for administrators to respond to email inquiries. To filter out automated submissions and reduce the incidence of spam, it would also be nice to include a Chapter 3: Working with Forms visual CAPTCHA—something that’s quite easy to do with Zend_Form, as illustrated earlier. Here’s an example of what the resulting form definition would look like: setAction('/contact/index') ->setMethod('post'); // create text input for name $name = new Zend_Form_Element_Text('name'); $name->setLabel('Name:') ->setOptions(array('size' => '35')) ->setRequired(true) ->addValidator('NotEmpty', true) ->addValidator('Alpha', true) ->addFilter('HTMLEntities') ->addFilter('StringTrim'); // create text input for email address $email = new Zend_Form_Element_Text('email'); $email->setLabel('Email address:'); $email->setOptions(array('size' => '50')) ->setRequired(true) ->addValidator('NotEmpty', true) ->addValidator('EmailAddress', true) ->addFilter('HTMLEntities') ->addFilter('StringToLower') ->addFilter('StringTrim'); // create text input for message body $message = new Zend_Form_Element_Textarea('message'); $message->setLabel('Message:') ->setOptions(array('rows' => '8','cols' => '40')) ->setRequired(true) ->addValidator('NotEmpty', true) ->addFilter('HTMLEntities') ->addFilter('StringTrim'); // create captcha $captcha = new Zend_Form_Element_Captcha('captcha', array( 'captcha' => array( 'captcha' => 'Image', (continued) 83 84 Zend Framework: A Beginner’s Guide 'wordLen' 'timeout' 'width' 'height' 'imgUrl' 'imgDir' 'font' => => => => => => => 6, 300, 300, 100, '/captcha', APPLICATION_PATH . '/../public/captcha', APPLICATION_PATH . '/../public/fonts/LiberationSansRegular.ttf', ) )); $captcha->setLabel('Verification code:'); // create submit button $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Send Message') ->setOptions(array('class' => 'submit')); // attach elements to form $this->addElement($name) ->addElement($email) ->addElement($message) ->addElement($captcha) ->addElement($submit); } } You should already be familiar with most of the preceding code. The form contains two text input elements for the user’s name and email address, one text area for the message body, and a CAPTCHA element for verification. Alpha and NotEmpty validators are attached to the name and message body fields, while an EmailAddress validator is used to check the submitted email address. All fields are filtered using the HTMLEntities validator, and the email address is additionally converted to lowercase with the StringToLower validator. The options passed to the Zend_Form_Element_Captcha instance are also worth looking into. Unlike the example shown in an earlier section, this definition generates a more complex CAPTCHA by dynamically overlaying a random sequence of characters on a distressed background. This type of CAPTCHA is commonly used in Web forms to stymie automated bot submissions, many of which include optical character recognition (OCR) algorithms that can “read” characters overlaid on a clear background. The options passed to the object instance include the dimensions of the CAPTCHA image, the disk location to store the generated CAPTCHA, the number of characters in the CAPTCHA, and the font file to use for the text overlay. CAUTION If you’re using copyrighted fonts that cannot be redistributed, you should move the $APP_DIR/public/fonts/ directory to a location outside the server document root, such as $APP_DIR/application/fonts/, to ensure that the fonts are not publicly accessible through a Web browser. If you’re doing this, remember to update the application code to reflect the new path as well. Chapter 3: Working with Forms You’ll notice that the previous example makes use of a custom font, and it stores generated CAPTCHAs in a specified directory. Accordingly, also create the $APP_DIR/public/captcha/ and $APP_DIR/public/fonts/ directories and copy over the necessary assets to these locations. You’ll find these assets in the code archive for this chapter, which can be downloaded from this book’s companion Web site at http://www.zf-beginners-guide.com/. NOTE The font used for the CAPTCHA in this example is the Liberation Sans font, part of a collection of fonts released to the community under the GNU General Public License by RedHat Inc. in 2007. Users are free to use, modify, copy, and redistribute these fonts under the terms of the GNU GPL. Using a Custom Namespace The definition in the previous section uses a custom namespace, “Square,” which is prefixed to the class name. This is a recommended practice for any custom objects or libraries that you may create for the application, as it helps avoid name collisions between your definitions and others that may exist in the application space. An added benefit is that if you register your custom namespace with the Zend Framework’s autoloader and then locate your definitions correctly in the application directory structure, the Zend Framework will automatically find and load them as needed at run time. With this in mind, save the class definition from the preceding code to $APP_DIR/ library/Square/Form/Contact.php, and then add the following directive to the application configuration file, at $APP_DIR/application/configs/application.ini, to register the “Square” namespace with the autoloader: autoloaderNamespaces[] = "Square_" CAUTION If your classes use an underscore to separate the namespace from the rest of the class name, you must include this underscore when registering the namespace with the Zend Framework autoloader. Defining a Custom Route This is also a good time to add a custom route for the new form. While you’ve got the application configuration file open in your text editor, add the following route definition to it: resources.router.routes.contact.route = /contact resources.router.routes.contact.defaults.module = default resources.router.routes.contact.defaults.controller = contact resources.router.routes.contact.defaults.action = index (continued) 85 86 Zend Framework: A Beginner’s Guide Based on the material discussed in Chapter 2, this should be quite familiar to you—it sets up a route such that requests for the application URL /contact are handled by the “default” module’s ContactController::indexAction. Defining Controllers and Views The next step is to define the aforesaid ContactController::indexAction.. By convention, this controller should be located at $APP_DIR/application/modules/default/ controllers/ContactController.php, and should look something like this: view->doctype('XHTML1_STRICT'); } public function indexAction() { $form = new Square_Form_Contact(); $this->view->form = $form; if ($this->getRequest()->isPost()) { if ($form->isValid($this->getRequest()->getPost())) { $values = $form->getValues(); $mail = new Zend_Mail(); $mail->setBodyText($values['message']); $mail->setFrom($values['email'], $values['name']); $mail->addTo('info@square.example.com'); $mail->setSubject('Contact form submission'); $mail->send(); $this->_helper->getHelper('FlashMessenger') ->addMessage('Thank you. Your message was successfully sent.'); $this->_redirect('/contact/success'); } } } public function successAction() { if ($this->_helper->getHelper('FlashMessenger')->getMessages()) { $this->view->messages = $this->_helper->getHelper('FlashMessenger')->getMessages(); } else { Chapter 3: Working with Forms $this->_redirect('/'); } } } Most of the heavy lifting here is done by the indexAction() method, which creates an object of the Square_Form_Contact class discussed earlier and attaches it to the view. When the form is submitted, the object’s isValid() method is used to validate the input submitted by the user. If the input is found to be valid, an instance of the Zend_Mail component is created, and object methods are used to format the input into an email message and send it to a specified email address. Once the message has been sent, control is transferred to the successAction() method, which renders a success view. That’s what happens if all goes well…but there’s many a slip ’twixt the cup and the lip, so it’s useful to understand what happens if things go wrong. If the input is found to be invalid, the isValid() method will return false and the form will be redisplayed, with error messages indicating the source of the error(s). Zend_Form will also automatically populate the form with the original input values to ensure that the user doesn’t need to re-enter all the requested data. On the other hand, if the input is valid but an error occurs in the process of email generation and transmission, Zend_Mail will throw a PHP exception, which will be caught and handled by the application’s default error handler. NOTE In order for the Zend_Mail object’s send() method to work correctly, a mail delivery agent (such as sendmail) must be available and correctly configured in your php.ini configuration file. If this is not the case, message transmission will fail and the send() method will throw an exception at the point of failure. This controller also introduces a new tool, the FlashMessenger helper, which is a useful little “helper” to simplify the display of status messages to the user. Messages can be added to the FlashMessenger object via its addMessage() method; these messages are then stored in the session until retrieved with a call to the getMessages() method, at which point they are removed from the session. This makes the FlashMessenger a convenient place to temporarily store messages between the time an operation ends and the time the subsequent view completes rendering, and you’ll see it being used frequently throughout this book. CAUTION You’ll notice that the controller’s init() method sets the view’s document type to XHTML 1.0 Strict. This is because, by default, Zend_Form doesn’t produce well-formed XHTML markup. Setting the document type in this manner forces it to do so. Obviously, you also need a couple of views, one for the input form and one for the success message. Here’s the input view, which by convention should be located at $APP_DIR/ application/modules/default/views/scripts/contact/index.phtml:DocumentRoot "/usr/local/apache/htdocs/square/public" ServerName square.localhost These lines define a new virtual host, http://square.localhost/, whose document root corresponds to the $APP_DIR/public/ directory. Restart the Web server to activate these new settings. Note that if you’re on a network, it might be necessary to update your network’s local DNS server to let it know about the new host as well. (continued) 13 14 Zend Framework: A Beginner’s Guide Figure 1-6 The default application index page Once these steps are complete, pop open your Web browser and browse to the virtual host that you just set up, by entering the URL http://square.localhost/. If you see a Zend Framework welcome page, like the one shown in Figure 1-6, pat yourself on the back, because you just got a complete (albeit extremely simple) Zend Framework application up and running! Using the Command-Line Tool As illustrated in the previous section, the zf command-line script allows you to perform a number of different operations. For example, drop to your command prompt and issue the following command: shell> zf show version Chapter 1: Introducing the Zend Framework Ask the Expert Q: Can I use the Zend Framework in a shared hosting environment, where I’m likely to have limited or no control over global PHP configuration directives like the PHP “include path”? A: Yes, absolutely. There are a couple of ways to accomplish this: L L If you’re simply concerned about using Zend Framework classes in your application, all you need to do is copy the library/Zend directory to your home area, and then use the ini_set() function to dynamically add this location to your PHP include path in your application scripts. If you’d like to use the zf command-line script, you should also copy the bin/ directory to your home area (at the same level as the library/ directory). You should then be able to invoke the zf command-line script as usual, by prepending the complete filesystem path to the script name. This will work because, if the Zend Framework cannot be found in the PHP include path, the zf command-line script will also look for a library/ Zend directory one level above it in the current directory hierarchy and use it if available. Alternatively, you can explicitly tell the zf command-line script where to find your Zend Framework installation, by setting the ZEND_TOOL_INCLUDE_PATH_ PREPEND environment variable to the appropriate location. This command displays the version number of the currently installed Zend Framework release. Figure 1-7 and Figure 1-8 illustrate the output on Linux and Windows, respectively. You can also try the following command to retrieve complete phpinfo() information: shell> zf show phpinfo The zf command-line tool also provides a quick and easy way to view the current “profile” of your application. This profile contains a hierarchical list of the current contents of your application, with descriptions of the files within it, and it’s a great way to get a fast bird’s-eye view of the application without manually drilling down into each directory. Figure 1-7 The output of the zf show version command on Linux 15 16 Zend Framework: A Beginner’s Guide Figure 1-8 The output of the zf show version command on Windows Try it out by changing directories to $APP_DIR and executing the following command: shell> zf show profile Figure 1-9 illustrates the output on Linux. Figure 1-9 An example project profile Chapter 1: Introducing the Zend Framework Summary This chapter provided a gentle introduction to the world of Zend Framework development, introducing you to the project and illustrating some of its unique features and advantages visà-vis competing alternatives. It also guided you through the process of installing the Zend Framework and using the command-line tool to start a new project. These basic skills will serve you well as you move to the next chapter, which discusses core application development concepts and gets you started with building a framework-based Web application. If you’d like to learn more about the topics discussed in this chapter, you’ll find the following links useful: L The official Zend Framework Web site, at http://framework.zend.com/ L The Zend Framework community wiki, at http://framework.zend.com/wiki/ L Zend Framework usage statistics, at http://framework.zend.com/about/numbers L Zend Framework case studies, at http://framework.zend.com/about/casestudies L Zend Framework components, at http://framework.zend.com/about/components L The Zend Framework CLI tool, at http://framework.zend.com/manual/en/zend.tool.framework.clitool.html L The Zend Framework filesystem layout, at http://framework.zend.com/wiki/display/ZFDEV/Choosing+Your+Application%27s+ Directory+Layout L The Zend Framework development roadmap, at http://framework.zend.com/roadmap 17 This page intentionally left blank Chapter 2 Working with Models, Views, Controllers, and Routes 19 20 Zend Framework: A Beginner’s Guide Key Skills & Concepts L Understand the basics of the Model-View-Controller pattern L Find out how URL requests are handled in a Zend Framework application L Gain the benefits of a modular directory layout L Define and apply a global template to application views L Create custom routes for application resources L Learn to serve static content pages T he preceding chapter gave you a gentle introduction to the Zend Framework, by guiding you through the process of installing the framework and starting a new project. You now need to start fleshing out the application skeleton with code that makes it functional. This chapter will help you to do so, by introducing you to the fundamental design principles of a Zend Framework application and then applying this knowledge to the task of building a real-world application. So without further ado, let’s jump straight in! Understanding Basic Concepts When you are developing a PHP application, the typical approach is to embed PHP code into one or more HTML documents using special delimiters. This makes it easy to construct dynamic Web pages containing programming constructs like variables and function calls; simply alter the values of the variables embedded within the HTML code, and the content displayed on the page changes appropriately. As every application developer knows, however, this convenience comes at a price. The approach described in the previous paragraph produces PHP scripts that are so closely interwoven with HTML code that maintaining them is a nightmare. Since the same physical file usually contains both HTML interface elements and PHP code, developers and interface designers must coordinate with each other to make changes. The most common example of this is when interface designers need to alter the look and feel of a Web application—typically, the changes they make to the HTML code must be monitored by a developer to ensure the integrity of the embedded business logic. This type of arrangement is easily recognized by its most visible symptom: a bunch of harried developers and designers clustered around a single computer arguing with each other as they take turns at the keyboard. Needless to say, in addition to producing frayed tempers and suboptimal code, this approach also usually requires more time and money than is strictly necessary for the task at hand. And that’s where the Zend Framework can help. Chapter 2: Working with Models, Views, Controllers, and Routes Zend Framework applications are built according to a widely accepted set of principles which encourage code reusability, maintainability, and scalability. One of the linchpins of this approach is the Model-View-Controller (MVC) design pattern, which allows application business logic to be separated from the user interface and data models, such that they can be manipulated independent of each other. The MVC pattern also encourages efficient organization and separation of an application’s responsibilities, and allows different components to be tested independently. The following sections explain the key components of the MVC pattern, with specific notes on the Zend Framework’s implementation where relevant. Models Every application is driven by data, whether it’s something as simple as a username and password or as complex as a multicurrency shopping cart. In the MVC pattern, this “data layer” is represented by one or more models, which provide functions to retrieve, save, delete, and otherwise manipulate application data. This data layer is output-agnostic: it is completely concerned with the data itself, and completely unconcerned with how that data is presented to the user. As such, it provides a logically independent interface to manipulate application data. To illustrate, consider a simple Web application that allows users to post classified advertisements for used cars. Under the MVC pattern, this application’s data—the car listings—would be represented by a Listing model, which would expose methods for manipulating the underlying data. This model would not be concerned with the visual display of the listings; rather, its focus would be on the functions needed to access and manipulate individual listings and their attributes in the data store. Here’s an example of what one such model might look like: When application data is stored in a database, such as MySQL, SQLite, or PostgreSQL, models may make use of an underlying database abstraction layer to handle the tasks of managing database connections and executing SQL queries. The Zend Framework includes a database abstraction layer, Zend_Db, which provides a common interface to many different database systems, and models in the Zend Framework are typically expressed using the Data Mapper pattern. It’s also quite easy to integrate third-party models, such as those created with Object-Relational Mapping (ORM) tools such as Doctrine and Propel, into a Zend Framework application. Views If models are concerned solely with accessing and manipulating the application’s raw data, views are concerned solely with how this data is presented to the user. Views can simply be thought of as the “user interface layer,” responsible for displaying data but not capable of directly accessing or manipulating it. Views can also receive input from the user, but their responsibility is again limited to the appearance and behavior of the input form; they are not concerned with processing, sanitizing, or validating the input data. In the context of the classifieds application discussed earlier, views would be responsible for displaying current listings and for generating forms into which new listings could be entered. So, for example, there might be a view to display all the latest listings in a particular category, and a view to input new listings. In all of these cases, the controller and/or the model would handle the tasks of retrieving and processing data, while the view would take care of massaging this data into an acceptable format for display and then rendering it to the user. Here’s an example of a view intended to display the most recent listings: Chapter 2: Working with Models, Views, Controllers, and Routes
As this example illustrates, views are typically expressed as PHP scripts containing the HTML code or markup necessary to correctly render and display output to the user. These scripts can also contain variable placeholders for dynamic data; values for these placeholders are set by the corresponding controller and interpolated into the view when it is rendered. Under the Zend Framework, view scripts are rendered by the Zend_View component, which also provides ancillary functions for output escaping and a set of “helpers” for common view tasks such as navigation, metatag creation, and link generation. It’s also quite easy to integrate third-party template engines, such as Smarty or Savant, into a Zend Framework application by extending the Zend_View_Interface abstract class. Controllers Controllers are the link between models and views. They make changes to application data using models, and then call views to display the results to the user. A controller may be linked to multiple views, and it may call a different view depending on the result that is to be shown at any given time. Controllers can thus be thought of as the “processing layer,” responsible for responding to user actions, triggering changes in application state, and displaying the new state to the user. In the context of the application discussed earlier, controllers would be responsible for reading and validating request parameters, saving and retrieving listings using model functions, and selecting appropriate views to display listing details. So, for example, a controller would intercept a request for the most recent listings, query the ListingModel model for a list of recent entries, select an appropriate view, interpolate the entries into the view, and 23 24 Zend Framework: A Beginner’s Guide render the view. Similarly, if the user chose to add a new listing, the controller would select and render an input view, validate and sanitize the user’s input, insert the sanitized input into the data store using the ListingModel model, and then select and render another view to display whether or not the operation was successful. Here’s an example of a controller that brings together the model and view illustrated previously: find( array( 'date' => '-10 days', 'status' => 'published' ) ); // code to initialize view // and populate with data returned from model $view = new ListingView(); $view->listings = $matches; echo $view->render('recent.php'); } } ?> As this example illustrates, a controller serves as an intermediary, invoking model methods to perform operations on application data, and using views to display the results of those operations to the user. Under the Zend Framework, controllers are created as children of the Zend_Controller_Action class, and contain methods, also called actions, which hold the processing code necessary to interact with models and views. There’s also an übercontroller, the front controller, which is responsible for intercepting user requests and invoking appropriate controller and action methods to satisfy them (more on this in the next section). In addition to the three key components described above, the Zend Framework also introduces some additional ideas to streamline application development. These are described in the following sections. Modules By default, all models, controllers, actions, and views live in the—what else?—“default” module. However, you may often wish to group models, views, and controllers together into self-contained “boxes” based on the different functional areas of your Web application. Chapter 2: Working with Models, Views, Controllers, and Routes Modules provide a way to accomplish this. So, for example, if your application includes functions for search, user profile management, and news, you could create separate “search,” “profile,” and “news” modules, and place the corresponding models, views, and controllers for each in these modules. Modules provide a convenient way to organize application code, or a way to build thirdparty application components that can easily be plugged in to an existing installation. The Zend Framework’s default router is fully module-aware, allowing you to begin using modules in your application without any custom programming, and each module (except the “default” module) also has its own namespace to prevent object and variable collisions. Routes Routes provide the link between user requests and actions. When a user makes a request for an application URL, the front controller intercepts that request and decides, based on the URL pattern, which controller and action should be invoked to fulfill the request. This process of routing requests to controllers is a key part of the application execution flow, and is capable of extensive configuration. Routes make use of regular expressions for pattern matching, and can be expressed using either XML or INI file syntax. By default, the Zend Framework includes some standard routes that are suitable for applications of small to medium complexity. These standard routes assume that application URLs are of the form /module/controller/action, and divert user requests accordingly. So, for example, a request for http://application/auto/listing/index would be automatically mapped to ListingController::indexAction in the “auto” module. Notice that controller and action names follow a specific case and naming convention. NOTE The Zend Framework’s routing subsystem automatically applies default values to routes that don’t conform to the /module/controller/action format. A detailed discussion of these default values, and their impact on how request URLs are mapped to controllers and actions, can be found in the section entitled “Understanding Component Interaction.” In case the standard routes described in the previous paragraph are too limiting or inflexible for the demands of your application, the Zend Framework also supports custom, user-defined routes. These routes support (among other things) optional and mandatory parameters, default values, and multiple chained routes, and they allow you to micromanage your application’s routing so that application URLs need not directly reflect your internal classification of controllers, actions, and modules. Layouts In the classical sense, a layout is a definition of how a collection of elements is arranged. In the context of a Zend Framework application, layouts can be thought of as interface templates, providing a way to “decorate” application content using one or more standard applicationwide interfaces. Layouts provide a way to abstract common interface elements, such as page 25 26 Zend Framework: A Beginner’s Guide headers and footers or site-wide navigation widgets, into separate files that can be edited and maintained independent of individual views. It’s quite common for an application to have more than one layout. For example, you might wish to present one interface to users and another to administrators, or you might wish to allow users to customize their experience of the application by selecting from a set of predefined interface themes. Layouts make these, and other templating options, reasonably easy to implement. NOTE You’ll begin working with modules, custom routes, and layouts later in this chapter. Understanding Component Interaction If you think of a Zend Framework application like a circus (and the analogy is often more than a little apt), then the front controller is the ringmaster, whipping the acts into shape and making sure the audience is satisfied. This section examines its role in more detail, illustrating it in the context of the steps that go into intercepting and satisfying a request for an application resource. Figure 2-1 illustrates the flow of a request through a typical Zend Framework application. Request Front controller Database Router Response Figure 2-1 Action controller Model View Layout Interaction between models, views, and controllers Chapter 2: Working with Models, Views, Controllers, and Routes As the controller tasked with intercepting client requests and directing them to the appropriate target for response, the front controller has a key role to play in the overall flow of a request through the application. 1. When a request arrives, the Web server’s .htaccess file automatically rewrites it into a standard format and passes it on to the index.php script. This script sets up the application environment, reads the application configuration file, and creates an instance of the front controller. 2. The front controller examines the request and determines the key components of the URL. It then attempts to route the request to an appropriate controller and action. To perform this routing, the front controller will check both default and custom routes, and make use of pattern-matching techniques to select an appropriate target for the request. 3. If a match is found, the front controller transfers control to the corresponding controller and action. Once invoked, the action makes changes to the application state using one or more models. It also selects the view to be displayed and sets any required view properties. Once the action has completed, the selected view renders its output, wrapping it in a layout as needed. This output is then transmitted back to the requesting client. 4. In the event that none of the application’s defined routes match the request, an exception is thrown and the error controller and action are invoked. Based on the parameters of the exception, the error action renders a view containing a failure notice. This output is then transmitted back to the requesting client. NOTE Any uncaught exceptions generated during the request-handling process will invoke the error controller and corresponding action. This will produce a view containing a failure notice, which is transmitted back to the requesting client. As noted earlier, the routing subsystem automatically maps URLs in the format /module/controller/action to the corresponding module, controller, and action. So, for example, to access the ListingController::saveAction in the “auto” module, you’d need to request the URL http://application/auto/listing/save. Similarly, to access the NewsController::editAction in the “content” module, you’d need to request the URL http://application/content/news/edit. If the routing subsystem receives a request that doesn’t conform to the /module/controller/ action format, it automatically applies default settings, as follows: L For requests without a module name, the routing subsystem automatically assumes the module to be the “default” module. L For requests without a controller name, the routing subsystem automatically assumes the controller to be the IndexController of the selected module. 27 28 Zend Framework: A Beginner’s Guide L For requests without an action name, the routing subsystem automatically assumes the controller to be the indexAction of the selected controller. To better understand how these default settings play out in practice, consider the following examples: L The ContactController::sendAction in the “default” module can be accessed at both http://application/default/contact/send and http://application/contact/send. L The PostController::indexAction in the “default” module can be accessed at both http://application/default/post/index and http://application/post/. L The NewsController::indexAction in the “content” module can be accessed at both http://application/content/news/index and http://application/content/news. Looking Behind the Default Index Page With all this background information at hand, let’s go back to the code generated by the zf command-line tool in Chapter 1 and take a closer look at how the default Zend Framework welcome page is generated. We’ll begin at the beginning, with the initial request for http:// square.localhost/. This request produces the default application index page shown in Figure 2-2. What goes into making this happen? Well, consider that when you request the URL http:// square.localhost/, the default routes described in the previous section come into play, and this URL is automatically rewritten to http://square.localhost/default/index/index. The Zend Framework’s routing subsystem then redirects this request to the “default” module’s IndexController and indexAction() method. This controller and action is automatically created by the zf command-line tool, and by convention is stored at $APP_DIR/application/controllers/IndexController.php. Here’s what it looks like: a:link, a:visited { color: #0398CA; } span#zf-name { color: #91BE3F; } div#welcome { color: #FFFFFF; background-image: url( http://framework.zend.com/images/bkg_header.jpg); width: 600px; height: 400px; border: 2px solid #444444; overflow: hidden; text-align: center; } 29 30 Zend Framework: A Beginner’s Guide div#more-information { background-image: url( http://framework.zend.com/images/bkg_body-bottom.gif); height: 100%; }Recent listings
listings as $l): ?>title; ?>
content; ?>From this markup, it should be clear that so long as you name and place your controllers, actions, and views correctly, there isn’t really very much work for you to do. The framework will automatically locate and execute files for you, using its default routes, without requiring any manual intervention. As you proceed through this chapter and the remainder of this book, you’ll learn a little more about how these standard Zend Framework conventions can be used to your advantage, by reducing the amount of manual coding required in getting an application up and running. Understanding the Modular Directory Layout In the previous chapter, you saw how the zf command-line tool creates a directory structure for your new Zend Framework application. In its initial form, this structure only contains the directories needed to get a basic test application up and running. As you flesh out your application with new features, you’ll also need to expand this basic structure and create additional directories to hold different types of data. To better understand this, consider Figure 2-3, which illustrates the full directory structure for a Zend Framework application. Each of the directories shown in Figure 2-3 has a specific purpose, as listed in Table 2-1. The $APP_DIR/application/modules/ directory bears special mention. This directory Chapter 2: Working with Models, Views, Controllers, and Routes Directory Description $APP_DIR/application Main application directory $APP_DIR/application/controllers Global controllers $APP_DIR/application/views Global views $APP_DIR/application/models Global models $APP_DIR/application/configs Global configuration data $APP_DIR/application/layouts Global layouts $APP_DIR/application/modules Modules $APP_DIR/library Third-party libraries and classes $APP_DIR/public Main publicly accessible directory $APP_DIR/public/css CSS style sheets $APP_DIR/public/js JavaScript program code $APP_DIR/public/images Application images $APP_DIR/tests Unit tests $APP_DIR/temp Temporary data Table 2-1 The Key Directories in a Zend Framework Application is intended to store application modules, with each module represented as a subdirectory under $APP_DIR/application/modules/. The internal structure of each module directory mirrors that of the global $APP_DIR/application/ directory, as shown in Figure 2-4. This directory structure thus makes a distinction between global application controllers, views, and models, which are stored under the $APP_DIR/application/ hierarchy, and modulespecific controllers, views, and models, which are stored under the $APP_DIR/application/modules/ hierarchy. From a development perspective, the choice of which location to use for your application’s code is an entirely subjective one. There is no one “correct” approach, and so you can choose to store your code in the global directories, in permodule directories, or in a hybrid combination of both, depending on what approach you find Figure 2-3 The recommended directory structure for a Zend Framework application 31 32 Zend Framework: A Beginner’s Guide appropriate to your application’s requirements and structure. That said, the SQUARE example application described in these chapters makes extensive use of modules, and this book recommends the use of modules in general for Zend Framework application development, for the following reasons: L L Organizing code into modules produces a structured code tree, because all the controllers, views, and models related to a particular function or set of functions are stored within the same directory tree. A module-based directory layout also makes the areas of logical separation within an application immediately visible, with no additional documentation necessary, and is more maintainable in the long run. Figure 2-4 The recommended directory structure for a Zend Framework application module Organizing code into modules encourages the creation of more robust and extensible software. Modules can be structured as independent packages with their own controllers, views, and models. Modules can thus be thought of as reusable components that can be plugged in to an existing application to quickly give it new functionality. When you are using modules with the Zend Framework’s default routes, it’s necessary to include the module name in your URL request, in the format /module/controller/action. If the routing subsystem receives a request that doesn’t contain a module name, it automatically looks in what it considers the “default” module—the global application directory, $APP_DIR/ application/. This behavior can create confusion and no small degree of inconsistency in application URLs when the application also contains additional modules in the $APP_DIR/application/ modules/ hierarchy. Therefore, when following a modular directory structure, it’s a good idea to explicitly create a directory for the “default” module, at $APP_DIR/application/ modules/default, and move controllers, views, and models that were previously stored in the $APP_DIR/application/* hierarchy to the $APP_DIR/application/modules/default/* hierarchy. Try This 2-1 Using a Modular Directory Layout As discussed in the previous section, a modular directory layout enforces consistency and produces a more manageable code tree. The following steps discuss how to adopt this layout for the example application created in Chapter 1, as a prelude to beginning application development. Chapter 2: Working with Models, Views, Controllers, and Routes Creating the Default Module The first step is to create the $APP_DIR/application/modules/ directory, and then create a set of subdirectories within that for the default module and its controllers and views. The zf commandline tool does not create these directories, and so it is necessary to perform this task manually. shell> cd /usr/local/apache/htdocs/square/application shell> mkdir modules shell> mkdir modules/default Next, move the existing models, controllers, and views from $APP_DIR/application/* to $APP_DIR/application/modules/default/*: shell> mv controllers modules/default/ shell> mv views modules/default/ shell> mv models modules/default/ Updating the Application Configuration File The next step is to update the global application configuration file, located at $APP_DIR/ application/configs/application.ini, with the location of the modules directory. This tells the Zend Framework’s routing subsystem how to resolve module-specific entities. To perform this update, open the application configuration file in a text editor and add the following lines to the [production] section: resources.frontController.moduleDirectory = APPLICATION_PATH "/ modules" resources.modules = "" Once you’ve completed the preceding steps, try accessing the application index page using the URLs http://square.localhost/ and http://square.localhost/default/index/index. If all has gone well, you should see the default application index page in both cases, as shown in Figure 2-2. TIP Regardless of whether or not you organize your application into modules, remember that you can always redirect URL requests to specific modules, controllers, and actions through the use of custom routes. Understanding Master Layouts and Custom Routes At this point, you know enough about the inner workings of a Zend Framework application to actually begin writing some code of your own. The following sections get you started, by guiding you through the process of creating a custom welcome page, setting up a master layout for the application, and building a custom route to it. 33 34 Zend Framework: A Beginner’s Guide Ask the Expert Q: How is it that the URLs http://square.localhost/ and http://square.localhost/default/index/ index produce the same output page? A: The Zend Framework’s routing subsystem automatically applies certain default settings to routes that don’t conform to the standard /module/controller/action format. Simply put, in the absence of a module name, it assumes the “default” module, and in the absence of a controller and action name, it assumes the indexAction() method of the IndexController of the selected module. As a result of these default substitutions, the request http://square.localhost/ is automatically rewritten to http://square.localhost/default/ index/index, and directed to the “default” module’s IndexController::indexAction for completion. Q: How do naming conventions for controllers, actions, and views work in the Zend Framework? A: The Zend Framework uses “camel-casing” for controller and action names. Controller names are specified using upper camel-case and are suffixed with the word Controller (examples: IndexController, StaticContentController, FunkyChickenController), while action names are specified using lower camel-case and suffixed with the word Action (examples: indexAction, displayPostAction, redButtonAction). For modules other than the “default” module, controller names must be additionally prefixed with the module name (examples: News_IndexController, Catalog_EntryController). View scripts take their name from the corresponding controller and action. Typically, the view script is stored in a directory corresponding to the controller name (without the Controller suffix), in a file whose name corresponds to the action name (without the Action suffix). Therefore, the view script for the clickAction in the ExampleController would be located at /views/scripts/example/click.phtml. Multiple words in the controller or action name are represented by hyphens or periods in the corresponding view script file path. Therefore, the view script for the displayItemAction in the ShoppingCartController would be located at /views/ scripts/shopping-cart/display-item.phtml. To prevent name collisions, the Zend Framework also allows the use of custom namespaces, which can be prefixed to object or class names. These namespaces can be registered with the Zend Framework autoloader, to have the corresponding definitions automatically loaded on demand, as needed. You’ll see an example of this in Chapter 3. Chapter 2: Working with Models, Views, Controllers, and Routes Updating the Application Index Page Now, while the default welcome page created by the zf command-line tool is certainly pretty, it’s not really what you want application users to see in a live environment. So, how about updating it with a custom welcome message and some descriptive information about the application? You already know that the application index page is served by the “default” module’s IndexController::indexAction. Under a modular layout, the view corresponding to this controller and action is located at $APP_DIR/application/modules/default/views/scripts/ index/index.phtml. Open this file in a text editor, and replace its contents with the following markup:Welcome to the Zend Framework!
This is your project's main page
![]()
Helpful Links:
Zend Framework Website | Zend Framework ManualWelcome!
Welcome to SQUARE, our cutting-edge Web search application for rare stamps.
We have a wide collection of stamps in our catalog for your browsing pleasure, and we also list hundreds of thousands of stamps from individual collectors across the country. If you find something you like, drop us a line and we'll do our best to obtain it for you. Needless to say, all stamps purchased through us come with a certificate of authenticity, and our unique 60-day money-back guarantee.
The SQUARE application is designed to be as user-friendly as possible. Use the links in the menu above to navigate and begin searching.
Save the changes, and revisit the application index page at http://square.localhost/. You should see a revised index page, as shown in Figure 2-5. Figure 2-5 The updated application index page 35 36 Zend Framework: A Beginner’s Guide Setting a Master Layout The application index page now displays relevant content, but it’s still a long way from being easy on the eyes. So, the next step is to give it some visual pizzazz by adding some images and navigation to it. These elements will be common to all the pages of the application, so, although you can add them to each individual view of your application, they’re better suited for placement in a master layout, which can then be wrapped around each view. Using a layout is not only less time-consuming, but because the layout is stored in a single file that’s accessed from multiple views, it’s also easier to make changes as the application evolves. To set a master layout for the application, perform the following steps. Creating the Layout Template File The first step is to create the $APP_DIR/application/layouts/ directory, which is the default location for layout files under the Zend Framework’s recommended directory layout. shell> cd /usr/local/apache/htdocs/square/application shell> mkdir layouts Within this directory, create a new text file containing the following markup:
layout()->content ?>Save this file as $APP_DIR/application/layouts/master.phtml. You’ll notice that the master layout also makes use of two additional assets—a CSS stylesheet and a logo image. These files need to be located in the application’s public area, so that they can be retrieved over HTTP by connecting clients. Accordingly, also create the $APP_DIR/public/css/ and $APP_DIR/public/images/ directories and copy over the necessary assets to these locations. You’ll find these assets in the code archive for this chapter, which can be downloaded from this book’s companion Web site at http://www.zf-beginners-guide.com/. Updating the Application Configuration File The next step is to update the global application configuration file, located at $APP_DIR/ application/configs/application.ini, with the location of the layouts directory and the name of the default layout to use when rendering application views. Open the file in a text editor, and add the following directives to the [production] section: resources.layout.layoutPath = APPLICATION_PATH "/layouts" resources.layout.layout = master And now, when you revisit the application index page, you should see your new layout in all its glory, as shown in Figure 2-6. Using a Custom Route Custom routes make it possible to map application URLs to specific modules, controllers, and actions, and are particularly useful when the Zend Framework’s default routes turn out to be inadequate or limiting. To illustrate, let’s say that you’d like the application URL /home to redirect users to the application index page. To create a custom route that maps this URL to the “default” module’s IndexController::indexAction, simply update the application configuration file at $APP_DIR/application/configs/application.ini and add the following route entry to the [production] section: resources.router.routes.home.route = /home resources.router.routes.home.defaults.module = default resources.router.routes.home.defaults.controller = index resources.router.routes.home.defaults.action = index A route entry typically contains route, module, controller, and action attributes. The route attribute specifies the URL pattern that the route should match, while the module, controller, and action attributes indicate which module, controller, and action should 37 38 Zend Framework: A Beginner’s Guide be used to fulfill the request. Each route also has a unique name (in this case, home), which serves as a shortcut for automatic URL generation inside views. After saving your changes, try visiting the URL http://square.localhost/home in your Web browser. If all is well, you should be presented with the application index page, as shown in Figure 2-6. TIP Route names are useful because you can use them with the url() view helper to automatically generate URLs inside views. For example, a call to $view>url(array(), 'home') inside a view script would automatically generate the URL string /home. Figure 2-6 The application index page, wrapped in a custom layout Chapter 2: Working with Models, Views, Controllers, and Routes Ask the Expert Q: A: Why are layouts stored at the global level and not the module level? With Web applications, it’s quite common for views in different modules to share the same common layout. Therefore, the Zend Framework’s default directory layout suggests placing layouts at the global, or application, level under $APP_DIR/application/layouts/, rather than under the $APP_DIR/application/modules/ hierarchy. That said, if your application is structured such that each module uses a different layout, you could relocate your layout files to the module level, and write a custom plug-in that dynamically changes the layout based on the module being accessed. You’ll find a more detailed discussion of how to do this in the links at the end of this chapter. Try This 2-2 Serving Static Content In previous sections, your activities have been limited to modifying the application’s existing controllers and views. However, as development progresses, you’ll find it necessary to begin creating new controllers, actions, and views to encapsulate additional functionality. This shouldn’t be any cause for alarm, though, because there’s a standard process you can follow whenever you need to add new functionality to a Zend Framework application. These steps are illustrated in the following sections, which will guide you through the process of adding a new StaticContentController and associated views that will be responsible for serving up static content pages such as “About Us” and “Services” pages. Defining Custom Routes The first step is to define a base route for static content pages. For simplicity, we’ll assume that all static content page URLs will be of the form /content/xx, where xx is a variable indicating the name of the content page. To set up a custom route to handle such URLs, add the following route definition to the application configuration file at $APP_DIR/application/configs/ application.ini: resources.router.routes.static-content.route = /content/:page resources.router.routes.static-content.defaults.module = default resources.router.routes.static-content.defaults.controller = static-content resources.router.routes.static-content.defaults.action = display This route is a little more complex than the one you saw earlier, because it contains a variable placeholder. The Zend Framework supports the use of variable placeholders, indicated by a preceding colon, in route patterns, and will automatically convert that segment of the request (continued) 39 40 Zend Framework: A Beginner’s Guide URL into a variable that can be accessed from within a controller. This means that if, for example, a client requested the URL /content/hello-world, the routing subsystem would automatically capture the URL segment “hello-world” and store it in a request variable named page. Defining the Controller Why do we need to capture the final segment of the URL and store it as a request variable? That question is answered in the next paragraph, but, until we get there, let’s create the “default” module’s StaticContentController, which, by convention, should be located at $APP_DIR/application/modules/default/controllers/StaticContentController.php. Create this file and fill it with the following code: getRequest()->getParam('page'); if (file_exists($this->view->getScriptPath(null) . "/" . $this->getRequest()->getControllerName() . "/$page." . $this->viewSuffix)) { $this->render($page); } else { throw new Zend_Controller_Action_Exception('Page not found', 404); } } } This controller exposes a single action, displayAction(), which is responsible for reading the page variable set up by the routing subsystem using the request object’s getParam() method. It then attempts to find a view script matching the value of this variable. If a matching view script is found, it is rendered and sent to the client. So, for example, if the URL requested by the client was /content/hello-world, the call to $request->getParam('page') would return the value “hello-world,” and the action would therefore attempt to render a view named $APP_DIR/application/modules/default/ views/scripts/static-content/hello-world.phtml. If no matching view can be found, a 404 exception is raised and propagated forward to the default exception handler, which formats it into a readable error page and displays it to the client. Chapter 2: Working with Models, Views, Controllers, and Routes Ask the Expert Q: A: Why do none of the class definitions in this chapter include a closing PHP tag? According to the Zend Framework coding standard, files that contain only PHP code should not include the closing tag ?>, as it can lead to header problems with the HTTP response if the text editor you’re using automatically adds a new line to the end of your files. For more information on the Zend Framework coding standard, refer to the links at the end of this chapter. Defining the View The next (and final) step is to create views corresponding to the controllers and actions created in the previous step. In most cases, the view name will be based on the controller and action name. However, in this particular case, the action is a generic display action that receives the view name as a variable from the URL request. So, to define a static “Services” page accessible at the URL /content/services, create a file at $APP_DIR/application/modules/default/views/ scripts/static-content/services.phtml and fill it with some content, as in the following example:Services
We provide a number of services, including procurement, valuation and mail-order sales. Please contact us to find out more.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Similarly, to define an “About Us” page accessible at the URL /content/about-us, create a file named $APP_DIR/application/modules/default/views/scripts/static-content/about-us.phtml and fill it with some content, as in the following example:About Us
We have been in the business of stamp procurement and sales since 1927, and are internationally known for our expertise and industry-wide 41 42 Zend Framework: A Beginner’s Guide network. We encourage you to find out more about us using the links above.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
If you now try visiting the URL http://square.localhost/content/about-us or http:// square.localhost/content/services through your Web browser, you should see the static pages above. Figure 2-7 illustrates a sample of the result. Updating the Master Layout As a final step, you can update the navigation links in the application’s main menu to reflect the new static content pages using the url()helper method. To do this, update the master layout, at $APP_DIR/application/layouts/master.phtml, with the changes highlighted in bold: Chapter 2: Figure 2-7 Working with Models, Views, Controllers, and Routes A static content page
layout()->content ?>Here, the url() helper method is used to automatically generate URLs for the /home and /content/services routes. This helper accepts two parameters—an array of variable-value pairs 43 44 Zend Framework: A Beginner’s Guide to be interpolated into the URL string, and the name of the route—and then generates URL strings corresponding to these parameters when the view is rendered. And if you revisit the application index page, you’ll see that the “Home” and “Services” main menu links are now active and, when clicked, display the correct content. NOTE It’s worth pointing out that there is actually an easier way to serve static pages in a Zend Framework application: You can simply place them in the $APP_DIR/public/ directory as HTML files, and link to them manually. However, these pages would not be able to use any of the Zend Framework’s built-in features, such as global layouts, routes, caching, or security, and, as such, this approach is not usually recommended. Summary Now that you are at the end of this chapter, you should have a much deeper understanding of what goes into making a Zend Framework application tick. This chapter began with an introduction to the Model-View-Controller design pattern, explaining what models, views, and controllers are and how they interact with each other to produce a logically separated, layered application. It also introduced some additional important concepts, such as modules, routes, layouts, and the front controller, and explained the basics of how user requests are routed and handled in an application context. Finally, it applied all this theory to the real world, by beginning the process of customizing and enhancing the simple test application created at the end of the previous chapter. The SQUARE example application is still in its early stages: It has a customized layout, knows how to deal with modules, and can serve up static content. This might not seem like much, but implementing even this very basic functionality will have helped you understand the main principles of MVC-based development with the Zend Framework and created a solid foundation for the more advanced material in subsequent chapters. To learn more about the topics discussed in this chapter, consider visiting the following links: L Wikipedia’s discussion of Model-View-Controller architecture, at http://en.wikipedia.org/wiki/Model-view-controller L The basics of the Zend Framework’s MVC implementation, at http://framework.zend.com/docs/quickstart L The Zend Framework router, at http://framework.zend.com/manual/en/zend.controller.router.html L The Zend Framework front controller, at http://framework.zend.com/manual/en/zend.controller.front.html L The Zend Framework layout engine, at http://framework.zend.com/manual/en/zend.layout.html Chapter 2: Working with Models, Views, Controllers, and Routes L A discussion of building a modular application with the Zend Framework (Jeroen Keppens), at http://blog.keppens.biz/2009/06/create-modular-application-with-zend.html L A discussion of using per-module layouts in the Zend Framework wiki and forums, at http://framework.zend.com/wiki/display/ZFPROP/Zend_Layout and http://www .zfforums.com/zend-framework-components-13/model-view-controller-mvc-21/ modules-layouts-2645.html L The Zend Framework directory layout, at http://framework.zend.com/wiki/display/ZFPROP/Zend+Framework+Default+ Project+Structure+-+Wil+Sinclair L The Zend Framework coding standard, at http://framework.zend.com/manual/en/coding-standard.html 45 This page intentionally left blank Chapter 3 Working with Forms 47 48 Zend Framework: A Beginner’s Guide Key Skills & Concepts ● Learn to programmatically create forms and form elements ● Understand how to filter and validate user input ● Protect your forms from Cross-Site Request Forgery (CSRF) attacks and spambots ● Control the appearance of form elements and error messages ● Create a working contact form I n the previous chapter, you learned how the Zend Framework implements the Model-ViewController pattern, and you looked underneath the hood of the example application to see how it works. You also started to flesh out the example application by adopting a modular directory structure, adding a master layout, and creating custom controllers, views, and routes for static content. Now, while you can certainly use the Zend Framework to serve up static content, doing so is a lot like using a bulldozer to knock over a tower of plastic blocks. There’s nothing stopping you from doing it, but it’s not really what the bulldozer was intended for, and you’re liable to face hard questions about why there’s a bulldozer in your living room in the first place! The Zend Framework is similar, in that it’s intended to provide robust, elegant, and extensible solutions to complex Web application development tasks. The more complex the task, the better suited it is to the power and flexibility of the framework…and the more fun you’ll have knocking it down! In this chapter, you’ll learn how the Zend Framework can simplify one of the most common application development tasks: creating Web forms and processing user input. You’ll also apply this knowledge to add some interactivity to the SQUARE example application, by creating a contact form. So without further ado, let’s jump right in! Understanding Form Basics To demonstrate how the Zend Framework can help you with forms, a brief yet illustrative example will suffice. If you’re like most PHP developers, chances are that you’ve written a form-processing script like the following one at some point in your career: Chapter 3: Working with FormsCreate Item
Contact
form; ?> (continued) 87 88 Zend Framework: A Beginner’s Guide And here’s the success view, which by convention should be stored at $APP_DIR/ application/modules/default/views/scripts/contact/success.phtml:Success
messages); ?> Updating the Master Layout All that’s left now is to update the navigation links in the application’s main menu to reflect the new inquiry form using the url()helper method. To do this, update the master layout, at $APP_DIR/application/layouts/master.phtml, with the changes highlighted in bold: