Professional Documents
Culture Documents
Zend Framework PDF
Zend Framework PDF
Zend Framework PDF
Oleg Krivtsov
This book is for sale at http://leanpub.com/using-zend-framework-2
This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools
and many iterations to get reader feedback, pivot until you have the right book and build
traction once you do.
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
About this Leanpub Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Why to Read this Book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
Zend Framework Explained . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . i
See ZF2 Wider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
ZF2 Book for Beginners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Structure of the Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ii
Learn ZF2 by Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iii
Book Site . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
Book Reviews . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
Testimonials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iv
Your Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Affiliate Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi
4. Model-View-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
4.1 Get the Hello World Example from GitHub . . . . . . . . . . . . . . . . . . . . . 72
4.2 Separating Business Logic from Presentation . . . . . . . . . . . . . . . . . . . . 73
4.3 Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
4.3.1 Base Controller Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
4.3.2 Retrieving Data from HTTP Request . . . . . . . . . . . . . . . . . . . . . 78
4.3.3 Retrieving GET or POST Variables . . . . . . . . . . . . . . . . . . . . . . 79
4.3.4 Putting Data to HTTP Response . . . . . . . . . . . . . . . . . . . . . . . . 80
4.4 Variable Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
4.5 Controller Registration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.6 When to Create a New Controller? . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.7 Controller Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
4.7.1 Writing Own Controller Plugin . . . . . . . . . . . . . . . . . . . . . . . . 84
4.8 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
4.9 View Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
4.10 View Template Names & View Resolver . . . . . . . . . . . . . . . . . . . . . . . 91
4.11 Disabling the View Rendering . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
4.12 Error Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
4.13 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.14 Model Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
4.14.1 Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
4.14.2 Value Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
4.14.3 Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
4.14.4 Factories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
4.14.5 Repositories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
4.15 Determining the Correct Model Type . . . . . . . . . . . . . . . . . . . . . . . . 103
4.16 Skinny Controllers, Fat Models, Simple Views . . . . . . . . . . . . . . . . . . . . 104
4.16.1 Skinny Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
4.16.2 Fat Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
4.16.3 Simple View Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
4.17 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474
Buttons & Glyphicons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
Customizing Bootstrap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478
You will receive all newer versions of this book for free as they appear.
You can have a brief introduction to the book by watching the intro video² on YouTube!
• HTML (Hyper Text Markup Language) – used for creating web pages that can be displayed
in a web browser.
• CSS (Cascading Style Sheets) – used for defining the look and feel of a web page, like font
size or background color.
• JavaScript – a client-side scripting language used for making a web page more interactive.
For learning HTML, CSS and JavaScript, a good starting point is W3Schools Tutorials⁵.
⁶https://github.com/olegkrivtsov/using-zend-framework-2-book
Preface iv
using-zend-framework-2-book
blog
helloworld
formdemo
...
Book Site
The “Using Zend Framework 2” book has a dedicated web site using-zend-framework-2-
book.com⁷. This is the central place where you can find all the information about the book:
• intro videos,
• tutorials,
• code examples,
• reader reviews & feedback,
• announcements,
• and more.
Book Reviews
Richard Holloway: “This will likely improve your overall understanding of modern PHP”
Richard Holloway is an organiser of PHP Hampshire⁸, which is a recognised PHP user group:
“Many people struggle to get into Zend Framework 2 but this book does a good job of taking you
over that initial steep learning curve and providing enough information to get you started on
building websites.”
The complete review is available by this link⁹.
Testimonials
Below, there are some selected testimonials from satisfied readers of the book:
“I’m a very satisfied reader of your book (using zend framework 2”: it details many important
notions, but it never miss to give the big picture: great work!” ∼Francesco
“I’ve recently bought your book “Using Zend Framework 2” and I think this is the best available
resource to get started with ZF2.” ∼Janusz K
“I purchased your book on Zend framework 2 some days ago and I thought i should congratulate
you for your amazing work. I tried another books and methods to learn zf2, but definitely your
book is the only that works for me.”
⁷http://using-zend-framework-2-book.com
⁸http://phphants.co.uk
⁹http://richardjh.org/blog/book-review-using-zend-framework-2/
Preface v
Zf2 is something complex to me and your book is making it easier. I really like the detailed
explanations of the concepts and examples you use.” ∼Welington*
“I am one of (hopefully many) people who bought and read your ‘using ZF2’ book. […] Your book
taught me not only many new concepts, but also why these concepts came to be and (as a personal
comfort to me) that almost half of these new features (or rather: ways of thinking) were things I
was already doing, albeit in some other, non-object oriented way; I just never realised it. Having
things explained by someone who obviously knows what he is talking about was a great help to
me, and while I have yet to reach any important milestone, I feel I understand what I have to do
much better now and I am much more confident that I will eventually successfully ‘refresh’ my
hopelessly outdated projects.” ∼J.B.
Your Feedback
Thank you for reading this book and helping to make it better. You are encouraged to point
out errors, make suggestions and critical remarks. You can write the author a message through
the dedicated Forum¹⁰. Alternatively, you can contact the author through his E-mail address
(olegkrivtsov@gmail.com). Your feedback is highly appreciated.
Affiliate Program
Now the “Using Zend Framework 2” book is part of the Leanpub Affiliate Program¹¹. This means
that anyone can earn 50% of profit for advertising the book on his/her web site. So, if you like this
book and want to earn money by promoting it on your web page or blog, feel free to participate!
How does this Work?
Affiliate Link
¹⁰https://leanpub.com/using-zend-framework-2/feedback
¹¹http://blog.leanpub.com/2014/03/introducing-the-leanpub-affiliate-program.html
¹²https://leanpub.com/
Preface vi
You can read more about the Leanpub affiliate program terms on this page¹³.
Acknowledgements
Thanks to Edu Torres, a 2D artist from Spain, for making the cover and an illustration for this
book, and for making a design for the book’s web site. Also thanks to Moriancumer Richard Uy
and Charles Naylor for helping the author to find and fix the mistakes in the text.
The author would like to thank Richard Holloway (an organiser of PHP Hampshire¹⁴, which is
a recognised PHP user group in South England) for reviewing the book. Richard’s review¹⁵ is
really useful for determining the proper development direction for this book.
¹³http://blog.leanpub.com/2014/03/introducing-the-leanpub-affiliate-program.html
¹⁴http://phphants.co.uk
¹⁵http://richardjh.org/blog/book-review-using-zend-framework-2/
1. Introduction to Zend Framework 2
In this chapter we’ll learn about Zend Framework 2, its main principles and components. We’ll
also compare Zend Framework 2 with other PHP frameworks.
• Develop your web site much faster than when you write it in pure PHP. ZF2 provides
many components that can be used as a code base for creating your site.
• Easier cooperation with other members of your site building team. Model-View-Controller
pattern used by ZF2 allows to separate business logics and presentation layer of your web
site, making its structure consistent and maintainable.
• Scale your web site with the concept of modules. ZF2 uses the term module, allowing to
separate decoupled site parts, thus allowing to reuse models, views, controllers and assets
of your web-site in other works.
• Accessing database in an object-oriented way. Instead of directly interacting with the
database using SQL queries, you can use Doctrine Object-Relational Mapping (ORM) to
manage the structure and relationships between your data. With Doctrine you map your
database table to a PHP class (also called an entity class) and a row from that table is
mapped to an instance of that class. Doctrine allows to abstract of database type and make
code easier to understand.
Introduction to Zend Framework 2 2
• Write secure web sites with ZF2-provided components like form input filters and valida-
tors, HTML output escapers and cryptography algorithms, human check (Captcha) and
Cross-Site Request Forgery (CSRF) form elements.
Figure 1.1. Zend Framework sits on top of PHP and contains reusable components for building your web site
1.2 License
Zend Framework 2 is licensed under BSD-like license, allowing you to use it in both commercial
and free applications. You can even modify the library code and release it under another name.
The only thing you cannot do is to remove the copyright notice from the code. If you use Zend
Framework 2, it is also recommended that you mention about it in your site’s documentation or
on About page.
Below, the Zend Framework 2 license text is presented. As you can see, it is rather short.
Introduction to Zend Framework 2 3
* Neither the name of Zend Technologies USA, Inc. nor the names of
its contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.
• BBC The British Broadcasting Corporation (BBC) is a British public service broadcasting
statutory corporation ².
¹http://framework.zend.com/
²BBC – From Wikipedia, the free encyclopedia http://en.wikipedia.org/wiki/BBC
Introduction to Zend Framework 2 4
• BNP Paribas Banque BNP Paribas is a French bank and financial services company,
European leader in global banking and financial services and is one of the six strongest
banks in the world according to the agency Standard & Poor’s ³.
³BNP Paribas – From Wikipedia, the free encyclopedia http://en.wikipedia.org/wiki/BNP_Paribas
Introduction to Zend Framework 2 5
Date Version
March 12, 2014 ZF 2.3.0
October 31, 2013 ZF 2.2.5
August 26, 2013 ZF 2.2.4
August 21, 2013 ZF 2.2.3
June 24, 2013 ZF 2.2.2
June 12, 2013 ZF 2.2.1
May 15, 2013 ZF 2.2.0 Stable
May 10, 2013 ZF 2.2.0rc3
May 6, 2013 ZF 2.2.0rc2
May 1, 2013 ZF 2.2.0rc1
April 17, 2013 ZF 2.1.5
April 17, 2013 ZF 2.1.5
⁴http://framework.zend.com/blog
Introduction to Zend Framework 2 6
Date Version
March 14, 2013 ZF 2.1.4
February 21, 2013 ZF 2.1.3
February 20, 2013 ZF 2.1.2
February 06, 2013 ZF 2.1.1
January 30, 2012 ZF 2.1.0 Stable
January 30, 2012 ZF 2.0.7
December 19, 2012 ZF 2.0.6
November 25, 2012 ZF 2.0.5
November 20, 2012 ZF 2.0.4
October 17, 2012 ZF 2.0.3
September 21, 2012 ZF 2.0.2
September 20, 2012 ZF 2.0.1
September 05, 2013 ZF 2.0.0 Stable
As you can see from the table above, Zend Framework 2 is being constantly developed and
updated. Its developers listen to user community’s feedback and strive to keep the framework
well polished and ready for use in production systems. For the detailed list of changes between
the versions of ZF2, you can refer to the Changelog⁵ page.
1.5 Distributions
You can download the source code of Zend Framework 2 from the official site⁶ (presented in
figure 1.4) to become familiar with its structure and components.
ZF2 can be downloaded in two types: full and minimum. A full-size archive contains a complete
set of components plus demos; its size is about 3 Mb. Mimimum-size distribution contains library
components only, and its size is 3 Mb (also !).
In most cases you won’t need to download the code of Zend Framework 2 manually.
Instead, you will install it with Composer dependency manager. We will become
familiar with Composer later in Chapter 2.
Documentation. Documentation for ZF2 is located by this address⁷. It includes beginner’s tutorial,
programmers manual, and API reference (API stands for Application Programming Interface).
Community Forums. Zend Framework 2 has dedicated user groups all over the world. The list of
groups can be found on this page⁸.
Webinars are video tutorials covering various ZF2 features. Complete list of webinars can be
⁷http://framework.zend.com/learn/
⁸http://framework.zend.com/participate/user-groups
Introduction to Zend Framework 2 8
• Zend Framework 2 Patterns. Tells about what’s new in ZF2 compared to the first version
of the framework. It also shows how namespaces, class autoloading, and exceptions are
used in ZF2.
• Getting started with ZF2. Teaches you the basics of developing ZF2-based applications, like
creating controllers and views, manipulating services and listening to events.
• The MVC architecture of ZF2. Teaches the MVC (Model View Controller) architecture of
Zend Framework 2.
Training Classes with live instructors can be accessed by this link¹⁰. Here you can learn ZF2 by
doing exercises, mini-projects and developing real code.
Certification Program. Allows you to become a Zend Certified Engineer (ZCE), thus making it
easier to improve your skills as an architect and to find a job in a competitive PHP job market.
ZF2 utilizes URL rewriting extension for redirecting web-users to entry script of your site
(you have to enable Apache’s mod_rewrite module.) You may also need to install some PHP
extensions, like memory caching extension, to improve ZF2 performance. This can be a difficulty
when using a shared hosting and requires that you have admin rights on your server.
So, if you are planning to use ZF2 on a shared web hosting, think twice. The best server to install
ZF2 on is a server with the latest version of PHP and with shell access to be able to execute
Composer, install PHP extensions and provide an ability to schedule console PHP scripts by
cron.
If your company manages its own server infrastructure and can afford upgrading PHP version
to the latest one, you can install ZF2 on your private server.
An acceptable alternative is installing a ZF2-based web application to a cloud-based hosting
service, like Amazon Web Services¹². Amazon provides Linux server instances as a part of EC2
service. EC2 is rather cheap, and it provides a free usage tier¹³ letting you try it for free for one
year.
1.9 Security
Zend Framework 2 follows the best practices to provide you with a secure code base for your
web sites. ZF2 creators release security bug fixes on a regular basis. You can incorporate those
fixes with a single command through Composer dependency manager.
ZF2 provides many tools allowing to make your web site secure:
• Routing allows to define strict rules on how an acceptable page URL should look like. If
a site user enters an invalid URL in a web browser’s navigation bar, he is automatically
redirected to an error page.
• Access control lists and Role-Based Access Control (RBAC) allow to define flexible rules
for granting or denying access to certain resources of your web site. For example, an
anonymous user would have access to your index page only, authenticated users would
have access to their profile page, and the administrator user would have access to site
management panel.
• Form validators and filters ensure that no unwanted data is collected through web forms.
Filters, for example, allow to trim strings or strip HTML tags. Validators are used to
check that the data that had been submitted through a form conforms to certain rules.
For example, E-mail validator checks that an E-mail field contains valid E-mail address,
and if not, raises an error forcing the site user to correct the input error.
• Captcha and CSRF (Cross-Site Request Forgery) form elements are used for human checks
and hacker attack prevention, respectively.
• Escaper component allows to strip unwanted HTML tags from data outputted to site pages.
• Cryptography support allows you to store your sensitive data (e.g. credentials) encrypted.
¹²http://aws.amazon.com/
¹³http://aws.amazon.com/free/
Introduction to Zend Framework 2 10
1.10 Performance
ZF2 creators have claimed to do a great job to improve performance of the ZF2 comparing to the
first version of the framework.
Lazy class autoloading. Classes are loaded once needed. You don’t have to write require_once
for each class you want to load. Instead, the framework automatically discovers your classes
using the autoloader feature. Autoloader uses either class map or class naming conventions to
find and load the needed class.
Efficient plugin loading. In ZF2, plugin classes are instantiated only when they really need to.
This is achieved through service manager (the central repository for services of your application).
Support of caching. PHP has several caching extensions (like APC or Memcache) that can be used
to speed-up ZF2-based web sites. Caching saves frequently used data to memory to speed-up data
retrieval. For example, a Zend Framework 2 application consists of many files which require time
for PHP interpreter to process the files each time you open the page in the browser. You can use
APC extension to cache precompiled PHP opcodes to speed up your site. Additionally, you can
use the ZF2’s event manager to listen to dispatching events, and cache HTML response data with
Memcache extension.
• Aspect Oriented Design pattern. In ZF2, everything is event-driven. When a site user
requests a page, an event is generated (triggered). A listener (or observer) can catch event
and do something with it. For example, a router service parses the URL and determines
what controller class to call. When the event finally reaches the page renderer, an HTTP
response is generated and the user sees the web page.
• Singleton pattern. In ZF2, there is the service manager object which is the centralized
registry of all services available in the application. Each service exists in a single instance
only.
• Strategy pattern. While browsing ZF2 documentation or source code, you’ll encounter the
word strategy for sure. A strategy is just a class encapsulating some algorithm. And you
can use different algorithms based on some condition. For example, the page renderer has
several rendering strategies, making it possible to render web pages differently based on
Accept HTTP header (the renderer can generate an HTML page, a JSON response, an RSS
feed etc.)
• Adapter pattern. Adapters allow to adapt a generic class to concrete use case. For example,
Zend\Db component provides access to database in a generic way. Internally, it uses
adapters for each supported database (SQLite, MySQL, PostgreSQL and so on.)
• Factory pattern. You can create an instance of a class using the new operator. Or you can
create it with a factory. A factory is just a class encapsulating creation of other objects.
Factories are useful, because they simplify dependency injection - you can provide a
generic factory interface instead of hard-coding the concrete class name. This simplifies
the testing of your model and controller classes.
1.12 Components
ZF2 developers believe that the framework should be a set of decoupled components with
minimum dependencies on each other. This is how ZF2 is organized.
The idea was to let you use some selected ZF2 components alone, even if you write your site with
another framework. This becomes even easier, keeping in mind that each component of ZF2 is
a Composer-installable package, so you can easily install any ZF2-component together with its
dependencies through a single command.
The table 1.2 lists ZF2 components with their brief description. As you can see from the table,
we can divide all ZF2 components into the following groups ¹⁴:
• Core Components. These components are used (either explicitly or implicitly) in almost any
web application. They provide the base functionality for class auto-loading, for triggering
events and listening to them, for loading modules, for organizing the code within a module
in conformance to the Model-View-Controller pattern, for creating console commands and
more.
• Web Forms. Forms are the way of collecting user-entered data on web pages. A form
usually consists of elements (input fields, labels, etc). For checking and filtering the user-
entered data, filters and validators are utilized.
¹⁴These component groups are not an official classification, but the author’s personal point of view.
Introduction to Zend Framework 2 12
• User Management. This important group includes components for providing user authen-
tication, authorization and access control. Internally, these are based on the PHP feature
called sessions.
• Presentation Utilities. In this group, we can put components implementing useful web page
elements: navigation bars, progress bars, etc.
• Persistence. This group contains components whose purpose is to convert in-memory data
into formats storable on a disk media (XML, JSON, a database, etc.), and vice-versa.
• Testing and Debugging. In this (small) group, there are several components for logging,
testing and debugging your web site.
• Web Services. This group contains components that implement various protocols for
accessing your web site programmatically (e.g. RSS, XML RPC, SOAP and so on).
• Mail. Useful components for composing E-mail messages and sending them with different
transports.
• Miscellaneous. Various components that cannot be put in any other group.
Zend\Log Component for general purpose logging. Logging site operations is used to
troubleshoot possible errors with your site in development and production
environments.
Zend\Test Base classes for unit testing and test bootstrapping.
Web Forms
Zend\Filter Provides a set of commonly needed data filters, like string trimmer. This
component is covered in Chapter 8.
Zend\Form Web form data collection, filtering, validation and rendering. This
component is covered in Chapter 7 and Chapter 10.
Zend\InputFilter Provides an ability to define form data validation rules. This component is
covered in Chapter 7.
Zend\Validator Provides a set of commonly needed validators. This component is covered
in Chapter 9.
Web Services
Zend\Feed Provides functionality for consuming RSS and Atom feeds.
Zend\Ldap Provides support for Lightweight Directory Access Protocol (LDAP)
operations including but not limited to binding, searching, and
modifying entries in an LDAP directory.
Zend\Server Client-server generic class interfaces.
Zend\Soap Implementation of Simple Object Transfer Protocol (SOAP).
Zend\XmlRpc Used for creation of web-services utilizing XML Remote Procedure
Call (RPC) protocol.
Mail
Zend\Mail Provides generalized functionality to compose and send both text
and MIME-compliant multi-part E-mail messages. This component is
covered in Chapter 7.
Zend\Mime Support class for Multipurpose Internet Mail Extensions (MIME)
messages.
Miscellaneous
Zend\Config Provides a nested object property based user interface for
accessing the configuration data within application code.
Zend\Crypt Contains implementation of symmetric and asymmetric cryptographic
algorithms.
Zend\File PHP class file discovery.
Zend\I18n Support of multi-lingual web sites.
Zend\Math Big integer support and some auxiliary math functionality.
Zend\Memory This class encapsulates memory management operations, when PHP
Introduction to Zend Framework 2 15
ZendService\Rackspace API to manage the Rackspace services Cloud Files and Cloud
Servers.
ZendService\ReCaptcha Provides API for the reCAPTCHA²⁰ service, used to digitize books
and (as a side product) generate CAPTCHA images.
ZendService\SlideShare Access to the SlideShare²¹ services for hosting slide shows online.
ZendService\StrikeIron API for accessing the StrikeIron²² web services – Cloud-Based Data
Quality & Enhancement Solutions.
ZendService\Technorati Provides interface for using Technorati²³, which is a place storing
individual reviews, essays, interviews, and news stories.
ZendService\Twitter Provides API to Twitter²⁴ microblogging service.
ZendService\Windows Azure Provides API for accessing Microsoft Windows Asure²⁵ cloud web
hosting platform.
Because the API to above mentioned web resources may be changed by their vendors
without a notice, those components are not part of the “core” Zend Framework 2
distribution. By the same reason, those components are not discussed deeply in this
book.
1.14.2 ZFTool
In Zend Framework 1, you used ZFTool for creating the application, adding layouts and
controllers. In ZF2, you create your new applications by downloading Zend Skeleton Application
available on GitHub. By the way, in ZF2 you can install a component called ZFTool, and it can
also create the skeleton application or a module for you.
²⁰http://www.google.com/recaptcha
²¹http://www.slideshare.net/
²²http://www.strikeiron.com/
²³http://technorati.com/
²⁴http://twitter.com
²⁵http://www.windowsazure.com/
Introduction to Zend Framework 2 17
1.14.3 Modules
In Zend Framework 1, your application was monolithic (although there was a concept of module).
In ZF2, everything is a module. The skeleton application has single Application module by
default. Each module may contain configuration, models, views, controllers and the assets (e.g.
database tables, files etc.) A module can call classes from other modules. You can install third-
party modules and reuse your own modules across applications.
1.14.5 Namespaces
In ZF1, you worked with long underscore-separated class names like Zend_Controller_Action.
In ZF2, PHP namespaces are used, so instead you’ll have something like Zend\MVC\Controller\AbstractActionC
which can be easier to type with auto-completion feature and easier to understand. Namespaces
allow to define short class names (aliases) and use them instead of full names. By convention,
namespaces are mapped to directory structure, making it easier to perform class autoloading.
1.14.6 Configuration
In ZF1, you had an application-level INI config file. In ZF2, each module has its configuration
file in a form of PHP array. At application level, module configurations are finally merged into
a single large nested PHP array.
frameworks in some way, we can use Google Trends³⁰ site, which allows to track count of a
keyword search queries over time. For example, if you enter “Zend Framework, CakePHP, Yii,
CodeIgniter, Symfony” into the query field, you will get the graph as shown in figure 1.5.
As you can see from the graph, Zend Framework (blue line) has reached its popularity peak
by 2010, and since then it has slowly lost its popularity. However, ZF is still one of the strong
players on the market. On the other hand, Cake PHP, Symfony, CodeIgniter and Yii framework
are becoming highly popular nowadays.
Let’s also look at the relative popularity of ZF1 and ZF2 by typing “Zend Framework, Zend
Framework 2” into the search query field. The result is shown in figure 1.6.
As we can see, Zend Framework 2 (the red line) was released not so long ago, and has yet to
become popular. The author believes that ZF2 has all the necessary qualities to become popular
over time.
³⁰http://www.google.ru/trends/
Introduction to Zend Framework 2 19
Figure 1.6. Popularity of Zend Framework 2 comparing to the first version. Powered by Google Trends
If you are familiar with one of the above mentioned frameworks, in table 1.4 you can find the
comparison of features provided by those PHP frameworks. Capabilities of Zend Framework 2
are marked with bold.
• Zend Framework 2 can be considered as one of the most mature and established PHP
frameworks on the market. This allows to be sure that ZF2 creators won’t stop to update
and support it unexpectedly.
• The major way for installing ZF2 is through Composer dependency manager. Symfony 2
is similar to ZF2 in this sence. Other PHP frameworks utilize the conventional installation
from an archive.
• ZF2 (as Symfony 2) has bad compatibility with shared hostings because of the Composer-
based installation method and strict PHP version requirements. So, if you need to install
your website to a shared hosting, you probably need to contact the hosting’s support for
installation instructions.
• ZF2 provides the sophisticated configuration methods, so you can fine-tune and override
any aspect of its work. Some other PHP frameworks prefer the “conventions over
configuration” way, making it easier for newbies to start developing websites.
• For the presentation layer, ZF2 suggests the use of Twitter Bootstrap CSS Framework by
default. But this does not limit you on using any other CSS frameworks.
• In ZF2, you can use several database access methods. And like in most PHP frameworks,
you can benefit from using an object-oriented way of managing the database (with
Doctrine ORM). Additionally, you can use Doctrine migrations mechanism for managing
the database schema in a standardized way.
• Comparing to other frameworks, ZF2 provides good capabilities for unit- and functional
testing (based on PHPUnit framework). This makes it possible to automate the testing of
Introduction to Zend Framework 2 21
the code you write. For testing the database functionality, you can use Doctrine-provided
fixture mechanism.
1.16 Summary
A PHP framework is a library, giving you the code base and defining consistent ways of creating
web applications. Zend Framework 2 is a modern web development framework created by
the Zend Company, the vendor of PHP language. It provides the developers with outstanding
capabilities for building scalable and secure web sites. ZF2 is licensed under BSD-like license and
can be used for free in both commercial and open-source applications.
ZF2 is updated frequently, making your sites more resistant to vulnerabilities and security holes.
On its official site, ZF2 provides the documentation (tutorials and API reference), webinars,
community forums and commercial support services, like trainings and certification program.
ZF2 creators strive to improve the performance of ZF2 in comparison to the first version of the
framework. Among the features that contribute into the performance of ZF2, there are lazy class
autoloading and support of caching.
On the market, Zend Framework is not the only web development framework. ZF2 is positioned
as a good framework for corporate applications because of its pattern-based design and
scalability. However, you can use ZF2 in any-sized web application with great success.
³¹https://leanpub.com/using-zend-framework-2/feedback
2. Zend Skeleton Application
Zend Framework 2 provides you with the so called “skeleton application” to make it easier to
create your new applications from scratch. In this chapter, we will download and install the
skeleton application which can be used as a base for creating your web sites. It is recommended
that you refer to Appendix A before reading this chapter to get your development environment
configured.
To download the code archive on a Linux machine without graphical interface, you
can use the wget command, like this:
wget https://github.com/zendframework/ZendSkeletonApplication/archive/master.zip
Unpack the downloaded ZIP archive to some directory. If you are programming in Linux, it is
recommended to unpack it in your home directory:
cp /path/to/downloaded/archive/ ZendSkeletonApplication-master.zip ~
cd ~
unzip ZendSkeletonApplication-master.zip
The commands above will copy the file ZendSkeletonApplication-master.zip archive that
you’ve downloaded to your home directory, then unpack the archive.
If you are using Windows, you can place the skeleton app directory anywhere in your
system, but ensure that file and directory access rights are sufficient for your web server
to read and write the directory and its files. Actually, if you don’t put your files to
C:\Program Files, everything should be OK.
¹https://github.com/zendframework/ZendSkeletonApplication
Zend Skeleton Application 23
Your web site will have a single entry point, index.php, because this is more secure than
allowing anyone to access all your PHP files.
Inside of the public directory, you can also find .htaccess file. Its main purpose is to define URL
rewriting rules, but you also can use it to define access rules for your web-site. For example, with
.htaccess you can grant access to your web-site from your own IP address only, or use HTTP
authorization to request users for username and password.
The public directory contains several subdirectories also publicly accessible by web-users:
• and js subdirectory stores publicly accessible JavaScript files used by your web-pages.
Typically, files of jQuery² library are placed here, but you can put your own JavaScript
files here, too.
Because the Zend Skeleton Application is stored on GitHub, inside of the directory structure,
you can find hidden .gitignore and .gitmodules files. These are GIT³ version control system’s
files. You can ignore them (or even remove them if you do not plan to store your code in a GIT
repository).
Because we will later use the skeleton as the base for our Hello World application, let’s rename
the ZendSkeletonApplication-master directory into helloworld, which also sounds shorter. In
Linux, you can do that with the following command:
mv ZendSkeletonApplication-master helloworld
²http://jquery.com/
³http://git-scm.com/
⁴http://getcomposer.org/
⁵https://packagist.org/
Zend Skeleton Application 26
{
"name": "zendframework/skeleton-application",
"description": "Skeleton Application for ZF2",
"license": "BSD-3-Clause",
"keywords": [
"framework",
"zf2"
],
"homepage": "http://framework.zend.com/",
"require": {
"php": ">=5.3.3",
"zendframework/zendframework": ">2.2.0rc1"
}
}
What is JSON?
JSON (JavaScript Object Notation), is a text-based file format used for human-readable
representation of simple structures and nested associative arrays. Although JSON
originates from Java, it is used in PHP and in other languages, because it is convenient
for storing configuration data.
In that file, we see some basic info on the skeleton application (its name, description, license,
keywords and home page). You will typically change this info for your future web-sites. This
information is optional, so you can even safely remove it, if you do not plan to publish your web
application on Packagist catalog.
What is interesting for us now is the require key. The require key contains the dependencies
declarations for our application. We see that we require PHP engine version 5.3.3 or later and
Zend Framework 2.2.0 Release Candidate 1 or later.
The information contained in composer.json file is enough to locate the dependencies, download
and install them into the vendor subdirectory. Let’s finally do that by typing the following
commands from your command shell (replace APP_DIR placeholder with your actual directory
name):
cd APP_DIR
php composer.phar self-update
php composer.phar install
The commands above will change your current working directory to APP_DIR, then self-update
the Composer to the latest available version, and then install your dependencies. By the way,
Composer does not install PHP for you, it just ensures PHP has an appropriate version, and if
not, it will warn you.
Zend Skeleton Application 27
If you look inside the vendor subdirectory, you can see that it now contains a lot of files. Zend
Framework 2 files can be found inside the APP_DIR/vendor/zendframework/zendframework/library
directory (figure 2.3). Here you can encounter all the components that we described in Chapter
1 (Authentication, Barcode, etc.)
Currently, we have the skeleton application inside of home folder. To let Apache know about it,
we need to edit the virtual host file.
Let’s now edit the default virtual host file to make it look like below (this example is applicable
to Apache v.2.2):
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /home/username/helloworld/public
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /home/username/helloworld/public/>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order allow,deny
allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
</VirtualHost>
Zend Skeleton Application 29
Line 1 of the file makes Apache to listen to all (*) IP addresses on port 80.
Line 2 defines the web master’s E-mail address. If something bad happens to the site, Apache
sends an alert E-mail to this address. You can enter your E-mail here.
Line 4 defines the document root directory (APP_DIR/public). All files and directories under
the document root will be accessible by web-users. You should set this to be the absolute
path to skeleton application’s public directory. So, the directories and files inside public (like
index.php, css, js, etc.) will be accessible, while directories and files above public directory
(like config, module, etc.) wont’ be accessible by web users, which enhances the security of the
web site.
Lines 5-8 define default access rules for directories. These rules are rather strict. The Options
FollowSymLinks directive allows Apache to follow symbolic links (in Linux, a symbolic links is
an analog of a shortcut in Windows). The AllowOverride None directive forbids overriding any
rules using .htaccess files.
Lines 9-14 define rules for the document root directory (APP_DIR/public). These rules override
the default rules mentioned above. For example, the AllowOverride All directive allows to
define any rules in .htaccess files. The Order allow,deny and allow from all control a three-
pass access control system, effectively allowing everyone to visit the site.
Line 16 defines the path to error.log file, which can be used to troubleshoot possible errors
occurring in your site code. Line 23 defines the logging level to use (the warn means that warnings
and errors will be written to log).
Lines 18-19 are comments and ignored by Apache. You mark comments with the hash (#)
character.
Zend Framework 2 utilizes Apache’s URL rewriting module for redirecting web-users
to entry script of your web-site. Please ensure that your web-server has mod_rewrite
module enabled. For instructions on how to enable the module, please refer to Appendix
A.
After editing the config file, do not forget to restart Apache to apply your changes.
In the Run Configuration page, it is recommended that you specify the way you run the web site
(Local Web Site) and web site URL (http://localhost). Keep the Index File field empty (because
we are using mod_rewrite, the actual path to your index.php file is hidden by Apache). If you
are seeing the warning message like “Index File must be specified in order to run or debug project
in command line”, just ignore it.
Click the Finish button to create the project. When the helloworld project has been successfully
created, you should see the project window (see the figure 2.7).
In the project window, you can see the menu bar, the tool bar, the Projects pane where your
project files are listed, and, in the right part of the window, you can see the code of the index.php
entry file.
Please refer to Appendix B for more NetBeans usage tips, including launching and interactively
debugging ZF2-based web sites.
The .htaccess (hypertext access) file is actually an Apache web server’s configuration file
allowing to override some web server’s global configuration. The .htaccess file is a directory-
level configuration, which means it affects only its owning directory and all sub-directories.
The content of .htaccess file is presented below:
1 RewriteEngine On
2 # The following rule tells Apache that if the requested filename
3 # exists, simply serve it.
4 RewriteCond %{REQUEST_FILENAME} -s [OR]
5 RewriteCond %{REQUEST_FILENAME} -l [OR]
6 RewriteCond %{REQUEST_FILENAME} -d
7 RewriteRule ^.*$ - [NC,L]
8 # The following rewrites all other queries to index.php. The
9 # condition ensures that if you are using Apache aliases to do
10 # mass virtual hosting, the base path will be prepended to
11 # allow proper resolution of the index.php file; it will work
12 # in non-aliased environments as well, providing a safe, one-size
13 # fits all solution.
14 RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
15 RewriteRule ^(.*) - [E=BASE:%1]
16 RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
Zend Skeleton Application 34
Line 1 tells Apache web server to enable URL rewrite engine (mod_rewrite). The rewrite engine
modifies the incoming URL requests, based on regular expression rules. This allows you to map
arbitrary URLs onto your internal URL structure in any way you like.
Lines 4 - 7 define rewrite rules that tell the web server that if the client (web browser) requests
a file that exists in the document root directory, than to return the contents of that file as HTTP
response. Because we have our public directory inside of the virtual host’s document root, we
allow site users to see all files inside of the public directory, including index.php, CSS files,
JavaScript files and image files.
Lines 14 - 16 define rewrite rules that tell Apache what to do if the site user requests a file which
does not exist in document root. In such a case, the user should be redirected to index.php.
Table 2.2 contains several URL rewrite examples. The first and second URLs point to existing
files, so mod_rewrite returns the requested file paths. The URL in the third example points to
a non-existent file htpasswd (which may be a symptom of a hacker atack), and based on our
rewrite rules, the engine returns index.php file.
1 Order allow,deny
2 Deny from all
3 Allow from 127.0.0.1
Line 1 defines the order. Order allow deny means allow everything which is not denied.
Line 2 forces Apache to deny access to your site for everyone.
Line 3 overrides the line 2, allowing access to your site from IP 127.0.0.1 (localhost). You may
need to replace 127.0.0.1 with your external IP address.
Zend Skeleton Application 35
1 AuthUserFile /usr/local/apache/passwd/passwords
2 AuthType Basic
3 AuthName "Authentication Required"
4 require valid-user
Line 1 defines the file where passwords will be stored. This file should be created with the
htpasswd utility.
Line 2 defines Basic authentication method. The most common method is Basic. It is important
to be aware, however, that Basic authentication sends the password from the client to the
server unencrypted. This method should therefore not be used for highly sensitive data. Apache
supports one other authentication method: AuthType Digest. This method is much more secure.
Most recent browsers support Digest authentication.
Line 3 defines the text that will be displayed to user when he tries to log in.
Line 4 will allow anyone to log in that is listed in the password file, and who correctly enters
their password.
To create passwords file, type the following command:
htpasswd -c /usr/local/apache/passwd/passwords <username>
In the command above, you should replace the <username> placeholder with the name of the
user. You can choose an arbitrary name, for example “admin”. The command will request the
user’s password and write the password to the file:
⁶http://www.whatismyip.com/
Zend Skeleton Application 36
When the user tries to visit the site, he sees the HTTP authentication dialog (see the figure below).
To log into your site, the visitor should enter the correct username and password.
NameVirtualHost 8080
<VirtualHost *:8080>
...
</VirtualHost>
To access the web site, in your browser’s navigation bar, enter “http://localhost:8080”.
After editing the virtual host config file, you should restart Apache to apply changes.
127.0.0.1 site1.localhost
127.0.0.1:8080 site2.localhost
So now you’ll be able to simply enter “site1.localhost” in your browser’s address bar instead of
remembering the port number.
In Linux, the hosts file is located in /etc/hosts. In Windows, the file is typically
located in C:\Windows\System32\drivers\etc\hosts. To edit the file, you need to be
an administrator. Please also note that some anti-virus software may block changes
to hosts file, so you’ll have to temporarily disable your anti-virus to edit the file, and
enable it after.
{
"require": {
"php": ">=5.3.3",
"zendframework/zendframework": ">2.2.0rc1"
}
}
After installation, Composer also creates the APP_DIR/composer.lock file. This file now contains
actual versions of the packages that were installed. If you run the install command again,
Composer will encounter the composer.lock file, check which dependencies already installed
and as all packages already installed, it just exits without doing anything.
Now assume that in some period of time new security updates for your dependency packages
are released. You will want to update your packages to keep your web site secure. You can do
that by typing the following:
php composer.phar update
If you want to update only a single dependency, type its name as the following:
php composer.phar update zendframework/zendframework
After the update command, your composer.lock file will be updated, too.
If you want to add new dependency to the application, you can either edit composer.json
manually, or issue require command. For example, to install Doctrine ORM module to your
web site (to add the “doctrine/doctrine-module” package to the application dependencies), type
the following:
php composer.phar require doctrine/doctrine-module 2.*
The command above edits composer.json file, and downloads and installs the package. We will
use this command later in Chapter 12, when becoming familiar with database management.
You can use composer show --platform command to display a list of available virtual packages
for your machine.
What if some dependence will be declared obsolete and removed from Packag-
ist.org?
Well, the possibility of package removal is minimum. All packages are free and open-
source, and the community of users can always restore the dependency even if it is
removed from packagist. By the way, the same concept of dependency installation is
used in Linux (remember APT or RPM manager?), so did anyone see any Linux package
lost?
But there may be situations when you should store some dependent libraries under version
control:
• If you have to make custom changes to third-party code. For example, assume you have
to fix a bug in a library, and you cannot wait for the library’s vendor to fix it for you (or
if the library vendor cannot fix the bug). In this case, you should place the library code
under version control to ensure your custom changes won’t be lost.
• If you have written a reusable module or library and want to store it in the vendor directory
without publishing it on Packagist.org. Because you don’t have an ability to install this
code from the Packagist, you should store it under version control.
• If you want a 100% guarantee that a third-party package won’t be lost. Although the risk is
minimum, for some applications it is critical to be autonomous and not depend on package
availability on Packagist.org.
Zend Skeleton Application 41
2.13 Summary
In this chapter, we have downloaded the Zend Skeleton Application project code from GitHub
code hosting and installed it using Composer dependency management tool. We’ve configured
the Apache Virtual Host to tell the web server about location of the web site’s document root
directory.
The skeleton application demonstrates the recommended directory structure of a typical web
site. We have the public directory containing files publicly accessible by site users, including
the index.php entry point file, CSS files, JavaScript files and images. All other directories of
the application are inaccessible by site users and contain application configuration, data and
modules.
In the second part of the chapter we discussed some advanced usage of hypertext access file
(.htaccess). With this file, you can protect your web site with password and allow accessing it
from certain IP addresses only.
The Composer dependency manager is a powerful tool for installing the dependencies of your
web site. For example, Zend Framework 2 itself can be considered as a dependency. All packages
installable by Composer are registered in a centralized catalog on the Packagist.org site.
3. Web Site Operation
In this chapter we will provide some theory on how a typical Zend Framework 2 based
application works. You’ll learn how PHP namespaces are used for avoiding name collisions,
what class autoloading is, how to define application configuration parameters and the stages
present in an application’s life-cycle. You will also become familiar with such an important ZF2
components as Zend\EventManager, Zend\ModuleManager and Zend\ServiceManager. If instead
of learning the theory, you want to have some practical examples, skip this chapter and refer
directly to Chapter 4.
ZF2 components covered in this chapter:
Component Description
Zend\Mvc Support of Model-View-Controller pattern. Separation of business
logic from presentation.
Zend\Loader Implements the PHP class autoloading support.
Zend\ModuleManager This component is responsible for loading and initializing modules of the
web application.
Zend\EventManager This component implements functionality for triggering events and event
handling.
Zend\ServiceManager Implements the registry of all services available in the web application.
1 <?php
2 namespace Zend\Mvc;
3
4 // ...
5
6 /**
7 * Main application class for invoking applications.
8 */
9 class Application
10 {
11 // ... class members were omitted for simplicity ...
12 }
You may notice that in example above we have the opening <?php tag which tells the
PHP engine that the text after the tag is a PHP code. In example above, when the file
contains only the PHP code (without mixing PHP and HTML tags), you don’t need to
insert the closing ?> tag after the end of the code. Moreover, this is not recommended
and may cause undesired effects, if you occasionally add some character after the
closing ?> tag.
In Zend Framework 2, all classes belong to top-level Zend namespace. The line 2 defines
the namespace Mvc, which is nested into Zend namespace, and all classes of this component
(including the Application class shown in this example on lines 9-12) belong to this namespace.
You separate nested namespace names with the back-slash character (‘\’).
In other parts of code, you reference the Application class using its full name:
<?php
$application = new \Zend\Mvc\Application;
It is also possible to use the alias (short name for the class) with the help of PHP’s use statement:
<?php
// Define the alias in the beginning of the file.
use Zend\Mvc\Application;
Although the alias allows to use a short class name instead of the full name, its usage
is optional. You are not required to always use aliases, and can refer the class by its full
name.
Every PHP file of your application typically defines the namespace (except index.php entry
script and config files, which typically do not). For example, the main module of your site, the
Application module, defines its own namespace whose name equals to the module name:
<?php
namespace Application;
// ...
class Module
{
// ... class members were omitted for simplicity ...
}
<?php
namespace Zend\Mvc;
//...
interface ApplicationInterface
{
// Retrieves the service manager.
public function getServiceManager();
As you can see from the example above, an interface is defined using the interface keyword,
almost the same way you define a standard PHP class. As a usual class, the interface defines
methods. However, the interface does not provide any implementation of its methods. In the
ApplicationInterface interface definition above, you can see that every application implement-
ing this interface will have method getServiceManager() for retrieving the service manager
(about the service manager, see later in this chapter), the getRequest() and getResponse()
methods for retrieving the HTTP request and response, respectively, and method run() for
running the application.
A class implementing an interface is called a concrete class. The concrete Application class
implements the ApplicationInterface, which means it provides the implementation of the
methods defined by the interface:
<?php
namespace Zend\Mvc;
//...
The concrete Application class uses the implements keyword to show that it provides an
implementation of all methods of ApplicationInterface interface. The Application class can
also have additional methods, which are not part of the interface.
Web Site Operation 46
Graphically, the class relations are displayed using inheritance diagrams. In figure 3.1, the
diagram for Application class is presented. The arrow points from the child class to the parent
class.
<?php
require_once "/path/to/zend/lib/Application.php";
use Zend\Mvc\Application;
As your application grows in size, it may be difficult to include each needed file. Zend Framework
2 itself consists of hundreds of files, and it can be very difficult to load the entire library and all
its dependencies this way. Moreover, when executing the resulting code, PHP interpreter will
take CPU time to process each included file, even if you don’t create an instance of its class.
To fix this problem, in PHP 5.1, the class autoloading feature has been introduced. The PHP
function spl_autoload_register() allows you to register an autoloader function. For complex
web sites, you even can create several autoloader functions, which are chained in a stack.
During script execution, if PHP interpreter encounters a class name which has not been defined
yet, it calls all the registered autoloader functions in turn, until either the autoloader function
includes the class or “not found” error is raised. This allows for “lazy” loading, when PHP
interpreter processes the class definition only at the moment of class invocation, when it is really
needed.
To give you an idea of how an autoloader function looks like, below we provide a simplified
implementation of an autoloader function:
Web Site Operation 47
<?php
// Autoloader function.
function autoloadFunc($className) {
In the above example, we define the autoloadFunc() autoloader function, which we will further
refer to as the class map autoloader.
The class map autoloader uses the class map for mapping between class name and absolute path
to PHP file containing that class. The class map is just a usual PHP array containing keys and
values. To determine the file path by class name, the class map autoloader just needs to fetch
the value from the class map array. It is obvious, that the class map autoloader works very fast.
However, the disadvantage of it is that you have to maintain the class map and update it each
time you add a new class to your program.
For example, for the Zend\Mvc\Application class, you will have the following directory
structure:
/path/to/zend/lib
/Zend
/Mvc
Application.php
For the code conforming to the PSR-0 standard, we can write and register an autoloader, which
we will refer to as the “standard” autoloader:
<?php
The standard autoloader works as follows. Assuming that the class namespace can be mapped to
the directory structure one-by-one, the function calculates the path to PHP file by transforming
back-slashes (namespace separators) to forward slashes (path separators) and concatenating the
resulting path with the absolute path to the directory where the library is located. Then the
function checks if such a PHP file really exists, and if so, includes it with the require statement.
Web Site Operation 49
It is obvious, that the standard autoloader works slower than the class map autoloader. However,
its advantage is that you don’t need to maintain any class map, which is very convenient when
you develop new code and add new classes to your application.
HTTP² (stands for Hyper Text Transfer Protocol) – a protocol for transferring data in
the form of hyper text documents (web pages). HTTP is based on the client-server
technology: the client initiates a connection and sends a request to web server, and the
server waits for a connection, performs the necessary actions and returns a response
message back.
Thus, the main underlying goal of any web application is handling the HTTP request and
producing an HTTP response typically containing the HTML code of the requested web page.
The response is sent by the web server to the client web browser and the browser displays a web
page on the screen.
A typical HTTP request is presented below:
• The starting line (line 1) specifies the method of the request (e.g GET or POST), the URL
string and HTTP protocol version.
• Optional headers (lines 2-8) characterize the message, the transmission parameters and
provide other meta information. In the example above, each row represents a single header
in the form of name:value.
• Optional message body contains message data. It is separated from the headers with a
blank line.
The headers and the message body may be absent, but the starting line is always present in the
request, because it indicates its type and URL.
The server response for the above request is presented below:
As you can see from the dump above, the HTTP response has almost the same format as the
request:
• The starting line (line 1) represents the HTTP protocol version, response status code and
message (200 OK).
• Optional headers (lines 2-10) provide various meta information about the response.
• Optional message body follows the headers, and must be separated from headers by an
empty line. The message body typically contains the HTML code of the requested web
page.
The entry script is the only PHP file accessible to the outside world. Apache web server
directs all HTTP requests to this script (remember the .htaccess file?). Having this single
entry script makes the web site more secure (comparing with the situation when you
allow everyone to access all PHP files of your application).
Web Site Operation 51
Although the index.php file is very important, it is surprisingly small (see below):
1 <?php
2
3 /**
4 * This makes our life easier when dealing with paths.
5 * Everything is relative to the application root now.
6 */
7 chdir(dirname(__DIR__));
8
9 // Setup autoloading
10 require 'init_autoloader.php';
11
12 // Run the application!
13 Zend\Mvc\Application::init(
14 require 'config/application.config.php')->run();
Zend Framework 2 uses the concept of event. One class can trigger an event, and
other classes may listen to events. Technically, triggering an event means just calling
another class’ “callback” method. The event management is implemented inside of the
Zend\Mvc\EventManager component.
Each application life stage is initiated by the application by triggering an event. Other classes
(either belonging to Zend Framework or specific to your application) may listen to events and
react accordingly.
Below, the four main events (life stages) are presented:
Bootstrap. When this event is triggered by the application, a module has a chance to register
itself as a listener of further application events in its onBootstrap() callback method.
Web Site Operation 52
Route. When this event is triggered, the request’s URL is analyzed using a router class (typically,
with Zend\Mvc\Router\Http\TreeRouteStack class. If an exact match between the URL and a
route is found, the request is passed to the site-specific controller class assigned to the route.
Dispatch. The controller class “dispatches” the request using the corresponding action method
and produces the data that can be displayed on the web page.
Render. On this event, the data produced by the controller’s action method are passed for
rendering to Zend\View\Renderer\PhpRenderer class. The renderer class uses a view template
file for producing an HTML page.
The event flow is illustrated in figure 3.2:
Some PHP frameworks prefer conventions over configuration concept, where most of
your parameters are hard-coded and do not require configuration. This makes it faster
to develop the application, but makes it less customizable. In Zend Framework 2, the
configuration over conventions concept is used, so you can customize any aspect of
your application, but have to spend some time for learning how to do that.
By convention, key names should be in lower case, and if the key name consists of
several words, the words should be separated by the underscore symbol (‘_’).
Web Site Operation 54
In line 5 we have the modules key listing all modules which are present in your web site.
Currently, you have the single Application module.
In line 17, there is the module_paths key which tells ZF2 about directories where to look for
source files belonging to modules. Application modules that you develop are located under
APP_DIR/module directory, and third-party modules may be located inside the APP_DIR/vendor
directory.
And in line 26 we have the config_glob_paths key, which tells ZF2 where to look for extra
config files. You see that files from APP_DIR/config/autoload which have global.php or local.php
suffix, are automatically loaded.
Summing up, you typically use the main application.config.php file for storing the information
about which modules should be loaded into your app and where they are located and how they
Web Site Operation 55
are loaded (for example, you can control caching options here). In this file you can also tune the
service manager. It is not recommended to add more keys in this file. For that purpose it is better
to use autoload/global.php file.
• You use the autoload/global.php file for storing parameters which do not depend on
the concrete machine environment. For example, here you can store parameters which
override the default parameters of some module. Do not store confidential information
(like database credentials) here, for that purpose it’s better to use autoload/local.php.
• You use the autoload/local.php file for storing parameters specific to the concrete environ-
ment. For example, here you can store your database credentials. Each developer usually
has a local database when developing and testing the web site. The developer thus will
edit the local.php file and enter his own database credentials here. When you install your
site to the production server, you will edit the local.php file and enter the credentials for
the “live” database here.
module.config.php file
<?php
return array(
'router' => array(
'routes' => array(
// Register URL routing rules here.
),
),
'service_manager' => array(
// Register module-provided services here.
),
'controllers' => array(
// Register module-provided controllers here.
),
'view_helpers' => array(
// Register module-provided view helpers here.
),
'view_manager' => array(
// Provide view manager configuration here.
),
);
In this file, you register the module’s controllers, put information about routing rules for mapping
URLs to your controllers, register controller plugins, and also register view templates and view
helpers (we will learn more about these terms in this chapter and in the next chapters).
You might also have seen the “combined” config file when installing PHP, where there
is the main php.ini file and several extra config files, which are included into the main
one. Such a separation makes your application configuration fine-grained and flexible,
because you don’t have to put all your params to a single file and edit it each time you
need to change something.
• The main application.config.php file is loaded first. It is used to initialize the service
manager and load application modules. The data loaded from this config is stored alone
and not merged with other config files.
Web Site Operation 57
• Configuration files for each application module are loaded and merged. Modules are
loaded in the same order as they are listed in the application.config.php file. If two modules
store (either intentionally, or by mistake) parameters in the similar-named keys, these
parameters may be overwritten.
• Extra config files from the APP_DIR/config/autoload folder are loaded and merged into
a single array. Then this array is merged with the module config array produced on the
previous stage, when loading the module configuration. Application-wide configuration
has higher priority than the module configuration, so you can override module keys here,
if you wish.
1 <?php
2 namespace Application;
3
4 use Zend\Mvc\ModuleRouteListener;
5 use Zend\Mvc\MvcEvent;
6
7 class Module
8 {
9 public function onBootstrap(MvcEvent $e)
10 {
11 $eventManager = $e->getApplication()->getEventManager();
12 $moduleRouteListener = new ModuleRouteListener();
13 $moduleRouteListener->attach($eventManager);
14 }
15
16 public function getConfig()
17 {
18 return include __DIR__ . '/config/module.config.php';
19 }
20
21 public function getAutoloaderConfig()
22 {
23 return array(
24 'Zend\Loader\StandardAutoloader' => array(
25 'namespaces' => array(
26 __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
27 ),
28 ),
Web Site Operation 58
29 );
30 }
31 }
The class Module belongs to the module’s namespace (for the main module it belongs to the
Application namespace).
The onBootstrap() method (lines 9-14) is called by the application on start up, during the
Bootstrap event, and allows to register event listeners for the module.
The getConfig() method (lines 16-19) tells the Zend Framework where the module.config.php
file is located.
The getAutoloaderConfig() method (lines 21-30) tells the Zend Framework where to find the
source files for the module. This information is used for initializing the autoloader function for
the module.
Recalling the index.php site entry script from a previous section, we see that it “includes” the
autoloader initialization script file named init_autoloader.php. The init_autoloader.php script
does one simple thing. It checks if Composer’s autoloader file is present, and just “includes” that
file:
<?php
//...
// Composer autoloading
if (file_exists('vendor/autoload.php')) {
$loader = include 'vendor/autoload.php';
}
This makes it possible to automatically find and load any PHP class in any library installed with
Composer (including Zend Framework 2 classes).
Web Site Operation 59
ZF2 has a special component named Zend\Loader, which contains implementations of the two
commonly-used autoloader classes: the standard autoloader (Zend\Loader\StandardAutoloader)
and class map autoloader (Zend\Loader\ClassMapAutoloader).
The autoloader class hierarchy is displayed in the diagram below (figure 3.4). The StandardAutoloader
and ClassMapAutoloader classes both implement SplAutoloader interface:
// Defines an interface for classes that may register with the spl_autoload
// registry
interface SplAutoloader
{
// Constructor
public function __construct($options = null);
// Autoload a class
public function autoload($class);
The SplAutoloader interface defines the register() method, which is intended for registering
the autoloader function (class method in our case) with the help of spl_autoloader_register(),
and the autoload() method, which is intended for providing the concrete class discovery
algorithm.
Web Site Operation 60
The fact that ZF2-based application modules conform to PSR-0 standard makes it possible to use
the standard autoloader.
In the skeleton application’s Module class, the getAutoloaderConfig() method provides the
configuration for registering the source PHP files belonging to the module’s namespace within
the standard autoloader:
<?php
namespace Application;
class Module {
In the above method, we see that the module tells ZF2 to use the Zend\Loader\StandardAutoloader
for autoloading the module’s source files. The namespaces key contains key⇒value pairs, where
key is the namespace, and the value is the absolute path to the directory containing the PHP files
implementing the classes from that namespace. The __NAMESPACE__ constant expands into the
module’s namespace string (for example, to Application for the main module of the application),
and the __DIR__ constant expands to directory path where the Module.php file is located.
Web Site Operation 61
In Zend Skeleton Application web site, you can see how the PSR-0 standard is applied in practice.
For the default module of your web site, the Application module, PHP classes which are
registered with the standard autoloader are stored under the APP_DIR/module/Application/src
directory (“src” abbreviation means “source”).
For example, lets look at the IndexController.php file of Application module (figure 3.5).
³IndexController class is the default controller for the skeleton web site. We will talk about controllers later in Chapter 4 when learning
about Model-View-Controller pattern.
Web Site Operation 62
The class map autoloader can be used as a faster replacement for the standard autoloader. This
autoloader expects you to pass it a class map array. Each key⇒value pair of the class map is,
respectively, the class name and path to the PHP file containing the class.
The module’s getAutoloaderConfig() method would look like below:
1 <?php
2 namespace Application;
3
4 class Module {
5
6 //...
7
8 public function getAutoloaderConfig()
9 {
10 return array(
11 'Zend\Loader\ClassMapAutoloader' => array(
12 __DIR__ . '/autoloader_classmap.php', // File-based class map.
13 array(
14 // Array class map.
15 'Application\Controller\IndexController' =>
16 __DIR__ . 'src/Application/Controller/IndexController.php,
17 //...
18 ),
19 ),
20 );
21 }
22 }
In the example above, we can provide the class map using an array (see lines 13-15) containing
key⇒value pairs, where the key is the full class name, and value is the absolute path to PHP
file where the class is implemented. Or we can create the class map in an external PHP file and
provide the autoloader the path to that file (line 12).
The problem with using the class map autoloader is that you have to maintain the class map,
which is difficult to do by hands. Fortunately, Zend Framework 2 provides you with the special
tool for this. The tool is a PHP script located in APP_DIR/vendor/bin/classmap_generator.php
file. To generate a class map file for your module, you call the generator script like below:
cd APP_DIR/vendor/bin/
php classmap_generator.php -l path/to/module -o /output/dir
for accessing the database, event manager service responsible for triggering events and delivering
them to event listeners, etc.
In Zend Framework 2, the ServiceManager class is a centralized repository for all application
services. The service manager is implemented in Zend\ServiceManager component, as the
ServiceManager class, which implements the ServiceLocatorInterface interface. The class
diagram is presented in figure 3.6:
The service manager is created on application start up (inside of init() static method of
Zend\Mvc\Application class). The standard services available through service manager are
presented in table 3.1. This table is incomplete, because the actual number of services registered
in service manager may be much bigger.
A service is typically an arbitrary PHP class, but not always. For example, when ZF2 loads the
configuration files and merges the data into nested arrays, it saves the arrays in the service
Web Site Operation 64
manager as a couple of services (!): ApplicationConfig and Config. The first one is the array
loaded from application-level configuration file application.config.php, and the latter one is the
merged array from module-level config files and auto-loaded application-level config files. Thus,
in the service manager you can store any asset you want: a PHP class, a variable or an array.
From table 3.1, you can see that in ZF2 everything can be considered as a service. The service
manager is itself registered as a service. Moreover, the Application class is also registered as a
service.
An important thing you should note about the services is that they are typically stored
in a single instance only (this is also called the singleton pattern). Obviously, you don’t
need the second instance of the Application class or, say, the event manager (in that
case you would have a nightmare with proliferating events).
<?php
namespace Zend\ServiceManager;
interface ServiceLocatorInterface {
In the web application, you typically deal with the ServiceLocatorInterface, instead of the
service manager itself.
You can test if a service is registered by passing its name to the service locator’s has() method.
It returns a boolean true if the service is registered, or false if the service with such a name is
not registered.
You can retrieve a service by its name at any place of your application with the help of the
service locator’s get() method. This method takes a single parameter representing the service
name. Look at the following example:
Web Site Operation 65
<?php
In the example above, we assume you have already retrieved the service locator from
somewhere. Looking ahead, let’s say that typically, you will do that in your controller
classes with the help of getServiceLocator() method. We will learn about controller
classes later in Chapter 4.
1 <?php
2 // Define a namespace where our custom service lives.
3 namespace Application\Service;
4
5 // Define a currency converter service class.
6 class CurrencyConverter {
7
8 // Converts euros to US dollars.
9 public function convertEURtoUSD($amount) {
10 return $amount*1.25;
11 }
12
13 //...
14 }
15
16 // Create an instance of the class.
17 $service = new CurrencyConverter();
18 // Save the instance to service manager.
19 $serviceManager->setService('CurrencyConverter', $service);
Above, in lines 5-13 we define an example CurrencyConverter class (for simplicity, we imple-
ment only a single method convertEURtoUSD() which is able to convert euros to US dollars).
Next, in line 17 we instantiate the class with the new operator, and in line 19 we register it with the
service manager using the setService() method (we assume that the $serviceManager variable
is of type Zend\ServiceManager\ServiceManager class, and that it was declared somewhere
else).
The setService() method takes two parameters: the service name string, and the service
instance. The service name should be unique within all other possible services. If you are trying
to register the service name which is already present, the setService() method throws an
exception. But sometimes you want to override the service with the same name (to replace it
by the new one). For this purpose, you can use the setAllowOverride() method of the service
manager:
1 <?php
2 // Allow to replace services
3 $serviceManager->setAllowOverride(true);
4 // Save the instance to service manager. There will be no exception
5 // even if there is another service with such a name.
6 $serviceManager->setService('CurrencyConverter', $service);
Above, the setAllowOverride() method takes the single boolean parameter defining whether
to allow you replace the service CurrencyConverter if such a name is already present, or not.
Once the service is stored in service manager, you can retrieve it by name at any place of your
application with the help of the service manager’s get() method. Look at the following example:
Web Site Operation 67
<?php
// Retrieve the currency converter service.
$service = $serviceManager->get('CurrencyConverter');
// Use it (convert money amount).
$convertedAmount = $service->convertEURtoUSD(50);
<?php
// Register an invokable class
$serviceManager->setInvokableClass('\Application\Service\CurrencyConverter');
In the example above, we pass to the service manager the full class name of the service instead
of passing its instance. With this technique, the service will be instantiated by the service
manager only when someone calls the get('CurrencyConverter') method. This is also called
lazy loading.
<?php
namespace Zend\ServiceManager;
// Factory interface.
interface FactoryInterface
{
// Creates the service instance.
public function createService(ServiceLocatorInterface $serviceLocator);
}
As we see from the definition of the FactoryInterface, the factory class must provide the
createService() method returning the instance of a single service. The service locator is passed
to the createService() method as a parameter; it can be used during the construction of the
service for accessing other services.
As an example, let’s write a factory for our currency converter service (see the code below). We
don’t use complex construction logics for our CurrencyConverter service, but for more complex
services, you may need to use one.
Web Site Operation 68
<?php
use Zend\ServiceManager\FactoryInterface;
// Factory class
class CurrencyConverterFactory implements FactoryInterface{
return $service;
}
}
Even more complex case of a factory is when you need to determine at run time which services
should be registered, and which should not. For such a situation, you can use an abstract factory.
An abstract factory class should implement the AbstractFactoryInterface interface:
<?php
namespace Zend\ServiceManager;
interface AbstractFactoryInterface
{
// Determine if we can create a service with such a name.
public function canCreateServiceWithName(
ServiceLocatorInterface $serviceLocator, $name, $requestedName);
<?php
// Register an alias for the CurrencyConverter service
$serviceManager->setAlias('CurConv', 'CurrencyConverter');
Once registered, you can retrieve the service by both its name and alias using the service locator’s
get() method.
If you are putting this key in a module-level configuration file, be careful about the
danger of name overwriting during the configs merge. Do not register the same service
name in different modules.
1 <?php
2 return array(
3 //...
4
5 // Register the services under this key
6 'service_manager' => array(
7 'services' => array(
8 // Register service class instances here
9 //...
10 ),
11 'invokables' => array(
12 // Register invokable classes here
13 //...
14 ),
15 'factories' => array(
16 // Register factories here
17 //...
18 ),
19 'abstract_factories' => array(
20 // Register abstract factories here
21 //...
22 ),
23 'aliases' => array(
24 // Register service aliases here
25 //...
26 ),
Web Site Operation 70
27 ),
28
29 //...
30 );
In the example above, you can see that the service_manager key may contain several subkeys
for registering services in different ways:
As another example, let’s look how this key looks like in the module.config.php file of the
Application module of the skeleton application:
<?php
return array(
//...
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
'Zend\Log\LoggerAbstractServiceFactory',
),
'aliases' => array(
'translator' => 'MvcTranslator',
),
),
);
Above, we can see that there are two abstract factories registered (for storage cache service and
logger service) and an alias is registered for the translator service. We will become familiar with
these services in the next chapters of this book.
3.12 Summary
In this chapter, we’ve learned some theory about ZF2-based web site operation basics.
ZF2 uses PHP namespaces and class autoloading features, simplifying the development of
applications which use many third-party components. The namespaces allow to solve the name
Web Site Operation 71
collisions between code components, and provide you with the ability to make the long names
shorter.
The class autoloading makes it possible to use any PHP class in any library installed with
Composer without the use of require_once statement.
Most of Zend Framework 2 components require configuration. You can define the configuration
parameters either at the application level, or at the module level.
The main goal of any web application is handling the HTTP request and producing an HTTP
response typically containing the HTML code of the requested web page. When Apache web
server receives an HTTP request from a client browser, it executes the index.php file, which is
also called the site’s entry script. On every HTTP request, the Zend\Mvc\Application object is
created, whose “life cycle” consists of several stages (or events).
The web application can be also considered as a set of services. In Zend Framework 2, the service
manager is a centralized repository for all the application services. A service is typically a PHP
class, but in general it can be a variable or an array, if needed. In your code, you retrieve the
services from the service manager with the help of the service locator interface.
4. Model-View-Controller
In this chapter, you will learn about the models, views and controllers (MVC). The web
application uses the MVC pattern to separate business logic from presentation. The goal of this
is to allow for code reusability and separation of concerns.
ZF2 components covered in this chapter:
Component Description
Zend\Mvc Support of MVC pattern. Implements base controller classes, controller plugins, etc.
Zend\View Implements the functionality for variable containers, rendering a web page and
common view helpers.
Zend\Http Implements a wrapper around HTTP request and response.
Zend\Version A small auxiliary component, which can be used for checking the version of Zend
Framework.
/using-zend-framework-2-book
/helloworld
...
The Hello World is a complete web site which can be installed on your machine. To install the
example, you can either edit your default Apache virtual host file or create a new one. After
editing the file, restart the Apache HTTP Server and open the web site in your web browser.
¹https://github.com/olegkrivtsov/using-zend-framework-2-book
Model-View-Controller 73
Figure 4.1. The Hello World sample can be downloaded from GitHub
Since that time, PHP became object-oriented, and now you can organize your code into classes.
The Model-View-Controller (MVC) pattern is just a set of advices telling you how to organize
your classes in a better manner, to make them easy to maintain.
In MVC, classes implementing your business logic are called models, code snippets rendering
HTML pages are called views, and the classes responsible for interacting with user are called
controllers.
Views are implemented as code snippets, not as classes. This is because views are
typically very simple and contain only the mixture of HTML and inline PHP code.
Model-View-Controller 74
The main objective of the MVC concept is to separate the business logic (models) from its
visualization (views). This is also called the separation of concerns, when each layer does its
specific tasks only.
By separating your models from views, you reduce the number of dependencies between them.
Therefore, changes made to one of the layers have the lowest possible impact on other layers.
This separation also improves the code reusability. For example, you can create multiple visual
representations for the same models.
To better understand how this works, lets remember that any web site is just a PHP program
receiving an HTTP request from the web server, and producing an HTTP response. Figure 4.2
shows how an HTTP request is processed by the MVC application and how the response is
generated:
• First, the site visitor enters an URL in his web browser, for example http://localhost, and
the web browser sends the request to the web server over the Internet.
• Web server’s PHP engine runs the index.php entry script. The only thing the entry script
does is creating the Zend\Mvc\Application class instance.
• The application uses its router component for parsing the URL and determining to which
controller to pass the request. If the route match is found, the controller is instantiated and
its appropriate action method is called.
• In the controller’s action method, parameters are retrieved from GET and POST variables.
To process the incoming data, the controller instantiates appropriate model classes and
calls their methods.
• Model classes use business logic algorithms to process the input data and return the
output data. The business logic algorithms are application-specific, and typically include
retrieving data from database, managing files, interacting with external systems and so
on.
• The result of calling the models are passed to the corresponding view script for the
rendering of the HTML page.
• View script uses the model-provided data for rendering the HTML page.
• Controller passes the resulting HTTP response to application.
• Web server returns the resulting HTML web page to the user’s web browser.
• The user sees the page in browser window.
Now you might have some idea how models, views and controllers cooperate to generate HTML
output. In the next sections, we describe them in more details.
Model-View-Controller 75
4.3 Controllers
A controller provides communication between the application, models and views: gets input
from HTTP request and uses the model(s) and the corresponding view to produce the necessary
HTTP response.
Controllers belonging to module typically reside in the Controller subdirectory of module’s
source directory (shown in figure 4.3).
Model-View-Controller 76
Zend Skeleton Application provides you with the default implementation of IndexController
class. The IndexController is typically the main controller class of the web site. Its code is
presented below (some parts of code were omitted for simplicity):
1 <?php
2 // IndexController.php
3 namespace Application\Controller;
4
5 use Zend\Mvc\Controller\AbstractActionController;
6 use Zend\View\Model\ViewModel;
7
8 class IndexController extends AbstractActionController {
9
10 // The "index" action
11 public function indexAction() {
12
13 return new ViewModel();
14 }
15 }
From the example above, you can see that controllers usually define their own namespace
(line 3). The Index controller, as all other controllers from the Application module, lives in
Application\Controller namespace.
A controller is a usual PHP class derived from the AbstractActionController base class (line
8).
By default, the controller class contains the single action method called indexAction() (see lines
11-14). Typically, you will create other action methods in your controller classes.
ZF2 automatically recognizes the action methods by the Action suffix. If a controller
method’s name does not have that suffix, it is considered as a usual method, not an
action.
Model-View-Controller 77
As its name assumes, an action method performs some site action, which typically results in
displaying a single web page. Index controller usually contains action methods for site-wide
web pages (table 4.1). For example, you would have “index” action for the Home page, “about”
action for About page, “contactUs” action for the Contact Us page and possibly other actions.
The AbstractActionController provides you with several useful methods you can use in your
controller classes. Table 4.2 provides you with a brief summary of the methods:
As you can see from the table above, the base controller class provides you with access to HTTP
request and response data, and provides you with the access to the service manager and to the
event manager. It also gives you an ability to register and call controller plugins (we will learn
about controller plugins later in this chapter).
The code above returns the instance of Zend\Http\Request class, containing all the HTTP
request data. In table 4.3, you can find the most widely used methods of the Request class together
with their brief description.
getQuery($name, $default) Returns the query parameter by name, or all query parameters.
If a parameter is not found, returns the $default value.
getPost($name, $default) Returns the parameter container responsible for post
parameters or a single post parameter.
getCookie() Returns the Cookie header.
getFiles($name, $default) Returns the parameter container responsible for file
parameters or a single file.
getHeaders($name, $default) Returns the header container responsible for headers
or all headers of a certain name/type.
getHeader($name, $default) Returns a header by $name. If a header is not found,
returns the $default value.
renderRequestLine() Returns the formatted request line (first line) for
this HTTP request.
fromString($string) A static method that produces a Request object from a
well-formed Http Request string
toString() Returns the raw HTTP request as a string.
In the example above, we used the Params controller plugin, which provides you with convenient
methods of accessing GET and POST variables, uploaded files, etc.
In line 2 we use the fromQuery() method for retrieving a variable having name “var_name” from
GET. If such a variable does not present, the default value “default_val” is returned. The default
value is very convenient, because you don’t have to use the isset() PHP function to test if the
variable exists.
In line 5 we use the fromPost() method to retrieve the variable from POST (line 5). The meaning
of this method’s parameters is the same as for the fromQuery() method.
In ZF2, you must not access request parameters through traditional PHP $_GET and
$_POST global arrays. Instead, you use ZF2-provided API for retrieving the request
data.
Model-View-Controller 80
The Zend\Version component is a small auxiliary component, which can be used by you if you
need to check the version of Zend Framework. To do that, you use the Version class.
In line 9, we use the VERSION constant defined in the Version class, which is the literal
representation of the version of Zend Framework currently installed in our machine (at the
moment of writing this text, this constant is equal to ‘2.2.1’).
In line 11, we use the getLatest() static method to load the latest available version of ZF from
the Internet (note, that this requires the Internet connection and may take a few seconds to
complete).
In line 13, we use the compareVersion() static method to compare our current version with the
latest one (the comparison result will be -1 if our current version is newer, 0 if both equal, or 1 if
ours is older).
In lines 17-21, we pass the variables we’ve created to the constructor of the ViewModel object as
an associative array. The array keys define the names of the variables which on return will be
accessible to view script.
The ViewModel class provides several methods that you can additionally use to set variables to
ViewModel and and retrieve variables from it. The table 4.5 provides the methods summary:
Model-View-Controller 82
1 <?php
2 return array(
3 // ...
4
5 'controllers' => array(
6 'invokables' => array(
7 'Application\Controller\Index' =>
8 'Application\Controller\IndexController'
9 // Put other controllers registration here
10 ),
11 ),
12
13 // ...
14 );
In line 5, we have the the controllers key, which contains the invokables subkey. You should
register your controllers here. To register a controller class, you add the line in form of key⇒value
pair. The key should be the unique ID of the controller, like Application\Controller\Index, and
value should be the fully qualified class name of the controller, like Application\Controller\IndexController.
By registering your controller under the invokables subkey, you tell Zend Framework
that it can invoke the controller by instantiating it with the new operator. This is the
most simple way of instantiating the controller. As an alternative, you can register a
factory to create the controller instance, in that case you would register your controller
under the factories subkey.
Model-View-Controller 83
Without plugins, to extend the functionality of all controllers, you would have to
create a custom base class, say BaseController, and derive other controllers from that
base class. This way is used in some PHP frameworks, but not in Zend Framework
2. From ZF2 creators’ point of view, plugins are better solution, because they use
class composition ², which provides better flexibility comparing to class inheritance.
You register your plugin controller and it automatically becomes accessible from all
controllers of your app (AbstactActionController base class uses PHP’s __call()
magic method to proxy calls to registered controller plugins).
There are several standard controller plugins available out of the box (table 4.6), and we’ve
already used one of them (the Params plugin) in one of our previous examples.
²Composition is a relationship between two classes that is best described as a “has-a” and “whole/part” relationship. The owner class contains
a reference to another class (plugin). The owner is responsible for the lifetime of the object it holds.
Model-View-Controller 84
Inside of the controller’s action method, you access a plugin in the following way:
<?php
namespace Application\Controller\Plugin;
use \Zend\Mvc\Controller\Plugin\AbstractPlugin;
// Plugin class
class AccessPlugin extends AbstractPlugin {
To let Zend Framework 2 know about your plugin, you need to register it in your mod-
ule.config.php file under the controller_plugins key. See below for example:
<?php
return array(
// ...
// ...
);
After that, you’ll be able to access your custom plugin from all of your controller’s actions in
this way:
4.8 Views
Views belong to the presentation layer of the web application, because their goal is to produce
HTML output returned by the web server to site visitors.
In Zend Framework 2, you implement a view as a template file, which is a file having .phtml
extension (“phtml” stands for PHP+HTML). View templates have such a name because they
usually contain HTML code mixed with PHP code snippets used for rendering the web pages.
Views typically live inside of the view subdirectory of the module (see figure 4.5):
Model-View-Controller 86
Why are view template files not stored under module’s source directory?
View templates (.phtml files) are not stored under module’s src/ directory, because
they are not usual PHP classes and do not need to be resolved by a PHP class
autoloading feature. View templates are resolved by the special ZF2 class called view
resolver, and for this reason, view templates are stored under the module’s view
directory.
View templates can have different behaviors, based on variables you pass to them from the
controller’s action method. Data are passed to view templates with the help of a ViewModel
variable container.
For example, let’s implement the view template for the aboutAction() of our Index controller.
The About page will display the title, some information about our Hello World application, and
the information about the current Zend Framework version.
To create the view template file, in your NetBeans window, navigate to view/application/index
directory (see figure 4.6), and right click on the “index” directory name. From the context menu
that appears, select the New->PHP File… menu item.
In the “New PHP File” dialog that appears (figure 4.7), enter the name about.phtml and click the
Finish button.
The about.phtml view template file will be created and displayed in the right pane of NetBeans
window. In that file, enter the following:
Model-View-Controller 87
1 <h1>About</h1>
2
3 <p>
4 The Hello World application.
5 </p>
6
7 <p>
8 Your Zend Framework version is
9 <?php echo $this->zendFrameworkVer; ?>
10 </p>
11
12 <?php if($this->isNewerVerAvailable): ?>
13
14 <p>
15 Your Zend Framework version is outdated. The latest available
16 version is <?php echo $this->latestVer; ?>.
17 </p>
18
19 <?php endif; ?>
As you can see, the view template is a usual HTML page with several PHP code fragments. A view
script just renders the data you pass to it with a ViewModel variable container. For example, in
Model-View-Controller 88
line 9 we get the value of $zendFrameworkVer variable and print it with the echo PHP statement.
In your view script, you can also use simple flow control operations (like if, foreach or switch)
to make the appearance of the page different depending on variable’s value. For example, in line
12 we have the if statement testing if the new Zend Framework version is available, and if yes,
the “Your Zend Framework version is outdated” text paragraph is displayed.
Now let’s look at how the page looks like in the web browser. Type “http://localhost/application/index/about”
URL in your browser’s navigation bar. The About page should appear (see figure 4.8):
In general, the PHP code you use inside of views must be as simple as possible. Views
typically do not modify the data you pass from controller. For example, a view can use
the model you pass to it to walk through database table rows and render the items to
an HTML page, but it should never create database tables or modify them itself.
View helpers are analogous to controller plugins: the controller plugins allow to
“extend” the functionality of controllers, and view helpers allow to “extend” the
functionality of view templates.
ZF2 provides many standard view helpers out of the box. In the table 4.7, some of them are
presented with a brief description:
Model-View-Controller 90
To demonstrate the usage of a view helper, below we will show how to set a title for a web
page. Typically, it is required to give a different title per each web page. You can do this with
the HeadTitle view helper. For example, you can set the title for the About page by adding the
following PHP code in the beginning of the about.phtml view template:
<?php
$this->headTitle('About');
?>
In the code above, we call the HeadTitle view helper and pass it the page title string (“About”) as
the argument. The HeadTitle view helper internally sets the text for the <title> HTML element
of your web page. Then, if you open the About page in your web browser, the page title will look
like “About - Zend Skeleton Application” (see the figure below for an example):
Model-View-Controller 91
We will discuss the view helpers in more details and provide more usage examples in
Chapter 6.
Table 4.8. Methods of the ViewModel class for setting and retrieving the view template name
To set the view template name, you use the setTemplate() method. The getTemplate() method
returns the view template name currently set for the view model.
Model-View-Controller 92
The following code example shows how you can call the setTemplate() method from your
IndexController class’ indexAction() method to force ZF2 to use the about.phtml view
template file for rendering the Home page, instead of the index.phtml file:
In the code above, we created a new instance of the ViewModel class as usual (line 5).
Then we called the setTemplate() method on the view model object (line 6) and passed the
name of the view template name as its argument. The view template name is actually a relative
path to the about.phtml file, minus file extension.
Finally, we returned the view model object from the action method (line 7).
However, calling the setTemplate() method in every action method is optional. If you don’t
do that, ZF2 will determine the view template name automatically by concatenating the current
module name, controller name and action method name.
When Zend Framework has the template name, it only remains to determine the absolute path
to the corresponding .phtml file. This is also called the view template resolving. View templates
are resolved with the special Zend Framework’s class called the view resolver.
In ZF2, there are two view resolvers out of the box: TemplatePathStack and TemplateMapResolver.
Both resolvers take a view template name as input, and return path to view template file as
output. The template name is composed of controller name followed by template name, like
“application/index/about”, “application/index/index”, “layout/layout” and so on.
• The template map resolver uses a PHP nested array to determine path to view template
file by its name. This way is fast, but you have to maintain some template map array and
update it each time you add a new view script.
• The template path stack resolver assumes that the view template name can be mapped
to directory structure. For example, “index/about” template name maps to APP_DIR/-
module/Application/view/application/index/about.phtml. This way is simpler, because you
don’t have to maintain any maps.
View resolver settings are stored inside of your module.php.config file under the view_manager
key:
Model-View-Controller 93
1 <?php
2 return array(
3 //...
4
5 'view_manager' => array(
6 //...
7
8 'template_map' => array(
9 'layout/layout' =>
10 __DIR__ . '/../view/layout/layout.phtml',
11 'application/index/index' =>
12 __DIR__ . '/../view/application/index/index.phtml',
13 'error/404' => __DIR__ . '/../view/error/404.phtml',
14 'error/index'=> __DIR__ . '/../view/error/index.phtml',
15 ),
16 'template_path_stack' => array(
17 __DIR__ . '/../view',
18 ),
19 ),
20 );
You can see that template map resolver’s settings are stored under the template_map key. By
default, there are several “standard” view templates, which are resolved this way: the index page
template, the layout template (we will talk about it in Chapter 6) and error templates (we will talk
about them a little bit later). These standard pages are served with this type of resolver, because
it is fast.
The template path stack resolver’s settings are stored under the template_path_stack key. You
can see that this resolver looks for your view scripts under the “view” directory of your module.
That’s why we could just put about.phtml file under that directory, and ZF will automatically
find the template.
The template map resolver and template path stack resolver work in pair. First, the fast template
map resolver tries to find the template view in its array map, and if the page is not found, the
template path stack resolver is executed.
For example, let’s create a DownloadController class, and add the “file” action, which would
allow site users to download files from your web site. This action does not need a corresponding
file.phtml view template, because it dumps file contents to PHP standard output stream and exits.
Add the DownloadController.php file to Controller directory of Application module, then put the
following code into the file:
Model-View-Controller 94
1 <?php
2 namespace Application\Controller;
3
4 use Zend\Mvc\Controller\AbstractActionController;
5 use Zend\View\Model\ViewModel;
6
7 /**
8 * This is the controller class for managing file downloads.
9 */
10 class DownloadController extends AbstractActionController {
11
12 /**
13 * This is the 'file' action that is invoked
14 * when a user wants to download the given file.
15 */
16 public function fileAction()
17 {
18 // Get the file name from GET variable
19 $fileName = $this->params()->fromQuery('name', '');
20
21 // Take some precautions to make file name secure
22 str_replace("/", "", $fileName); // Remove slashes
23 str_replace("\\", "", $fileName); // Remove back-slashes
24
25 // Try to open file
26 $path = './data/download/' . $fileName;
27 if (!is_readable($path)) {
28 // Set 404 Not Found status code
29 $this->getResponse()->setStatusCode(404);
30 return;
31 }
32
33 // Get file size in bytes
34 $fileSize = filesize($path);
35
36 // Write HTTP headers
37 $response = $this->getResponse();
38 $headers = $response->getHeaders();
39 $headers->addHeaderLine(
40 "Content-type: application/octet-stream");
41 $headers->addHeaderLine(
42 "Content-Disposition: attachment; filename=\"" .
43 $fileName . "\"");
44 $headers->addHeaderLine("Content-length: $fileSize");
45 $headers->addHeaderLine("Cache-control: private");
46
Model-View-Controller 95
The action method takes the name parameter from URL’s query part (line 19), removes slashes
from file name (lines 22-23), adds HTTP headers to Response object (lines 39-45) and file contents
(lines 48-55). Finally, it returns the Response object to disable the default view rendering.
Register the DownloadController class by adding the following line to your module.config.php
file:
<?php
return array(
// ...
'controllers' => array(
'invokables' => array(
// ...
'Application\Controller\Download' =>
'Application\Controller\DownloadController'
),
),
// ...
);
To see how the file download works, create APP_DIR/data/download directory and put some text
file named sample.txt in it. Then open your web browser and type the URL “http://localhost/application/downloa
in your browser’s navigation bar and press the Enter key. The browser will download the
sample.txt file and offer you to save it to some location.
error (shown in figure 4.10), and error/index which is displayed when an unhandled exception
is thrown somewhere inside of the application.
The module.config.php file contains several parameters under the view_manager key, which you
can use to configure the appearance of your error templates:
1 <?php
2 return array(
3 //...
4
5 'view_manager' => array(
6 'display_not_found_reason' => true,
7 'display_exceptions' => true,
8 //...
9 'not_found_template' => 'error/404',
10 'exception_template' => 'error/index',
11 'template_map' => array(
12 //...
13 'error/404' => __DIR__ . '/../view/error/404.phtml',
14 'error/index'=> __DIR__ . '/../view/error/index.phtml',
15 ),
16 'template_path_stack' => array(
17 __DIR__ . '/../view',
Model-View-Controller 97
18 ),
19 ),
20 );
4.13 Models
A model is a PHP class which contains the business logic of your application. The business logic
is the “core” of your web site which implements the goal of site operation. For example, if you
implement an E-shop web site, you will have models implementing the product catalog and the
shopping cart.
In general, the term model means a simplified representation of a real-life object or phenomenon.
Simplified because the real-life object has infinite amount of properties. For example, a real-
life person who visits your site consists of billions of atoms, and you cannot describe them all.
Instead, you take several properties of the object, which are the most important for your system
and ignore all others. For example, the most important properties of the site visitor (from web
site architect’s point of view) are first name, last name, country, city, ZIP code and street address.
Models can have some behavior. For example, a mailer model may send E-mail messages, the
currency converter model may be able to convert money and so on.
With ZF2, you represent models as usual PHP classes. Properties are implemented as class fields,
and the behaviors are implemented as class methods.
Services APP_DIR/module/Application/src/Application/Service
Repositories APP_DIR/module/Application/src/Application/Repository
Factories APP_DIR/module/Application/src/Application/Factory
Separation of models into different types make it easier to design your business logic
domain. This is also called the “Domain Driven Design” (or shortly, DDD). The person
who proposed DDD was Eric Evans in his famous book called Domain-Driven Design
— Tackling Complexity in the Heart of Software.
4.14.1 Entities
Entities always have some identifier property, so you can uniquely identify the object. For
example, a User entity always has a unique login property, and you can identify the user by
that attribute. You can change some other attributes of the entity, like firstName, or address,
but its identifier never changes. Entity models are usually stored in a database, in a file system
or in any other storage.
Below, you can find an example a User entity, which represents a site visitor:
20 $this->login = $login;
21 }
22
23 //...
24 }
In lines 5-10, we define User model’s properties. The best practice is to define the properties using
the private access type, and make them available to the caller through getter and setter public
methods (like getLogin() and setLogin(), etc).
Model’s behavior methods are not limited by getters and setters. You can create
other methods which manipulate with model’s data. For example, you can define the
getFullName() convenience method, which would return the user’s full name, like “Mr.
John Doe”.
1 class MoneyAmount {
2
3 // Properties
4 private $currency;
5 private $amount;
6
7 // Constructor
8 public function __construct($amount, $currency='USD') {
9 $this->amount = $amount;
10 $this->currency = $currency;
11 }
12
13 // Gets the currency code
14 public function getCurrency() {
15 return $this->currency;
16 }
17
18 // Gets the money amount
19 public function getAmount() {
20 return $this->amount;
21 }
Model-View-Controller 100
22
23 // Converts the money amount into new currency
24 public static function convert($newCurrency) {
25 // Use currency exchange rates algorithm to determine the
26 // new amount.
27 if($this->currency=='USD' && $newCurrency=='EUR') {
28 $newAmount = 1.1*$this->amount;
29 return new MoneyAmount($newAmount, $newCurrency);
30 } else {
31 throw new Exception('Unknown currency code');
32 }
33 }
34 }
In lines 4-5 we define two properties: currency and amount. The model has no identifier property,
instead it is identified by all properties as a whole: if you change either the currency or amount,
you would have a different money amount object.
In lines 8-10 we define the constructor method, which initializes the properties. In lines 14-21,
we define getter methods for model’s properties. Note that we do not have setter methods (the
model is immutable).
In lines 24-33 we have the static method convert(), which is designed to convert money amount
to another currency using some simple currency conversion algorithm. Instead of modifying the
money amount properties, it constructs and returns a new MoneyAmount object.
The real life is much more difficult than we described above, and you should be
“flexible” enough to adapt your entities and value objects to real conditions. In some
situations you will have a value object which is “mutable” (have setter methods), and
sometimes, you will have only getter methods for some of entity properties. Do not
dwell on the idealistic recommendations provided in this chapter, instead adapt your
models as your intuition advices to you.
4.14.3 Services
Service models usually encapsulate some business logic functionality. Services typically do not
have state (internal properties), instead they manipulate other entity models and value objects.
Services usually have easily recognizable names ending with “er” suffix, like FileUploader or
UserManager.
Below, an example of Mailer service is presented. It has the sendMail() method which takes an
EmailMessage value object and sends an E-mail message using standard PHP mail() function:
Model-View-Controller 101
// Constructor
public function __construct($recipient, $subject, $text) {
$this->recipient = $recipient;
$this->subject = $subject;
$this->text = $text;
}
// Getters
public function getRecipient() {
return $this->recipient;
}
return true;
}
}
Model-View-Controller 102
4.14.4 Factories
Factories are usually being designed to instantiate other models. In the simplest cases you
can create an instance of a class without any factory, just by using the new operator, but
sometimes class creation logic might be very complex, and you encapsulate it inside of a factory
class. Factory classes typically have names ending with Factory suffix, like ResourceFactory,
FileFactory, etc.
For example, let’s consider the case when you use a CAPTCHA image in a web form. The image
is a type of challenge-response test used to determine whether the user is a human or a robot.
There may be different CAPTCHA types: simple image captcha, reCAPTCHA³ and so on. Let’s
create a fictitious CaptchaFactory class, whose only goal is to create different CAPTCHA images
based on the $type argument you pass to its createCaptcha() method.
4.14.5 Repositories
Repositories are specific models responsible for storing and retrieving entities. For example, a
UserRepository may represent a database table and provide methods for retrieving User entities.
You typically use repositories when storing entities in a database. With repositories, you can
encapsulate SQL query logic in the single place and easily maintain and test it.
We will learn about repositories in more details in Chapter 12, when talking about
Doctrine library.
³http://www.google.com/recaptcha
Model-View-Controller 103
When writing your own application having specific model domain, you may be confused when
trying to decide to which model type your class belongs (whether it is an entity, value object,
repository, service or factory). Below, a simple algorithm is provided to make it easier for you to
determine the correct model type when writing your own application:
If nothing above matches your model specification, then your model probably is either a service
or a value object. If your model does not have a state, put it to services, otherwise to value objects.
• accesses user request data ($_GET, $_POST, $_FILES and other PHP variables);
• (optionally) makes some basic preparations to the data;
• instatiates the model class(es) (or gets service(s) registered in the ServiceManager);
• passes the data to model(s) and retrieves the result returned by the model(s);
• and finally returns the output data as a part of a ViewModel variable container.
• Takes the data passed by site user (line 6). This data is usually part of Request object and
can be retrieved using the controller’s getRequest() method or Params controller plugin.
• Performs the basic check on the data passed by user (line 7), and if the data is missing (or
invalid), sets an HTTP error code (line 9). Here, we only check if the variable “amount”
is present, because otherwise we can’t pass the money amount to the model class. More
complex parameter checks should be performed inside of the model class.
• Creates the CurrencyConverter model (line 14) and passes the money amount to the model
by calling its convertEURtoUSD() method. The method then returns the converted amount.
• Constructs the ViewModel variable container and passes the resuting data to it (line
17). This variable container can be further accessed in the corresponding view template
responsible for data presentation.
• Performs complex data filtering and validation. Because the data that you retrieved in
controller is passed to your application from an outside world, in your model, you have to
take a lot of effort to verify the data and ensure the data will not break your system. This
results in a secure web site resistent to hacker attacks.
• Performs data manipulation. Your models should manipulate the data: e.g. load the data
from database, save it to database and transform the data. Models are the right place for
storing database queries, file reading and writing functionality, and so on.
• Access the data from the HTTP request, $_GET, $_POST and other PHP variables. The
controller’s work is to extract that data and pass it to model’s input.
• Produce HTML or other code specific to presentation. The presentational code may vary
depending on the user request, and it is better to put it in a view template.
Model-View-Controller 106
If you follow this principles, you will encounter that your models are easy to test, because they
have clearly identified input and output. You can write a unit test which passes some test data
to input end of the model, retrieves the output data and verifies that the data is correct.
If you are confused whether to put certain code in a controller or in a model, ask yourself: is this
an important logic that needs to be carefully tested? If the answer is yes, you should put the code
in a model.
If you follow these principles, you will encounter that your views can easily be substituted
without modifying the business logic of your application. For example, you can easily change
the design of your web pages, or even introduce changable themes.
4.17 Summary
A Zend Framework 2 based web site is just a PHP program receiving an HTTP request from
the web server, and producing an HTTP response. The web application uses the Model-View-
Controller pattern to separate business logic from presentation. The goal of this is to allow for
code reusability and separation of concerns.
A controller is a mediator between the application, models and views: it gets input from HTTP
request and uses the model(s) and the corresponding view to produce the necessary HTTP
response. A controller is a usual PHP class containing action methods.
Views are simple HTML+PHP code snippets producing HTML output returned by the web server
to site visitors. You pass the data to view scripts through the ViewModel variable container.
A model is a PHP class which contains the business logic of your application. The business logic
is the “core” of your web site which implement the goal of site operation. Models can access
database, manipulate disk files, connect to external systems, manipulate other models and so on.
5. URL Routing
When a site user enters a URL in a web browser, the request is finally dispatched to controller’s
action. In this chapter, we will learn about how ZF2-based application maps page URLs to
controllers and their actions. This mapping is accomplished with the help of routing. Routing
is implemented as a part of Zend\Mvc component.
ZF2 components covered in this chapter:
Component Description
Zend\Mvc Implements support of MVC and routing.
Zend\Barcode Auxiliary component implementing barcodes.
This URL begins with a scheme segment (the scheme typically looks like http or https).
Then, the host name segment follows which is the domain name of your web server (like
site1.yourserver.com). Optional path segments follow the host name. So if you have the path
part “/path/to/page” then “path”, “to”, and “page” would each be a URL segment. Next, after
the question mark, the optional query part follows. It consists of one or several “name=value”
parameters separated from each other by an ampersand character (‘&’).
Each segment in a URL uses special character encoding, which is named the URL encoding. This
encoding ensures that the URL contains only “safe” characters from the ASCII ¹ table. If a URL
contains unsafe characters, they are replaced with a percentage character (‘%’) followed by two
hexadecimal digits (for example, the space character will be replaced by ‘%20’).
¹ASCII (American Standard Code for Information Interchange) is a character set which can be used to encode characters from the English
alphabet. It encodes 128 characters: digits, letters, punctuation marks and several control codes inherited from Teletype machines.
URL Routing 108
Each route type in the table above (except the Method type) may be matched against a specific
segment (or several segments) of a URL. The Method route type is matched against the HTTP
method (either GET or POST) retrieved from HTTP request.
There is also the Query route type, which is declared deprecated and is not recom-
mended to use. This route type is actually not needed, because you can retrieve query
parameters from your URL with the Params controller plugin (see the Retrieving Data
from HTTP Request section in Chapter 4).
URL Routing 109
The TreeRouteStack and SimpleRouteStack are used as the “top-level” route types. The
SimpleRouteStack allows to organize different routing rules in a priority list. The TreeRouteStack
allows to nest different routing rules, forming a “tree”.
Figure 5.2 shows the route class inheritance diagram.
As you can see from the image, all route classes are inherited from RouteInterface interface (we
will learn this interface in details in the Writing Own Route Type section later in this chapter).
The SimpleRouteStack is a parent class for TreeRouteStack class, which inherits the behavior
of the simple route stack (allows to organize routes in priority list) and extends it (allows to
organize routes in subtrees). The Part and Chain classes are derived from TreeRouteStack class
and are used internally by the TreeRouteStack for building subtrees and chains of child routes.
URL Routing 110
You may notice that the SimpleRouteStack class lives in the Zend\Mvc\Router names-
pace, while other route classes live in its sub-namespace Zend\Mvc\Router\Http. This
is because routing is also used for mapping shell commands to controllers in console
applications. Thus, console route classes will live in Zend\Mvc\Router\Console, while
the SimpleRouteStack compound route type will be used for both HTTP routing and
console routing.
Figure 5.3. An example of Simple Route Stack (left) and Tree Route Stack (right)
a branch containing a single Segment route, and a branch consisting of Scheme, Hostname and
Segment routes.
The tree route stack performs request matching in the following way. It walks through its priority
list items (denoted by dashed lines in figure 5.3), starting from high-priority routes. If a certain
item is a Chain route or a Part route, it processes such a nested route from its parent route to
children. If the parent route matches, the children (denoted with solid lines) are analyzed then.
The nested route is considered matching if at least one route matches in each tree (or chain) level.
Each route in a tree (or chain) consumes a part of the URL (figure 5.4). The parent route is
matched against the first segment (or several segments) of the URL, its child is matched again
the next segment (or several segments), and so on, until the end of the URL string is reached.
1 <?php
2 return array(
3 //...
4 'router' => array(
5 'router_class' => 'Zend\Mvc\Router\Http\TreeRouteStack',
6 'routes' => array(
7 // Register your routing rules here...
8 ),
9 'default_params' => array(
10 // Specify default parameters here for all routes here ...
11 )
URL Routing 112
12 ),
13 );
Above, in line 4 we have the router key, under which there is the routes subkey (line 6), containing
the routing rules.
You can specify which top-level route class to use (either TreeRouteStack or SimpleRouteStack)
with the router_class parameter (line 5). If this parameter is not present, the TreeRouteStack
is used by default.
You can use the optional default_params key (line 9) to define the default parameters for all
routes at once. However, you typically do not use this key and define the defaults on a per-route
basis.
Above, the <route_name> placeholder should be the name of the route. A route name must be in
lower case, like “home” or “about”. The type key specifies the route class name. It can be either
the full class name, like “ZendMvcRouterHttpLiteral”, or a short alias, like “Literal”.
The optional priority key allows to define the priority (which should be an integer number)
of the route in the priority list (routes with higher priority will be visited first). If you omit the
priority key, the routes will be visited in the LIFO ² order.
Routes having equal priority will be visited in the LIFO order. Thus, for the best
performance, you should register routes that will match most often in the last turn,
and least common routes should be registered first.
The options key defines the array of route’s options. We will discuss the options in the following
sections of this chapter.
If you need to organize the routes in a chain (degenerated subtree), you add the chain_routes
key to your route configuration:
Looking at the two examples above, you won’t see the explicit usage of Part and Chain
route types, because (for your convenience) they are used by the ZF2 automatically
when it encounters the child_routes and chain_routes keys in your routing config-
uration.
1 <?php
2 return array(
3 'router' => array(
4 'routes' => array(
5 'home' => array(
6 'type' => 'Zend\Mvc\Router\Http\Literal',
7 'options' => array(
8 'route' => '/',
9 'defaults' => array(
10 'controller' => 'Application\Controller\Index',
11 'action' => 'index',
12 ),
13 ),
14 ),
15 // The following is a route to simplify getting started creating
16 // new controllers and actions without needing to create a new
17 // module. Simply drop new controllers in, and you can access them
18 // using the path /application/:controller/:action
19 'application' => array(
20 'type' => 'Literal',
21 'options' => array(
22 'route' => '/application',
23 'defaults' => array(
24 '__NAMESPACE__' => 'Application\Controller',
25 'controller' => 'Index',
26 'action' => 'index',
27 ),
28 ),
29 'may_terminate' => true,
30 'child_routes' => array(
31 'default' => array(
32 'type' => 'Segment',
33 'options' => array(
34 'route' => '/[:controller[/:action]]',
35 'constraints' => array(
36 'controller' => '[a-zA-Z][a-zA-Z0-9_-]*',
37 'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
38 ),
39 'defaults' => array(
40 ),
41 ),
42 ),
43 ),
44 ),
45 ),
46 ),
URL Routing 115
47
48 //...
49 );
This configuration corresponds to the tree route stack shown in figure 5.5:
In the configuration presented above, we have two routing rules listed in turn: first we have the
“home” route (line 5) and then we have the compound route (line 19), consisting of two routes.
The parent route is named “application”. You can see that under the child_routes subkey it has
the child rule named “default”. This compound route also has a name: it is the concatenation of
all names of routes it consists of (“application/default”).
The may_terminate parameter (line 29) of the parent route defines whether the child route must
always match or the child route is optional. If the may_terminate parameter is true and the
end of the URL is reached, the child route is ignored and the route is treated as matching. If
the may_terminate parameter is false and the last segment of the URL is reached, the route is
treated as non-matching.
In the next sections, we will provide some examples on how to use the route types in your web
site.
Line 2 of this example says that the route’s type is Literal. The actual route matching algorithm
is implemented in the Zend\Mvc\Router\Http\Literal class. You can either specify the full class
name or its short alias (Literal).
Line 4 defines the route string to match against the URL path (the forward slash ‘/’ means the
empty URL part). Because we have the literal route type, the route match is achieved only when
you have the exact literal path match. For example, if you have the URL “http://localhost/” or
“http://localhost”, it will match the ‘/’ route string.
Lines 5-8 define the defaults, which are the parameters returned by the router if the route
matches. The controller and action parameters define the controller and controller’s action
method which should be executed. You can also define other parameters here, if needed.
As another example of the Literal route type, let’s add the ‘/about’ route for the About page
we’ve created earlier in the Views section of Chapter 4. To create the route, add the following
lines right after the “home” rule definition inside of your module.config.php file:
If you now open the “http://localhost/about” URL in your web browser, you should see the About
page.
If you look at the module.php.config file, you can see the Segment route type is used
inside of the “application/default” route to make actions of your controllers automat-
ically mapped to site URLs. You just add an action method to your controller, and
it becomes available by a URL like “http://localhost/<module>/<controller>/<action>”.
For example, you can see the About page of your site with the following URL:
“http://localhost/application/index/about”.
To demonstrate the creation of the Segment route type, let’s implement a controller action
which will generate a simple barcode image. Barcodes are widely used in supermarkets for
optically recognizing goods in your shopping cart. The barcodes may be of different types and
have different labels. We will use the Segment route type to map the action to a URL like
“http://localhost/barcode/<type>/<label>”.
First, we define the “barcode” routing rule in the module.config.php file:
Segments of the route string (line 4) may be constant or variable. You can define the variable
segments by using “wildcards”. We have three segments: barcode, :type and :label. The
barcode segment is constant, while the latter two are wildcards (wildcard’s name should start
with a colon).
You specify how a wildcard should look like inside of the constraints subkey (lines 5-8). We
define the regular expression [a-zA-Z][a-zA-Z0-9_-]* which constraints our :type wildcard
to begin with a letter and contain one or several letters, digits, underscores or minus characters.
The constraint for the :label wildcard is almost the same, but this segment can start with any
allowed character (either letter, digit, underscore or minus sign character).
Optional segments can be enclosed in square brackets. In our example, we have both the :type
and :label segments as optional.
In lines 9-12, we define the defaults, the parameters that will be returned by the router. The
controller and action defaults specify which controller and action method to execute on route
match.
Next, we add the barcodeAction() method into the IndexController class:
URL Routing 118
In lines 10-11 we get the values of the type and label wildcards from route. We do that with the
help of Params controller plugin’s fromRoute() method. Analogous to fromQuery() method, it
takes two arguments: the variable name and its default value.
For generating the barcode image, we use the Zend\Barcode component. In line 14 we define the
label text for the barcode. In lines 18-19 we create the Barcode object with the factory method.
Finally, in line 23 we render the image file by dumping it to PHP output stream.
In line 26 we return the Response object to suppress the default view rendering.
Now, enter the “http://localhost/barcode” URL into your browser to see the barcode image
(shown in figure 5.6):
URL Routing 119
Please note that for barcode images to work, you need to have the GD³ extension of
the PHP engine installed and enabled. Please refer to Appendix A for instructions on
how to do that.
Because we have the wildcards in the route, you can pass the type and label parameters of the
barcode image in the URL. Below, several URL examples are provided (corresponding barcodes
are presented in figure 5.7):
a. http://localhost/barcode/code39/HELLO-WORLD
b. http://localhost/barcode/leitcode/12345
c. http://localhost/barcode/identcode/98765453212
d. http://localhost/barcode/postnet/123456
e. http://localhost/barcode/planet/1234567890123
f. http://localhost/barcode/upca/12345678901
g. http://localhost/barcode/code128/ABCDEF
h. http://localhost/barcode/ean2/12
³PHP GD extension allows to create image files in different formats (like JPEG, PNG, GIF, etc.)
URL Routing 120
By the term “static page” we refer to a page which mostly contains static HTML code
plus several PHP inline fragments. For such simple pages you do not need to create
separate controller actions. All “static” pages can be served by the single controller
action.
Let’s implement the route which will serve the “static” pages of the site. Because “static” pages
are simple, you typically won’t need to add per-page action methods to the controller. All pages
will be handled by the single action IndexController::docAction().
First, we add the Regex route named “doc” to the module.config.php file:
Line 2 defines the Regex type for the route. In line 4, we have the regular expression /doc(?<page>\/[a-zA-Z0-9_-
\-]+)\.html. It will match to URLs like “/doc/contents.html”, “/docs/introduction.html” and so
on. The expression contains the named capture ⁴ “page”, which will be returned by the router on
match together with the default parameters.
Line 9 contains spec option, which is used for generating URLs by route (we will discuss
generating URLs by route later in this chapter).
Next, add the following action to IndexController class:
⁴In PHP PCRE regular expressions, it is possible to name a sub-pattern using the syntax (?P<name>pattern). This sub-pattern will then be
indexed in the matches array by its name.
URL Routing 121
In lines 3-4 above, we retrieve the page parameter from route (remember the “page” named
capture from our regular expression?) and save it as the $pageTemplate variable. We will use the
$pageTemplate variable for determining the view template name to pass to the view resolver.
Then, in lines 6-10, we check if such a file name is present, and if not, return the 404 “Not Found”
status code, which will force ZF2 to display the error page. In line 12, we create the ViewModel
variable container, and in line 13 we explicitly set the view template name for rendering.
To see the documentation system in action, create a couple of “static” view template files: the
Table of Contents page (contents.phtml) and the Introduction page (introduction.phtml).
Create the doc subdirectory under the view/application/index directory of the Application
module and put the contents.phtml view template there:
1 <h1>Table of Contents</h1>
2
3 <ul>
4 <li>
5 <a href="<?php echo $this->url('doc',
6 array('page'=>'introduction')); ?>">
7 Introduction
8 </a>
9 </li>
10 </ul>
In the lines above, we provide the HTML code for the “Table of Contents” page header, and the
list which contains the single item named “Introduction” pointing to the Introduction “static”
page. The link URL is generated with the Url view helper (for more details on the Url helper, see
further sections in this chapter).
Then add the introduction.phtml page into the same doc directory:
URL Routing 122
1 <h1>Introduction</h1>
2
3 <p>Some introductory materials.</p>
In the lines above, we define the HTML markup for the simple Introduction page.
Now, if you open the “http://localhost/doc/contents.html” URL in your browser, you should see
a nice simple documentation system which you can extend and use in your site (figure 5.8):
Clicking the Introduction link will direct you to the “Introduction” static page. You can also add
other pages to the doc directory to make them automatically available for site users through our
Regex route.
One disadvantage of such a documentation system is that it does not work well if
you place nested pages in subdirectories under the doc directory. The reason of this
limitation lies in the way the Regex route assembles URLs. You can’t generate URLs
containing slash characters, as these “unsafe” characters will be automatically URL-
encoded. We will work-around this problem with our custom route type that we will
create at the end of this chapter.
To demonstrate the usage of the Wildcard route type, let’s assume we need to create an action
that would display blog post(s) published during some period of time. For this action, we would
like to use URLs looking like “http://localhost/blog/year/2013/month/April/name/my-vacation”.
First, let’s add the blogAction() method to our IndexController class:
In the code above, we get $year, $month and $name parameters from route as usual (lines 3 - 5).
Because the parameters are optional, we echo different text to the screen based on which
parameters are present. If the user provided the name of the blog post to show, we display its
name, otherwise we display all blog posts for the period. If the user provided the year and month,
we display the blog post(s) for this year and month; if the user provided only the year, we display
blog posts for the year. If the user didn’t provide neither year nor month, we display all posts for
the period.
Next, define the routing configuration for this action. Add the following lines under your router
key:
URL Routing 124
The key_value_delimiter option defines the character which separates key/value pairs, typi-
cally you set this with slash (‘/’) character, but you can use any other one. The param_delimiter
option defines the character which separates the key and value within the pair. Typically, this is
also the ‘/’ character.
To see how the wildcard route works, type the following URL in your web browser’s nav-
igation bar: “http://localhost/blog/year/2013/month/April/name/my-vacation”. You should see
the following text in your browser: “You requested to see the blog post named “my-vacation”
published in April 2013”. If you enter the URL “http://localhost/blog/year/2013”, you should see
the following output: “You requested to see all blog posts published in 2013”.
⁵A Content Management System (CMS) is a web site allowing for collaborative creating, editing and publishing content (blogs, pages,
documents, videos etc.) using a centralized web interface. CMS systems make it possible for non-programmers to perform the web site’s daily
tasks, like content publishing.
URL Routing 125
In the example above, in line 1 we define the route which has the Hostname type. The route
option (line 4) defines the domain name to match against. The :subdomain is a wildcard, which
can take different sub-domain values. The constraints key defines the regular expression this
sub-domain parameter must match. The Hostname route will differentiate your domains, so each
site will behave differently, depending on the value of the subdomain parameter returned:
The Scheme route type is useful if you need to handle HTTP and HTTPS ⁶ protocols in different
ways.
The typical Scheme route configuration is presented below:
⁶The HTTPS protocol is typically used for secure connections, like account page or shopping cart page. When you use HTTPS, the request
data is tunnelled through Secure Socket Layer (SSL) channel and not available to third parties.
URL Routing 126
Above, we define the route of type Scheme. It takes the scheme option, which should be the
scheme to match against (like http or https). If the scheme in HTTP request’s URL is exactly
the same as the scheme option, the route is considered matching. You can use the defaults key
to return some parameters on route match. In the example above, the https boolean parameter
will be returned.
The Method route type can be used if you need to direct GET and POST requests into different
controller’s actions. Its typical configuration is presented below:
Above, we define the route which has the Method type. It takes the verb option, which may be
the comma-separated list of acceptable HTTP verbs (like get, post, put, etc.)
To retrieve a parameter from the route in your controller’s action method, you typically use the
Params controller plugin and its fromRoute() method, which takes two arguments: the name of
the parameter to retrieve and the value to return if the parameter is not present.
The fromRoute() method can also be used to retrieve all parameters at once as an array. To do
that, call the fromRoute() without arguments, as shown in the example below:
// An example action.
public function someAction() {
//...
}
In most cases, it will be sufficient to use the Params controller plugin, but alternatively
you can use the RouteMatch object for accomplishing the same task.
To get the RouteMatch object from your controller’s action method, you can use the following
code:
URL Routing 128
1 // An example action.
2 public function someAction() {
3
4 // Get the RouteMatch object.
5 $routeMatch = $this->getEvent()->getRouteMatch();
6
7 // Get matched route's name.
8 $routeName = $routeMatch->getMatchedRouteName();
9
10 // Get all route parameters at once as an array.
11 $params = $routeMatch->getParams();
12
13 //...
14 }
In line 5 of the code above, we use the getEvent() method of the AbstractActionController
base class to retrieve the MvcEvent object, which represents the event (in ZF2, the application life
cycle consists of events). We then use the the getRouteMatch() method of the MvcEvent class to
retrieve the RouteMatch object.
In line 8, we use the getMatchedRouteName() method to retrieve the name of the route that
matched the HTTP request, and in line 11 we retrieve all the parameters from the route.
The MvcEvent class can also be used for retrieving the router (the top-level route class). You can
do this with the getRouter() method of the MvcEvent class, as below:
In the code above, we use the getRouter() method, which returns the RouteStackInterface
interface. This interface is the base interface for both SimpleRouteStack and TreeRouteStack,
and it provides the methods for working with the routes contained inside the route stack.
tag having href attribute specifying the URL of the destination page. Below, an example of a
hyperlink pointing to an external page is presented:
<a href="http://example.com/path/to/page">A link to another site page</a>
When you generate a hyperlink to a resource internal to your site, you typically use relative URL
(without host name):
<a href="/path/to/internal/page">A link to internal page</a>
To generate URLs in your view templates (.phtml files), you can use the Url view helper class,
which takes the route name as an input argument:
In the lines above, we generate two relative URLs. In line 2, we call the Url view helper and
pass the “home” route name as its parameter. In line 5, we pass the “about” route name as an
argument for the Url view helper.
In the example above, the Url view helper internally uses the RouteMatch object and
calls the Literal route to assemble the URL string by route name.
After the PhpRenderer class executes the view template’s code, the output HTML markup will
be the following:
If a route uses some variable parameters, you should pass them to the Url view helper as the
second argument:
URL Routing 130
In the example above, we use Url view helper to generate the two URLs by route name and
parameters. We pass the “application/default” compound route name as the first argument, and
an array of parameters as the second argument.
In line 3, we pass the “controller” and “action” parameters to tell the Segment route class that it
should substitute the corresponding wildcards in the route string with the “index” and “about”
strings.
After the PhpRenderer class executes the view template’s code, the output HTML markup will
be the following:
As another example, let’s try to generate a URL for our Regex route (the one which serves our
“static” pages):
If you need to generate an absolute URL (having the scheme and host name), you can specify
the third parameter for the Url view helper. The third parameter should be an array containing
one or several options. For assembling the absolute URL, pass the force_canonical option, as in
the example below:
URL Routing 131
In lines 2-3 of the example above, we pass the “home” route name as the first argument, empty
array as the second argument, and an array containing force_canonical option as the third
argument. In lines 6-8, we also pass the force_canonical option as the third argument for
generating the URL of the About page.
The resulting HTML markup of the code above will be as follows:
If you want your URL to have a query part, you can specify the query option in the third
argument of the Url view helper. For example, assume you have the “search” action in some
controller (and a routing rule mapped to this action), and you want to pass it search query
string and count of output results per page. The URL for this action would be like this:
“http://localhost/search?q=topic&count=10”. To generate such a URL, you can use the following
code:
In the code above, we specified the query option, which is the array containing name⇒value
pairs of the query parameters.
The arguments the Url plugin takes and their meaning are identical to the Url view
helper’s ones. So, you can generate absolute or relative URLs the same way you did in
your view templates.
We could assume it generates the URL like “/doc/chapter1/introduction.html”. But because the
slash (‘/’) character is unsafe, it will be replaced with the “%2F” characters for security reasons,
and we will have the following HTML code:
Unfortunately, this hyperlink is unusable, because it won’t match our Regex route.
URL Routing 133
5.12.1 RouteInterface
We know that every route class must implement the Zend\Mvc\Router\Http\RouteInterface
interface. The methods of this interface are presented in table 5.4:
The static factory() method is used by the ZF2 router (TreeRouteStack or SimpleRouteStack)
for instantiating the route class. The router passes the options array an argument for the
factory() method.
The match() method is used to perform the matching of the HTTP request (or, particularly its
URL) against the options data passed to the route class through the factory(). The match()
method should return either an instance of the RouteMatch class on successful match, or null
on failure.
The assemble() method is used for generating URL string by route parameters and options. The
getAssembledParams() helper method’s purpose is to return the array of parameters which were
used on URL generation.
will be URL-encoded making the hyperlink unusable). We will create our custom StaticRoute
class that allows to fix this issue.
Moreover, the class we will create is more powerful, because it will not only recognize URLs
starting with “/doc” and ending with “.html”, but it will also recognize generic URLs, like “/help”
or “/support/chapter1/introduction”.
What we want to achieve:
• The StaticRoute class should be insertable to the route stack (to SimpleRouteStack or to
TreeRouteStack) and usable together with other route types.
• The route class should recognize generic URLs, like “/help” or “/introduction”.
• The route class should match the URL against the directory structure. For example, if
the URL is “/chapter1/introduction”, then the route should check if the corresponding
view template file <base_dir>/chapter1/introduction.phtml exists and is readable, and if
so, report match. If the file does not exist (or not readable), return the failure status.
• The route class should check the URL for acceptable file names using a regular expression.
For example, the file name “introduction” is acceptable, but the name “*int$roduction” is
not. If the file name is not acceptable, the failure status should be returned.
• The route should be able to assemble the URL string by route name and parameters.
To start, create the Service subdirectory under the module’s source directory and put the
StaticRoute.php file inside of it (figure 5.9). Inside that file, paste the stub code presented below:
1 <?php
2 namespace Application\Service;
3
4 use Traversable;
5 use \Zend\Mvc\Router\Exception;
6 use \Zend\Stdlib\ArrayUtils;
7 use \Zend\Stdlib\RequestInterface as Request;
8 use \Zend\Mvc\Router\Http\RouteInterface;
9 use \Zend\Mvc\Router\Http\RouteMatch;
10
11 // Custom route that serves "static" web pages.
12 class StaticRoute implements RouteInterface
13 {
14 // Create a new route with given options.
15 public static function factory($options = array()) {
16 }
17
18 // Match a given request.
19 public function match(Request $request, $pathOffset = null) {
20 }
21
22 // Assembles a URL by route params.
23 public function assemble(array $params = array(), array $options = array())\
24 {
25 }
26
27 // Get a list of parameters used while assembling.
28 public function getAssembledParams() {
29 }
30 }
From the code above, you can see that we placed the StaticRoute class inside the Application\Service
namespace (line 2), because it can be treated as a service model.
In lines 4-9, we define some class name aliases for making the class names shorter.
In lines 11-28, we define the stub for the StaticRoute class. The StaticRoute class implements
the RouteInterface interface and defines all the methods specified by the interface: factory(),
match(), assemble() and getAssembledParams().
Next, let’s add several protected properties and the constructor method to the StaticRoute class,
as shown below:
URL Routing 136
1 <?php
2 //...
3
4 class StaticRoute implements RouteInterface
5 {
6 // Base view directory.
7 protected $dirName;
8
9 // Path prefix for the view templates.
10 protected $templatePrefix;
11
12 // File name pattern.
13 protected $fileNamePattern = '/[a-zA-Z0-9_\-]+/';
14
15 // Defaults.
16 protected $defaults;
17
18 // List of assembled parameters.
19 protected $assembledParams = array();
20
21 // Constructor.
22 public function __construct($dirName, $templatePrefix,
23 $fileNamePattern, array $defaults = array())
24 {
25 $this->dirName = $dirName;
26 $this->templatePrefix = $templatePrefix;
27 $this->fileNamePattern = $fileNamePattern;
28 $this->defaults = $defaults;
29 }
30
31 // ...
32 }
Above, in line 7, we define the $dirName property that is intended for storing the name of
the base directory where the “static” view templates will be located. In line 10, we define the
$templatePrefix class variable for storing the prefix for prepending to all view template names.
Line 13 contains the $fileNamePattern variable that will be used for checking the file name.
In lines 22-29, we define the constructor method that is called on instance creation for initializing
the protected properties.
Next, let’s implement the factory() method for our StaticRoute custom route class. The
factory() method will be called by the router for instantiating the route class:
URL Routing 137
1 <?php
2 //...
3
4 class StaticRoute implements RouteInterface
5 {
6 //...
7
8 // Create a new route with given options.
9 public static function factory($options = array())
10 {
11 if ($options instanceof Traversable) {
12 $options = ArrayUtils::iteratorToArray($options);
13 } elseif (!is_array($options)) {
14 throw new Exception\InvalidArgumentException(__METHOD__ .
15 ' expects an array or Traversable set of options');
16 }
17
18 if (!isset($options['dir_name'])) {
19 throw new Exception\InvalidArgumentException(
20 'Missing "dir_name" in options array');
21 }
22
23 if (!isset($options['template_prefix'])) {
24 throw new Exception\InvalidArgumentException(
25 'Missing "template_prefix" in options array');
26 }
27
28 if (!isset($options['filename_pattern'])) {
29 throw new Exception\InvalidArgumentException(
30 'Missing "filename_pattern" in options array');
31 }
32
33 if (!isset($options['defaults'])) {
34 $options['defaults'] = array();
35 }
36
37 return new static(
38 $options['dir_name'],
39 $options['template_prefix'],
40 $options['filename_pattern'],
41 $options['defaults']);
42 }
In the code above, we see that the factory() method takes the options array as the argument
(line 9). The options array may contain the options for configuring the route class. The
StaticRoute class will accept the following options:
URL Routing 138
• dir_name - the base directory where to store all “static” view templates.
• template_prefix - the prefix to prepend to all template names.
• filename_pattern - the regular expression for checking the file names.
• defaults - parameters returned by router by default.
Once we parsed the options, in lines 37-41 we call the class’ constructor method to instantiate
and return the StaticRoute object.
The next method we add to the StaticRoute route class is the match() method:
1 <?php
2 //...
3
4 class StaticRoute implements RouteInterface
5 {
6 //...
7
8 // Match a given request.
9 public function match(Request $request, $pathOffset=null)
10 {
11 // Ensure this route type is used in an HTTP request
12 if (!method_exists($request, 'getUri')) {
13 return null;
14 }
15
16 // Get the URL and its path part.
17 $uri = $request->getUri();
18 $path = $uri->getPath();
19
20 if($pathOffset!=null)
21 $path = substr($path, $pathOffset);
22
23 // Get the array of path segments.
24 $segments = explode('/', $path);
25
26 // Check each segment against allowed file name template.
27 foreach ($segments as $segment) {
28 if(strlen($segment)==0)
29 continue;
30 if(!preg_match($this->fileNamePattern, $segment))
31 return null;
32 }
33
34 // Check if such a .phtml file exists on disk
35 $fileName = $this->dirName . '/'.
36 $this->templatePrefix.$path.'.phtml';
URL Routing 139
37 if(!is_file($fileName) || !is_readable($fileName)) {
38 return null;
39 }
40
41 $matchedLength = strlen($path);
42
43 // Prepare the RouteMatch object.
44 return new RouteMatch(array_merge(
45 $this->defaults,
46 array('page'=>$this->templatePrefix.$path)
47 ),
48 $matchedLength);
49 }
50 }
In the code above, we see that the match() method takes two arguments: the HTTP request object
(an instance of Zend\Stdlib\Request class) and the URL path offset. The request object is used
for accessing the request URL (line 17). The path offset parameter is a non-negative integer,
which points to the portion of the URL the route is matched against (line 21).
In line 24, we extract the segments from URL. Then we check if every segment is an acceptable
file (directory) name (lines 27-321). If the segment is not a valid file name, we return null as a
failure status.
In line 35, we calculate the path to the view template, and in lines 27-29 we check if such a
file really exists and accessible for reading. This way we match the URL against the directory
structure.
In lines 44-48, we prepare and return the RouteMatch object with the default parameters plus the
“page” parameter containing the view template name for rendering.
To complete the implementation of our StaticRoute class, we add the assemble() and getAssembledParams()
methods, that will be used for generation of URLs by route parameters. The code for these
methods is presented below:
1 <?php
2 //...
3
4 class StaticRoute implements RouteInterface
5 {
6 //...
7
8 // Assembles a URL by route params
9 public function assemble(array $params = array(),
10 array $options = array())
11 {
12 $mergedParams = array_merge($this->defaults, $params);
13 $this->assembledParams = array();
URL Routing 140
14
15 if(!isset($params['page'])) {
16 throw new Exception\InvalidArgumentException(__METHOD__ .
17 ' expects the "page" parameter');
18 }
19
20 $segments = explode('/', $params['page']);
21 $url = '';
22 foreach($segments as $segment) {
23 if(strlen($segment)==0)
24 continue;
25 $url .= '/' . rawurlencode($segment);
26 }
27
28 $this->assembledParams[] = 'page';
29
30 return $url;
31 }
32
33 // Get a list of parameters used while assembling.
34 public function getAssembledParams()
35 {
36 return $this->assembledParams;
37 }
38 }
In the code above, we define the assemble() method, which takes the two arguments: the
parameters array and the options array (line 10). The method constructs the URL by encoding
the segments with URL encoding and concatenating them (line 20-26).
The method getAssembledParams() just returns the names of the parameters we used for URL
generation (page 36).
Now we’ve finished the StaticRoute route class. To use our custom route type, we add the
following configuration to the module.config.php configuration file:
In line 1 of the configuration above, we define the routing rule named “static”. The type
parameter defines the full StaticRoute class name for the rule (line 2). In the options array, we
define the base directory where the “static” pages will be placed (line 4), the template prefix (line
5), the filename pattern (line 6), and the defaults array, containing the name of the controller
and the action that will serve all the static pages.
The final step is creating the action method in the IndexController class:
The action above is almost identical to the action we used for the Regex route. In line 4, we
retrieve the page parameter from route and save it as the $pageTemplate variable. In line 121,
we create the ViewModel variable container, and in line 12 we explicitly set the view template
name for rendering.
To see the system in action, let’s add a couple of “static” view pages: the Help page (help.phtml)
and the introduction page (intro.phtml). Create the static subdirectory under the view/applica-
tion/index directory of the Application module and put the help.phtml view template there:
1 <h1>Help</h1>
2
3 <p>
4 See the help <a href="<?php echo $this->url('static',
5 array('page'=>'/chapter1/intro')); ?>">introduction</a> here.
6 </p>
Then create the chapter1 subdirectory in the static directory and put the following chap-
ter1/intro.phtml file in there:
URL Routing 142
<h1>Introduction</h1>
<p>
Write the help introduction here.
</p>
Finally, you should receive the following directory structure (see figure 5.10):
Eventually, open the following URL in your browser: http://localhost/help. The Help page should
appear (see figure 5.11 for example). If you type the http://localhost/chapter1/intro URL in your
browser, you should see the Introduction page (figure 5.12).
You can create static pages just by adding the phtml files under the static directory, and they will
automatically become available to site users.
If you are stuck, you can find this complete working example inside the Hello World
application.
5.13 Summary
In this chapter, we’ve learned about routing. Routing is used for mapping HTTP request to
controller’s action method. There are several route types (Literal, Segment, Regex, Hostname,
Scheme, Method etc.). Each route type uses different URL segments (and, possibly, other HTTP
request’s data) to compare the URL with the specified route template. We also learned how to
write custom route class if the capabilities of standard route types are not sufficient.
The main task of a route class is to return a route match containing the set of parameters, by
which a controller and action can be determined. An opposite task a route class allows to do is
generating a URL by parameters. This feature is widely used in view layer of the application for
generating hyperlinks.
Route types can be combined in a nested tree with the help of TreeRouteStack router, or
organized in a chain with SimpleRouteStack router. These two routers allow to define arbitrarily
complex rules.
Routing configuration is stored in module’s configuration file under the router key. Each
module exposes its own routing rules, which are merged with other modules’ configuration
upon application start up.
6. Page Appearance and Layout
In this chapter you will learn how to make your web pages attractive and professionally looking
with the help of Twitter Bootstrap CSS Framework and how to position elements on a page
using ZF2 layout mechanism. You’ll also become familiar with common view helpers allowing
for composing web pages of reusable parts. If you are new to Twitter Bootstrap, it is also
recommended that you refer to Appendix C for advanced description of Bootstrap capabilities.
ZF2 components covered in this chapter:
Component Description
Zend\Mvc Support of MVC pattern. Implements base controller classes, controller plugins, etc.
Zend\View Implements the functionality for variable containers, rendering a web page and
common view helpers.
Twitter Bootstrap is shipped with Zend Skeleton Application, so you can use it out of
the box. Alternatively, you can download the newest version of Bootstrap from the
project’s official page⁴. At the moment of writing this book, the latest version is v.3.0.
• It provides the CSS reset that is a style sheet defining styles for all possible HTML elements.
This ensures your web site will look the same way in all web browsers.
• It provides the base CSS rules that define style of typography (headings and text), tables,
forms, buttons, images and so on.
• It defines the grid system. The grid system allows to arrange elements on your web page
in a grid-like structure. For example, look at the Skeleton Application’s main page (figure
6.1), where we have the grid consisting of three columns.
• It defines useful web interface components like dropdown menus, navigation bars, bread-
crumbs, pagination and so on. For example, on the skeleton app’s main page, there is
the navigation bar component at the top, and the header (also called the Hero Unit or
Jumbotron) component below the navbar. These components are very handy on any web
site.
• In includes the JavaScript extensions that allow to make Bootstrap-provided interface
components more interactive. For example, JavaScript is used to animate dropdown menus
and display “modal dialogs”.
Figure 6.1. Main page of the skeleton app and its layout
If you are new to Twitter Bootstrap, it is recommended that you refer to Appendix
C, where you can find more information about using Twitter Bootstrap and its
components.
Page Appearance and Layout 146
<!DOCTYPE html>
<html lang="en">
<head>
<title>Welcome</title>
<!-- Include metas, stylesheets and scripts here -->
</head>
<body>
<!-- Include page content here -->
</body>
</html>
The <head> element contains the page title text, meta information and references to included
stylesheets and scripts. The <body> element contains the content of the page, like the logo image,
the navigation bar, the page text, and the footer with copyright information.
In Zend Framework 2, you define this common structure with the “master” view template called
the layout. The layout “decorates” other view templates.
The layout template typically has a placeholder in which ZF2 puts the content specific to a
particular page (see figure 6.2 for example).
In the Skeleton Application, the default layout template file is called layout.phtml and is located
inside of the view/layout directory in Application module’s directory (see figure 6.3 for example).
Page Appearance and Layout 147
Let’s look at the layout.phtml template file in more details. Below, the complete contents of the
file is presented (because some lines of the file are too long for a book page, line breaks are
inserted where necessary):
You can see that the layout.phtml file (as a usual view template) consists of HTML tags
mixed with PHP code fragments. When the template is rendered, ZF2 evaluates the inline PHP
fragments and generates resulting HTML page visible to site users.
Line 1 above generates the <!DOCTYPE> ⁵ declaration of the HTML page with the Doctype view
helper.
Line 3 defines the <html> element representing the root of the HTML document. The <html> tag
is followed by the <head> tag (line 4), which typically contains a title for the document, and can
include other information like scripts, CSS styles and meta information.
In line 5, the <meta> tag provides the browser with a hint that the document is encoded using
UTF-8 ⁶ character encoding.
In line 6, we have the HeadTitle view helper that allows to define the title for the page
(“ZF2 Skeleton Application”). The title will be displayed in the web browser’s caption. The
setSeparator() method is used to define the separator character for the compound page titles⁷;
the setAutoEscape() method enhances the security by escaping unsafe characters from the page
title. The Translate view helper is used for localizing your web site’s strings into different
languages.
In line 12, the HeadMeta view helper allows to define the <meta name="viewport"> tag containing
meta information for the web browser to control layout on different display devices, including
mobile browsers. The width property controls the size of the viewport, while the initial-scale
property controls the zoom level when the page is loaded. This makes the web page layout
“responsive” to device viewport size.
In line 19, the HeadLink view helper allows to define the <link> tags. With the <link> tags, you
typically define the “favicon” for the page (located in APP_DATA\public\img\favicon.ico file)
and the CSS stylesheets.
In lines 22-24, the stylesheets common to all site pages are included by the prependStylesheet()
method of the HeadLink view helper. Any page in our web site will load three CSS stylesheet
files: bootstrap.min.css (the minified version of Twitter Bootstrap CSS Framework), bootstrap-
theme.min.css (the minified Bootstrap theme stylesheet) and style.css (CSS file allowing us to
define our own CSS rules overriding Bootstrap CSS rules).
Lines 27-35 include the JavaScript files that all your web pages will load. The scripts are executed
by the client’s web browser, allowing to introduce some interactive features for your pages.
We use the the bootstrap.min.js (minified version of Twitter Bootstrap) and jquery.min.js
(minified version of jQuery library) scripts. All scripts are located in APP_DIR/public/js directory.
⁵The <!DOCTYPE> declaration goes first in the HTML document, before the <html> tag. The declaration provides an instruction to the web
browser about what version of HTML the page is written in (in our web site, we use HTML5-conformant document type declaration).
⁶The UTF-8 allows to encode any character in any alphabet around the world, that’s why it is recommended for encoding the web pages.
⁷A “compound” page title consists of two parts: the first part (“Zend Skeleton Application”) is defined by the layout, and the second part -
defined by a particular page - is prepended to the first one. For example, for the About page of your site you will have the “About - Zend Skeleton
Application”, and for the Documentation page you will have something like “Documentation - Zend Skeleton Application”.
Page Appearance and Layout 150
Line 38 defines the <body> tag, the document’s body which contains all the contents of the
document, such as the navigation bar, text, hyperlinks, images, tables, lists, etc.
In lines 39-63, you can recognize the Bootstrap navigation bar definition. The skeleton application
uses the collapsible navbar with dark inverse theme. The navbar contains the single link Home.
If you look at lines 63-72, you should notice the <div> element with container class which
denotes the container element for the grid system. So, you can use the Bootstrap grid system to
arrange the contents of your pages.
Line 64 is very important, because this line defines the inline PHP code that represents the page
content placeholder we talked about in the beginning of this section. When the ZF2 page renderer
evaluates the layout template, it echoes the actual page content here.
Lines 65-71 define the page footer area. The footer contains the copyright information like “2013
by Zend Technologies Ltd. All rights reserved.” You can replace this information with you own
company name.
Line 73 is the placeholder for JavaScript scripts loaded by the concrete page. The InlineScript
view helper will substitute here all the scripts you register (about registering JavaScript scripts,
you will see it later in this chapter).
And finally, lines 74-75 contain the closing tags for the body and the HTML document.
<?php
echo $this->headTitle('Hello World')
->setSeparator(' - ')
->setAutoEscape(false)
?>
Next, we will use the Bootstrap-provided grid system for arranging the main blocks on the page.
Replace the HTML code of the <body> element (lines 37-73) with the following one:
Page Appearance and Layout 151
1 <body>
2 <div class="container">
3 <div class="row">
4 <!-- Page header -->
5 <div class="col-md-4">
6 <div class="app-caption">Hello World!</div>
7 </div>
8 </div>
9 <div class="row">
10 <div class="col-md-12">
11 <!-- Navigation bar -->
12 </div>
13 </div>
14 <div class="row">
15 <div class="col-md-12">
16 <!-- Breadcrumbs -->
17 </div>
18 </div>
19 <div class="row">
20 <div class="col-md-12">
Page Appearance and Layout 152
In the code above, we defined the <div> element with the container class and put the <div>
elements of the grid inside of it. The grid consists of 5 rows:
• The page header containing the “Hello World!” text (lines 3-8). The header text spans four
grid columns. For styling the text, we use our custom CSS class app-caption (we will
define this class in style.css file a little bit later).
• We left the space for navigation bar interface component in line 11.
• In line 16, we have the space for breadcrumbs component.
• In line 22, we have the page content placeholder. When the renderer evaluates the page, it
will echo the value of the $content variable, so the actual page content will be substituted
here.
• And in lines 25-32, we provided the page footer with the text “(c) 2013 by Your Company.
All rights reserved.” You can change this text and substitute your company name here, if
you wish.
</li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Support <b class="caret"></b>
<ul class="dropdown-menu">
<li>
<a href="<?php echo $this->url('doc',
array('page'=>'contents')) ?>">
Documentation
</a>
</li>
<li>
<a href="<?php echo $this->url('static',
array('page'=>'help')) ?>">
Help
</a>
</li>
</ul>
</a>
</li>
<li>
<a href="<?php echo $this->url('about') ?>">About</a>
</li>
</ul>
</div>
</nav>
In the code above, we used the navbar interface component provided by the Bootstrap. We also
used the Url view helper to insert the links to the navigation items.
We discussed the usage of the Url view helper in the Generating URLs from Route
section in Chapter 5.
Finally, we need to provide a couple of custom CSS rules to fine-tune the look and feel. We define
our own CSS rules in the style.css stylesheet.
We want to make the “Hello World!” header text to use larger bold font and use a nice looking
color. To do this, open the style.css file, and append the following lines to the end:
Page Appearance and Layout 154
div.app-caption {
padding: 25px 0px;
font-size:3.0em;
font-weight: bold;
color:#6aacaf
}
In the CSS code above, we created the app-caption class which can be applied to <div> element
and defining the 25 pixels vertical padding, large font size, bold text style and the hexadecimal
representation of the RGB text color.
By default, in skeleton application, the navbar is pinned to page top, and the CSS rule for the page
body defines the 20 pixels top padding to leave space for it. Since in our Hello World example
we’ve unpinned the navigation bar from top of the page and placed it in page flow, we need to
remove the padding from page body top. To do that, edit the following CSS rule in the style.css
file and make it look like the one below:
body {
padding-bottom: 40px;
}
Great, we’ve completed the page layout template! To see the result of our changes, open the site
in your browser, you should see the page as in figure 6.4. You can click the links in navigation
bar to visit the pages like About or Documentation, etc. The content of a particular page is put
into the content placeholder of our layout.
The result can be seen in action in the Hello World sample application that is part of
this book’s example code available on GitHub.
In the example action method above, we use the Layout controller plugin (line 8) that allows
to access the instance of the ViewModel class associated with the layout template. To change the
layout template for this particular action method, we called the setTemplate() method provided
by the ViewModel class.
In addition to the Layout controller plugin, there is the Layout view helper which
provides the same capabilities. With the Layout view helper, you can, for example,
switch layout from the “static” page which has no specific controller action.
// ...
/**
* We override the parent class' onDispatch() method to
* set an alternative layout for all actions in this controller.
*/
public function onDispatch(MvcEvent $e) {
// Call the base class' onDispatch() first and grab the response
$response = parent::onDispatch($e);
$this->layout()->setTemplate('layout/layout2');
),
);
The action method above just prepares an array of products for rendering and passes it to the
view template with the help of the ViewModel variable container.
Next, add the partial-demo.phtml template file:
1 <?php
2 $this->headTitle('Partial View Demo');
3 ?>
4
5 <h1>Partial View Demo</h1>
6 <p>
7 Below, the table of products is presented. It is rendered with the help of
8 partial views.
9 </p>
10 <table class="table table-striped table-hover">
11 <tr>
12 <th>ID</th>
13 <th>Product</th>
14 <th>Price</th>
15 </tr>
16
17 <?php
18 foreach($this->products as $product) {
19 echo $this->partial('application/index/table-row',
20 array('product'=>$product));
21 }
22 ?>
23 </table>
In the view template above, we define the markup for the table of products (lines 10-23). In line
18, we walk through the items of the products array and render each row with the Partial view
helper.
The first argument of the Partial view helper is the name of the partial view template file
(“application/index/table-row”).
The second argument of the Partial view helper should be an array of arguments passed to
the view template. They will be accessible the same way as if you would pass them with the
ViewModel variable container.
Finally, create the table-row.phtml view template, which will be used as the partial view template:
Page Appearance and Layout 158
<tr>
<td> <?php echo $this->product['id'] ?> </td>
<td> <?php echo $this->product['name'] ?> </td>
<td> <?php echo $this->product['price'] ?> </td>
</tr>
In the view template above, we just render a single row of the table.
To see the resulting web page, type “http://localhost/application/index/partialdemo” URL in your
browser’s navigation bar. You should see something like in figure 6.5.
In the code above, we call the captureStart() method (line 1) and captureEnd() method (line
15) of the Placeholder view helper to delimit the HTML markup that will be captured by the view
helper and stored in its internal storage (instead of rendering to PHP standard output stream).
In lines 3-14, we put the markup of the “inherited” layout. The derived layout uses the two-cell
grid. The first cell of the grid (spanning 8 columns) will contain the actual content of a certain
page, and the second cell (spanning 4 columns) will contain advertisements. For styling the ads,
we utilize the Panel interface component provided by the Twitter Bootstrap.
In line 16, we use the Partial view helper which is used to render the “parent” layout
(layout.phtml). We pass the content captured by the Placeholder view helper to the Partial
view helper as the second argument.
This way, we produced the nice-looking layout which inherits the default layout and improves
the code reusability.
Now, if you set the layout2.phtml for all actions of, say Index controller, you should be able to
see the result as in figure 6.6.
<script type="text/javascript">
// Show a simple alert window with the "Hello World!" text.
$( document ).ready(function() {
alert('Hello World!');
});
</script>
In the example above, we created the <script> element, and put the jQuery callback function
in it. The jQuery binds a function to be executed when the DOM has finished loading. When
the function is executed, a simple alert window with the “Hello World!” text and OK button will
appear.
Since you put this JavaScript code inside the HTML file, we will refer to it as inline script. An
alternative way of storing JavaScript code is putting it in an external .js file. External files
typically contain code that is designed to be used by several web pages. Typically, external
JavaScript files are stored in APP_DIR/public/js/ directory. To link an external JS file to your
HTML page, you add the <script> element like below:
When the browser encounter such a <script> element, it reads the external JS file and executes
the code.
Generally, there are two places inside an HTML file where you can put the script:
Page Appearance and Layout 162
• JavaScript code can be put in the <head> section of an HTML page. This method is
recommended to use when you need JavaScript to be loaded before the content of the
page. We used this method for loading the Twitter Bootstrap JavaScript extensions and
jQuery library.
• Script can be placed at the bottom of the <body> section of an HTML page, just before the
closing </body> tag. This way is acceptable when you need the entire DOM⁹ to be loaded
before the script can start executing.
If a certain JavaScript file needs to be used on all (or on most) of the web pages, it is better to
place it in layout view template. But when a script needs to be used on a single page only, putting
it in the layout template is not the best idea. If you put such a script to layout template, the script
will be loaded to all pages, which can produce an unneeded traffic and increase page load time
for the whole site. To avoid this, you can add such a script for the desired page only.
To add a page-specific script which will be put in the <head> section of the web page, you use
the HeadScript view helper. Its methods are summarized by table 6.1:
To add a link to external JS file to the <head> section, of a page, you add the following PHP code
in the beginning of your view template (.phtml) file:
<?php
$this->headScript()->appendFile('/js/yourscript.js', 'text/javascript');
In the code above, we called the appendFile() method of the HeadScript view helper. This
method takes two arguments. The first one is the path to external JS file (if the file is stored
inside of APP_DIR/public/js directory, or an URL of a JS file if the file is located on another web
server). The second argument is the type of the script (it is typically equal to “text/javascript”).
Other methods provided by HeadScript view helper (such as prependFile(), offsetSetFile()
and setFile() differentiate only in the position in the list of scripts into which the new script
will be inserted.
⁹The DOM (Document Object Model) is a convenient representation of an HTML document structure as a tree of elements.
Page Appearance and Layout 163
<?php
$script = <<<EOT
$( document ).ready(function() {
alert('Hello World!);
});
EOT
$this->inlineScript()->appendScript($script);
In the example above, we used the PHP’s Heredoc ¹¹ syntax to fill in the $script variable with
the inline JavaScript code. Then we call the appendScript() function on the InlineScript view
helper and pass the code as its argument.
But, using the InlineScript view helper may be not very convenient in sense of readability.
Moreover, NetBeans IDE syntax checker will be stuck on the Heredoc notation and will not
recognize the JavaScript code. To fix this, you can simply put the <script> element at the bottom
of your view template, as shown in the example below:
This ensures the same effect is achieved as with InlineScript view helper, but allows for better
script readability and automatic syntax checking.
For HeadScript and InlineScript view helpers to work, you should ensure their
content is echoed in layout view template (look at lines 27 and 72 of layout.phtml file).
If you remove those lines from the layout template, the scripts won’t be inserted in the
web page.
¹⁰The name InlineScript does not fully reflect the capabilities of this view helper. Actually, it can insert both inline and external scripts.
The better name for this view helper would be BodyScript, because it is intended for inserting scripts in document body.
¹¹Heredoc is an alternative string definition method provided by PHP. It works well with multi-line strings.
Page Appearance and Layout 164
6.7.1 Example
For a real-life example of inserting a JavaScript code in your web page, let’s add a page with auto-
complete feature. With this feature, the web browser will predict a word or phrase that the user
wants to type in by several first letters, without the user actually entering the text completely. We
can use an auxiliary JavaScript library called Twitter Typeahead. Analogous to Twitter Bootstrap,
the Typeahead library was developed in Twitter Inc. for their internal purposes and is distributed
freely.
Download typeahead.min.js file (a minified version of the Typeahead library) from the official
project page¹². When the download is finished, place the file in your APP_DIR/public/js directory.
Then add the typeahead.phtml file in your application/index/static subdirectory under the
module’s view directory. This directory is served by the StaticRoute route type that we’ve
created and configured earlier in Chapter 5, and all “static” pages placed here will automatically
become available to site users.
In the typeahead.phtml view template file, put the following content:
1 <?php
2 $this->headTitle('Typeahead');
3 // Add a JavaScript file
4 $this->headScript()->appendFile('/js/typeahead.min.js', 'text/javascript');
5 ?>
6
7 <h1>Typeahead</h1>
8 <p>Type a continent name (e.g. Africa) in the text field below:</p>
9 <input type="text" class="typeahead" title="Type here"/>
10
11 <script type="text/javascript">
12 $( document ).ready(function() {
13 $('input.typeahead').typeahead({
14 name: 'continents',
15 local: [
16 'Africa',
17 'Antarctica',
18 'Asia',
19 'Europe',
20 'South America',
21 'North America'
22 ]
23 });
24 });
25 </script>
In the code above, we set the title for the page (line 2), then we append the typeahead.min.js file
to the <head> section of the page with the HeadScript view helper (line 4).
¹²http://twitter.github.io/typeahead.js/
Page Appearance and Layout 165
In line 9, we create a text input field where the user will be able to enter some text. We mark the
input field with the typeahead CSS class.
Lines 11-25 contain inline JavaScript code placed at the bottom of the view template (we don’t
use InlineScript view helper for better code readability).
In line 12, we have the jQuery event handler bound to the “document is ready” event. This event
is fired when the complete DOM tree has been loaded.
In line 13, we have the jQuery selector (“input.typeahead”) which selects all input fields marked
with the typeahead CSS class and execute the typeahead() function on them.
The typeahead() function binds the change event handler to the text input field. Once the user
enters a character in the field, the handler is executed and checks the letters entered. It then
displays the dropdown menu with suggested auto-completion variants.
The typeahead() function takes two arguments: the name argument identifies the dataset, and
the local argument is a JSON array containing the available auto-completion variants.
To give the auto-completion field and its dropdown menu a nice-looking visual appearance, add
the following CSS rules to your style.css file.
.typeahead,
.tt-query,
.tt-hint {
width: 396px;
height: 30px;
padding: 0px 12px;
font-size: 1.1em;
border: 2px solid #ccc;
border-radius: 4px;
outline: none;
}
.tt-dropdown-menu {
width: 422px;
margin-top: 12px;
padding: 8px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 4px;
}
.tt-suggestion {
padding: 3px 20px;
font-size: 1.1em;
line-height: 24px;
}
Page Appearance and Layout 166
.tt-suggestion.tt-is-under-cursor {
color: #fff;
background-color: #0097cf;
}
.tt-suggestion p {
margin: 0;
}
To see the auto-completion feature in work, type the “http://localhost/typeahead” URL in your
browser and press Enter. The Typeahead page will appear with the prompt to enter a continent
name. For example, type a letter to see how Typeahead suggests you available variants (figure
6.7).
You can see this example working in the Hello World sample bundled with this book
by typing the URL “http://localhost/typeahead” in your browser.
<style>
body {
padding-top: 60px;
padding-bottom: 40px;
}
</style>
External CSS stylesheets are recommended to store the CSS rules. For example, the base CSS rules
provided by Twitter Bootstrap CSS framework are loaded from bootstrap.min.css and bootstrap-
theme.min.css files. Custom site-specific CSS rules can be stored in style.css file. Since you need
this CSS stylesheets for most of your pages, it is better to link them in the head section of the
layout template. But, if a certain CSS stylesheet needs to be loaded for a single page only, you
place it on that page’s view template.
To add an external CSS stylesheet to a view template, you use the HeadLink view helper:
1 <?php
2 $this->headLink()->appendStylesheet('/css/style.css');
3 $this->headLink()->appendStylesheet(
4 'http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css');
In the example code above, we used the appendStylesheet method of the HeadLink view helper
to add an external CSS stylesheet to the head section of the document. The method accepts a
path to local CSS file (line 2) or a URL to CSS file located on another server (line 3).
The summary of HeadLink view helper’s methods is provided in table 6.2).
Page Appearance and Layout 168
If you want to add an inline <style> element in the head section of the document, you can use
the HeadStyle view helper. Its methods are presented in table 6.3 below:
6.8.1 Example
To demonstrate how to add a CSS stylesheet to your web page, we will take a real-life example.
Assume you need to let the user the ability to type a date (in YYYY-MM-DD format) in a text
input field. You would like to improve user experience by not just letting him to type the date,
but also by selecting it from a pop-up date-picker widget.
To achieve this goal, you can use a third-party library called jQuery UI ¹³. To integrate jQuery
UI in your page, you need to download two files from the official project page¹⁴:
¹³jQuery UI provides a set of “user interface interactions, effects, widgets, and themes”; it is based on jQuery library. jQuery UI is analogous
to Twitter Bootstrap in the sense that both provide reusable user interface components.
¹⁴http://jqueryui.com/
Page Appearance and Layout 169
1 <?php
2 $this->headTitle('Datepicker');
3
4 $this->headScript()->appendFile('/js/jquery-ui.min.js', 'text/javascript');
5 $this->headLink()->appendStylesheet('/css/jquery-ui.min.css');
6 ?>
7
8 <h1>Datepicker</h1>
9
10 <p>
11 Click the edit box below to show the datepicker.
12 </p>
13
14 <input type="text" class="datepicker" title="Type here"/>
15
16 <script>
17 $( document ).ready(function() {
18 $( "input.datepicker" ).datepicker({ dateFormat: 'yy-mm-dd' });
19 });
20 </script>
In the example above, we use the HeadScript view helper’s appendFile() method (line 4) to add
the link to jquery-ui.min.js file to the head section of the document.
In line 5, we used the HeadLink view helper’s appendStylesheet() method to add the link to
jquery-ui.min.css CSS stylesheet to the head section of the document.
In line 8, we added the text input field which will be used to enter the date.
In line 10-14, we added an inline JavaScript code for binding jQuery event handler to the text
input field. When the user clicks the text input field, the datepicker widget will appear allowing
to select the date.
To see the result, enter the “http://localhost/datepicker” URL into your browser’s navigation bar
(see figure 6.8 for example).
6.9.1 Menu
First, let’s implement the Menu view helper class that will render the HTML code of the navigation
bar. The Menu class will provide several methods allowing to set menu items in a form of array,
set the active menu item and render the menu (see table 6.4 for method summary).
The information describing a single menu item will be represented by an array like below (for
example, the Home item will have an ID, text label and an URL for a hyperlink):
array(
'id' => 'home',
'label' => 'Home',
'link' => $this->url('home')
)
We also want to add the support for dropdown menus as navigation items. For example, in case of
the Support dropdown menu having the Documentation and Help sub-items, the item description
will take the following form:
array(
'id' => 'support',
'label' => 'Support',
'dropdown' => array(
array(
'id' => 'documentation',
'label' => 'Documentation',
'link' => $this->url('doc', array('page'=>'contents'))
),
array(
'id' => 'help',
'label' => 'Help',
'link' => $this->url('static', array('page'=>'help'))
)
)
)
Page Appearance and Layout 172
We want to put the Menu class in Application\View\Helper namespace. Thus, start from creating
the Menu.php file in the View/Helper directory under the Application module’s source directory
(figure 6.10).
Why do we place the view helper class under module’s source directory?
View helpers (unlike .phtml view templates) are stored under module’s src/ directory,
because they are usual PHP classes and require to be resolved by a PHP class auto-
loading feature. On the other hand, view templates are resolved by the special ZF2 class
called view resolver, and for this reason, view templates are stored under the module’s
view/ directory.
1 <?php
2 namespace Application\View\Helper;
3
4 use Zend\View\Helper\AbstractHelper;
5
6 // This view helper class displays a menu bar.
7 class Menu extends AbstractHelper {
8
9 // Menu items array.
10 protected $items = array();
11
12 // Active item's ID.
13 protected $activeItemId = '';
14
15 // Constructor.
16 public function __construct($items=array()) {
Page Appearance and Layout 173
17 $this->items = $items;
18 }
19
20 // Sets menu items.
21 public function setItems($items) {
22 $this->items = $items;
23 }
24
25 // Sets ID of the active items.
26 public function setActiveItemId($activeItemId) {
27 $this->activeItemId = $activeItemId;
28 }
29 }
In the code above, we defined several private fields for the Menu class. The $items field (line 10)
is an array which will store the information on the menu items; and the $activeItemId field
(line 13) is the ID of an active menu item. The active menu item will be visually highlighted.
In lines 15-18, we defined the class constructor method, which (optionally) takes the array of
items for initializing the menu. An alternative method of menu initialization is through the
setItems() method (lines 20-23). And the setActiveItemId() method (lines 25-28) sets the ID
of the currently active menu item.
Next, let’s add the render() method, which will generate HTML code for the whole navigation
bar and return it as a text string:
22 foreach($this->items as $item) {
23 $result .= $this->renderItem($item);
24 }
25
26 $result .= '</ul>';
27 $result .= '</div>';
28 $result .= '</nav>';
29
30 return $result;
31 }
In the code above, we produce the HTML markup for the Bootstrap navbar component. The
navbar will use the default theme and will be collapsible (adaptive to different screen widths).
The navbar will not have the brand text in the header. In lines 24-26, we loop through the menu
items and render each one with the renderItem() method. Finally, the render() method returns
the resulting HTML code as a text string.
To finish with creating the Menu class, let’s implement the renderItem() method. This method
will produce the HTML code for a single menu item:
1 // Renders an item.
2 protected function renderItem($item) {
3
4 $id = isset($item['id']) ? $item['id'] : '';
5 $isActive = ($id==$this->activeItemId);
6 $label = isset($item['label']) ? $item['label'] : '';
7
8 $result = '';
9
10 if(isset($item['dropdown'])) {
11
12 $dropdownItems = $item['dropdown'];
13
14 $result .= '<li class="dropdown ' . ($isActive?'active':'') . '">';
15 $result .= '<a href="#" class="dropdown-toggle" data-toggle="dropdown">';
16 $result .= $label . ' <b class="caret"></b>';
17
18 $result .= '<ul class="dropdown-menu">';
19
20 foreach($dropdownItems as $item) {
21 $link = isset($item['link']) ? $item['link'] : '#';
22 $label = isset($item['label']) ? $item['label'] : '';
23
24 $result .= '<li>';
25 $result .= '<a href="'.$link.'">'.$label.'</a>';
26 $result .= '</li>';
Page Appearance and Layout 175
27 }
28
29 $result .= '</ul>';
30 $result .= '</a>';
31 $result .= '</li>';
32
33 } else {
34 $link = isset($item['link']) ? $item['link'] : '#';
35
36 $result .= $isActive?'<li class="active">':'<li>';
37 $result .= '<a href="'.$link.'">'.$label.'</a>';
38 $result .= '</li>';
39 }
40
41 return $result;
42 }
In the renderItem() method’s code above we did the following. First, we checked whether the
item is a dropdown menu or a simple item (line 10). If the item is a dropdown menu, we walk
through the dropdown menu items, and render each one in turn (lines 15-20). Lines 25-30 contain
the rendering code for the case of a simple item.
To be able to use the Menu view helper in a view template, it is required to register it in
configuration. To do that, add the following view_helpers key in the module.config.php file:
<?php
return array(
// ...
In the example above, we registered our Menu class as a mainMenu view helper and will be able
to access it from any view template.
Since we plan to use the Menu view helper in the layout view template, replace the navigation
menu markup in layout.phtml file with the following code:
Page Appearance and Layout 176
In the code above, we access the registered mainMenu view helper and set the navigation bar
items with the help of setItems() method (line 1). As a parameter for the method, we pass the
array of items. Then we render the navigation bar with the render() method.
To set the active item for the navigation bar, we can call the setActiveItemId() method from
any view template. For example, add the following code to the beginning of the view template
for the About page (application/index/about.phtml) as follows:
Page Appearance and Layout 177
<?php
$this->mainMenu()->setActiveItemId('about');
?>
Now, if you open the About page in your browser, you should see that the About item of the
navigation menu is highlighted with a different color. To display the active item properly, you
need to call the setActiveItemId() method for each page presenting in the navbar (Home,
Downloads, Documentation, etc.) You can see how this is done in the Hello World sample.
6.9.2 Breadcrumbs
Now that you know how to implement a view helper, let’s create the second view helper for
rendering the breadcrumbs. It is completely analogous to the Menu view helper, so below we just
provide the complete code of the Breadcrumbs class:
1 <?php
2 namespace Application\View\Helper;
3
4 use Zend\View\AbstractHelper;
5
6 // This view helper class displays breadcrumbs.
7 class Breadcrumbs extends \Zend\View\Helper\AbstractHelper {
8
9 // Array of items.
10 private $items = array();
11
12 // Constructor.
13 public function __construct($items=array()) {
14 $this->items = $items;
15 }
16
17 // Sets the items.
18 public function setItems($items) {
19 $this->items = $items;
20 }
21
22 // Renders the breadcrumbs.
23 public function render() {
24
25 if(count($this->items)==0)
26 return ''; // Do nothing if there are no items.
27
28 // Resulting HTML code will be stored in this var
29 $result = '<ol class="breadcrumb">';
30
Page Appearance and Layout 178
To be able to use the Breadcrumbs view helper, register it in the module.config.php file as follows:
Page Appearance and Layout 179
1 <?php
2 return array(
3
4 //...
5
6 // The following registers our custom view helper classes.
7 'view_helpers' => array(
8 'invokables' => array(
9 'pageBreadcrumbs' => 'Application\View\Helper\Breadcrumbs',
10 ),
11 ),
12
13 );
Since we plan to use the Breadcrumbs view helper in the layout view template, replace the
breadcrumbs markup in layout.phtml file with the following code:
In the code above, we access the pageBreadcrumbs() view helper and call it with the render()
method. The echo operator then outputs the HTML code of the breadcrumbs.
Finally, you need to pass the breadcrumbs items for each view template. For example, add the
following lines in the view template for the About page:
<?php
$this->pageBreadcrumbs()->setItems(array(
'Home'=>$this->url('home'),
'About'=>$this->url('about'),
));
?>
Now, if you open the about page, you should see breadcrumbs as in figure 6.11 below. Site users
will easily see what page they are visiting right now and will not get lost.
But, actually the ViewModel class is more than just a variable container plus view template name.
In fact, it is closely related to the layout and page composition.
The third big capability of the view model class is that it allows for combining several view
models in a tree-like structure. Each view model in the tree has the associated view template
name and data variables that can be passed to the view template to control the process of
rendering.
This feature is internally used by Zend Framework 2 when “combining” the layout view template
and the view template associated with the controller’s action method. ZF2 internally creates the
view model for the layout template and assigns it with layout/layout view template name.
When your controller’s action method returns the ViewModel object, this object is attached as a
child to the layout view model (see figure 6.12 for an example).
• The child view model is visited first and its associated view template is rendered, and the
resulting HTML markup is saved in a temporary storage;
• The output HTML markup of the child view model is passed to the layout view model as
the $content variable. This way the layout view template can render the content specific
to the certain page.
Table 6.5 gives the summary of the methods provided by the ViewModel class for the purpose of
page composition:
Page Appearance and Layout 181
Below, we provide the brief description of the methods presented in the table above.
The addChild(), getChild(), hasChildren() and clearChildren() methods are used for
(respectively) adding a child view model to the parent one, retrieving the array of view models
attached, testing if the view model is leaf (doesn’t have children) and detaching all children.
The setCaptureTo() method allows to set the variable in the parent view template into which to
inject the HTML markup code produced by a child view template. If two child view models use
the same variable, the second one will overwrite the first one. The setAppend() method can be
used when you need to inject the results of two or more view templates into a single placeholder
variable. The next rendered view template will be appended to the variable’s existing content.
The view model returned by the controller is assigned the $content capture variable.
A view model can be marked as terminal with the setTerminal() method. The setTerminal()
method takes a single flag parameter. If true, the view model is considered as terminal (top-level
parent) and the renderer returns the output of the view template to the application, otherwise its
parents are rendered as well. The method terminate() tests whether the view model is terminal
or not.
The setTerminal() method is very useful in some situations, because with its help you
can disable the rendering of the layout view template. If you return from controller the
view model marked as terminal, the layout will not be applied. This can be used, for
example, when you want to load part of a page asynchronously by an AJAX ¹⁵ request
and need to insert its HTML code in the DOM tree of an existing page.
6.11 Summary
Zend Framework 2 is shipped with Twitter Bootstrap that is a CSS framework allowing for
creating visual appealing and professionally looking web applications. It provides the base CSS
¹⁵AJAX (stands for Asynchronous JavaScript and XML) is a capability provided by modern browsers which can be used to send data to, and
retrieve data from, a server asynchronously (in background) without interfering with the display and behavior of the existing page.
Page Appearance and Layout 182
rules, the simple layout grid, and useful interface components (like navigation bars, breadcrumbs,
pagination, etc.)
In a typical web site, pages have common structure (for example, a typical page may have
a navigation bar at the top, the body with page content, and the footer with the copyright
information at the bottom). In Zend Framework 2, you define this common structure with a
view template file called the layout. The layout template may have placeholder(s) in which ZF2
puts the content specific to a particular web page.
View helpers are (relatively) simple PHP classes that encapsulate a part of page rendering work.
For example, they allow for composing the page of several parts, setting page title and meta tags,
and creating the reusable widgets like navigation bar or breadcrumbs.
7. Collecting User Input with Forms
In this chapter, you will become familiar with using web forms for gathering data entered by site
users. In Zend Framework 2, functionality for working with forms is mainly spread across four
components: the Zend\Form component, which allows you to build forms and contains the view
helpers for rendering form elements; the Zend\Filter, Zend\Validator and Zend\InputFilter
components which allow you to filter and validate user input:
Component Description
Zend\Form Contains base form model classes.
Zend\Filter Contains various filters classes.
Zend\Validator Implements various validator classes.
Zend\InputFilter Implements a container for filters/validators.
/using-zend-framework-2-book
/formdemo
...
To install the example, you can either edit your default virtual host file or create a new
one. After editing the file, restart the Apache HTTP Server and open the web site in
your web browser. For additional information on Apache virtual hosts, you can refer
to Appendix A.
¹https://github.com/olegkrivtsov/using-zend-framework-2-book
Collecting User Input with Forms 184
• <input> - specifies an input field where the user can enter some data (field appearance
and behavior depends on the field type);
• <textarea> - multi-line text area which can contain an unlimited number of characters;
• <button> - a clickable button²;
• <select> - a dropdown list;
• <option> - used inside the <select> element for defining the available options in a
dropdown list.
In table 7.1, you can find examples of HTML form field definitions. Figure 7.1 contains corre-
sponding field visualizations (except the “hidden” field type, which has no visual representation).
²The <button> field is analogous to <input type="button">, however it provides additional capabilities, like specifying a graphical icon
on the button.
Collecting User Input with Forms 185
Field Definition
Text input field <input type="text" />
Select <select><option>Enable</option><option>Disable</option></select>
HTML5 introduced several new form field types (listed in table 7.2); figure 7.2 contains
corresponding field visualizations.
HTML5 fields provide more convenient ways for entering the most frequently used data types:
numbers, dates, E-mails, URLs, etc. Additionally, on form submit, the web browser validates that
the user entered data is in a correct format, and if not the browser will prevent form submission
and ask the user to correct the input error.
Field Definition
Color picker <input type="color" />
Date <input type="date" />
Date-time (with time zone) <input type="datetime" />
Date-time (without time zone) <input type="datetime-local" />
E-mail address <input type="email" />
Number <input type="number" />
Time <input type="time" />
Month <input type="month" />
Week <input type="week" />
URL <input type="url" />
Range (slider) <input type="range" />
Search field <input type="search" name="googlesearch" />
Telephone number <input type="tel" />
Collecting User Input with Forms 186
At the moment of writing this chapter, not all modern web browsers completely support
HTML5 form fields.
7.2.1 Fieldsets
You can group related form fields with the help of the <fieldset> tag, as shown in the example
below. The optional <legend> tag allows you to define the caption for the group.
<fieldset>
<legend>Choose a payment method:</legend>
<input type="radio" name="payment" value="paypal">PayPal</input>
<input type="radio" name="payment" value="card">Credit Card</input>
</fieldset>
The HTML markup presented above will generate the group as in figure 7.3:
In the example above, we have the feedback form which allows the user to enter his E-mail
address, message subject, text, and then submit them to the server. The form definition begins
with the <form> tag (line 1).
The <form> tag contains several important attributes:
In line 3, we define a text input field with the help of the <input> element. The name attribute
specifies the name of the field (“email”). The type attribute specifies the purpose of the element
(the type “text” means the input field is intended for entering text).
In line 2, we have the <label> element which represents the label for the E-mail text input field
(the corresponding input field’s name is determined by the for attribute of the <label> element).
In lines 5-6, by analogy, we have the “Subject” input field and its label.
In line 9, we have the text area field which is suited well for entering multi-line text. The height
of the text area (6 rows) is defined by the rows attribute.
In line 11, we have the submit button (input element with “submit” type). The value attribute
allows you to set the title text for the button (“Submit”). By clicking this button, the user will
send the form data to the server.
Line break <br> elements are used in lines 4, 7 and 10 to position form controls one below another
(otherwise they would be positioned in one line).
To see what this form looks like, you can put its HTML markup code in a .html file and open
the file in your browser. You will see the form visualization as in figure 7.4.
Collecting User Input with Forms 188
If you enter some data in the feedback form and click the Submit button, the web browser will
send an HTTP request to the URL you specified in the action attribute of the form. The HTTP
request will contain the data you entered.
Above, you can see that the form data is transmitted in request body (line 10). Form fields are
concatenated in a single string and then URL-encoded to replace unsafe characters with allowed
characters from the ASCII table.
In comparison, when you set the GET method for the form, an HTTP request will look like the
example below (the back-slash (‘’) character indicate a line break inserted where the line is too
long for a book page):
Collecting User Input with Forms 189
1 GET http://localhost/contactus?email=name%40example.com&\
2 subject=Example+Subject&body=Hello%21&submit=Submit HTTP/1.1
3 Host: localhost
4 Connection: keep-alive
5 Accept: text/html,application/xhtml+xml,application/xml
6 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64)
7 Accept-Encoding: gzip,deflate,sdch
In the example above, you can see that the form data is concatenated, URL-encoded and sent as
part of the HTTP request’s URL (line 1), which makes the URL long and harder to read. Since
the form data is sent inside the URL, this makes it easily visible to site visitors.
In most cases, you will use the POST method for delivering form data in the request body, because
the user doesn’t need to see the data in the browser’s navigation bar (especially when submitting
passwords or other sensitive data).
Please note that submitting form data using the POST method does not protect your
sensitive data (like passwords, credit card numbers, etc.) from being stolen. To protect
such data, you’ll have to direct your HTTP traffic to a SSL³ tunnel (SSL stands for
Secure Sockets Layer). Protected SSL connections are distinguished by using the https://
schema in web page URLs. To enable SSL for your Apache HTTP server, you will need
to obtain an SSL certificate from a trusted provider (like VeriSign⁴) and install it on
your server.
³http://en.wikipedia.org/wiki/Secure_Sockets_Layer
⁴http://www.verisign.com/
Collecting User Input with Forms 190
<h1>Contact Us</h1>
<p>
Please fill out the following form to contact us.
We appreciate your feedback.
</p>
<div class="form-group">
<label for="email">Your E-mail</label>
<input name="email" type="text" class="form-control"
placeholder="name@example.com">
</div>
<div class="form-group">
<label for="subject">Subject</label>
<input name="subject" type="text" class="form-control"
placeholder="Type subject here">
</div>
<div class="form-group">
<label for="body">Message Body</label>
<textarea name="body" class="form-control" rows="6"
placeholder="Type message text here"></textarea>
</div>
<div class="row">
<div class="col-md-6">
<form>
...
</form>
</div>
</div>
Collecting User Input with Forms 191
In the HTML markup above, we put a form inside of the 6-column-width grid cell, which makes
the form half the width of the screen.
• First, a controller’s action is executed rendering the web page containing the form
prompting the site user for input. Once the user fills the form fields, they click the Submit
button, and this generates a HTTP request and sends the data to the server.
• Second, in your controller’s action method, you can extract the submitted data from POST
(and/or GET) variables, and display the page with the results of the form processing.
Typically these two web pages are handled by the same controller action.
In the following example, we will show how you can create a controller action for displaying the
feedback form and retrieving the data submitted by the user. To start, add the contact-us.phtml
view template in the application/index/ directory under the module’s view/ directory (see figure
7.6 for example).
Put the HTML markup code of the feedback form from the previous section into the view
template file.
Then, add the contactUsAction() action method to the IndexController class. In the action
method, we want to extract raw data from the feedback form submitted by the site user:
Collecting User Input with Forms 192
1 <?php
2 namespace Application\Controller;
3
4 // ...
5
6 class IndexController extends AbstractActionController {
7
8 // This action displays the feedback form
9 public function contactUsAction() {
10
11 // Check if user has submitted the form
12 if($this->getRequest()->isPost()) {
13
14 // Retrieve form data from POST variables
15 $data = $this->params()->fromPost();
16
17 // ... Do something with the data ...
18 var_dump($data);
19 }
20
21 // Pass form variable to view
22 return new ViewModel(array(
23 'form' => $form
24 ));
25 }
26 }
In the code above, we define the contactUsAction() action method in the IndexController
class (line 9).
Then, in line 12, we check whether the request is a POST request (checking the starting line of
the HTTP request). Typically, the form uses the POST method for submitting the data. For this
Collecting User Input with Forms 193
reason, we can detect if the form is submitted or not by checking the starting line of the HTTP
request.
In line 15 we retrieve the raw data submitted by the user. We extract all the POST variables with
the help of the Params controller plugin. The data is returned in the form of an array and saved
into the $data variable.
Finally, we have to add a literal route to make a short and memorable URL for the Contact Us
page. Add the following contactus key to the routing configuration in the module.config.php
file:
<?php
return array(
// ...
'router' => array(
'routes' => array(
// Add the following routing rule for the "Contact Us" page
'contactus' => array(
'type' => 'Zend\Mvc\Router\Http\Literal',
'options' => array(
'route' => '/contactus',
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'contactUs',
),
),
),
),
),
),
// ...
);
Now, if you type the “http://localhost/contactus” URL in your web browser’s navigation bar,
you should see the page as in figure 7.7. Enter an E-mail, subject, and body text and click the
Submit button on the form. The data will be sent to the server, and finally extracted in the
IndexController::contactUsAction() method.
Below, an example of the $data array (produced with the var_dump() PHP function) is shown.
As you can see, the array contains a key for each form field, including the “submit” field.
Collecting User Input with Forms 194
array (size=4)
'email' => string 'name@example.com' (length=16)
'subject' => string 'Happy New Year!' (length=15)
'body' => string 'Dear Support, I'd like to thank you for the
excellent quality of your support service and wish you
a happy new year!' (length=118)
'submit' => string 'Submit' (length=6)
has a disadvantage in that we do not check user-submitted data for possible errors and/or
malicious code. Here we will discuss how to perform such validation.
In a ZF2-based web site that uses the Model-View-Controller pattern, form functionality is
usually separated into form models responsible for field definition, filtering and validation; and
form presentation (view) which is typically implemented with the help of special view helpers.
The functionality allowing to create form models, add validation rules and use view helpers, is
schematically shown in figure 7.8. As you can see from the figure, the standard HTML forms
functionality is used as a base.
The MVC approach to working with forms has the following advantages:
• You are able to reuse your form model in different controller’s actions.
• By using the view helpers, you can avoid the boring work of preparing HTML markup for
rendering the form and its possible validation errors.
• You are able to create one or several visual representations for the same form model.
• By encapsulating the form validation logic in a single form model class you have fewer
places in your code where you need to check user input, thus you improve your site
security.
1. First, inside of the controller’s action method, you retrieve the data submitted by the site
user from GET, POST (and possibly other) PHP variables. Then you create an instance of
Collecting User Input with Forms 196
the form model and pass it the user-submitted data. The form model’s work is to check
(validate) the data for correctness, and if something is wrong, produce error message(s) for
any invalid form field.
2. Secondly, you pass the form model to the .phtml view template for rendering (with the
help of the ViewModel variable container). The view template then will be able to access
the form model and call its methods.
3. And finally, the view template uses the form model and the view helpers provided by Zend
Framework 2 to render the form fields (and to display possible validation error messages
produced at the validation stage). As a result, the HTML markup of the form is produced.
can see from the figure, the Form class extends the Fieldset class. The Fieldset class, in turn,
is derived from the Element class which represents a single form field and its attributes.
This class inheritance may look strange at first sight, but everything becomes logical
if you remember that the Form class inherits methods for adding form fields from the
Fieldset class, and that it inherits methods for setting form attributes from the Element
class.
Below, we provide a stub model class for the feedback form from our previous examples:
1 <?php
2 namespace Application\Form;
3
4 use Zend\Form\Form;
5
6 // A feedback form model
7 class ContactForm extends Form
8 {
9 // Constructor.
10 public function __construct()
11 {
12 // Define form name
13 parent::__construct('contact-form');
14
15 // Set POST method for this form
16 $this->setAttribute('method', 'post');
17
Collecting User Input with Forms 198
As you can see, form models of the web site’s Application module (by convention) belong to
Application\Form namespace (line 2).
In line 7, we define the ContactForm form model class which extends the Form base class.
In line 10, we define the constructor method for the class. Because we derive our form model
from the base Form class, we have to call the parent class’ constructor to initialize it (line 13).
The parent class’ constructor accepts an optional argument allowing it to set the form’s name
(‘contact-form’).
We can also set form data delivery method (POST) by using the setAttribute() method
provided by the base class (line 16). The setAttribute() takes two parameters: the first one
is the name of the attribute to set, and the second one is the value of the attribute.
You also can set the form’s “action” attribute (line 19) with the setAttribute() method,
analogous to the way you did with the “name” attribute. Actually, as you will see later, setting
the form’s “action” attribute is optional.
Setting the “action” attribute for the form is optional, because empty form action forces
the browser to submit form data to the URL of the current page. This is sufficient in
most scenarios, because usually you use the single controller action for both displaying
the form and processing its data.
Form fields are typically created inside of the form model’s constructor (look at line 21). In the
next section, we will learn which form fields are available and how to add them to the form
model.
Concrete form element classes extend the Element base class. They are listed in table 7.3. These
classes live in the Zend\Form\Element namespace.
In the table above, you can see that the ZF2-provided form elements have direct mapping on
HTML4 and HTML5 input fields (discussed in the beginning of this chapter).
For your convenience, ZF2 also provides several “compound” fields. The MultiCheckbox field
is a field which is composed of a group of typical checkboxes related to each other. The
DateTimeSelect, DateSelect, and MonthSelect elements are analogous to corresponding HTML5
elements, but simulate them with the usual select fields. These input fields have an advantage in
that they are supported by all web browsers, unlike the corresponding HTML5 fields. The visual
representation of these elements can be seen in figure 7.11.
Additionally, ZF2 provides “security” form fields Captcha and Csrf, which can be used on a form
for enhancing the security. The Captcha element is a graphical element (image) that is placed
on a form for checking if the site user is a human or a robot. The Csrf element has no visual
representation and is used for prevention of hacker attacks related to cross-site request forgery
⁵.
There is another special form element called Collection. This element is analogous to fieldset,
because it allows you to group related form elements. But, it is designed for adding form elements
dynamically by binding an array of objects to the form.
⁵Cross-site request forgery (CSRF) is a type of malicious exploit of a website whereby unauthorized commands are transmitted from a user
that the website trusts.
Collecting User Input with Forms 201
Particularly, we are interested in the add() method which is used to attach an element to a form.
The add() method takes two arguments: the first one (named $elementOrFieldset) is an element
to insert, and the second one (named $flags) is the optional flags.
The $elementOrFieldset parameter may either be an instance of an Element-derived class (or
the Fieldset class), or an array describing the element that should be created.
The optional $flags argument is an array which may contain a combination of the following
keys: name (allows you to set the element’s name) and priority (allows to specify the zero-
based index in the list of elements to insert the element to. If the priority flag is not specified,
the element will be inserted at the end of the list of the form model’s elements.
Below, we provide two code examples illustrating the possible ways of adding elements to a
form.
1 <?php
2 namespace Application\Form;
3
4 // Define an alias for the class name
5 use Zend\Form\Form;
6 use Zend\Form\Element\Text;
7
8 // A feedback form model
9 class ContactForm extends Form
10 {
11 // Constructor.
12 public function __construct()
Collecting User Input with Forms 202
13 {
14 // Create the form fields here ...
15 $element = new Text(
16 'subject', // Name of the element
17 array( // Array of options
18 'label'=> 'Subject' // Text label
19 ));
20 $element->setAttribute('id', 'subject');
21
22 // Add the "subject" field to the form
23 $this->add($element);
24 }
25 }
In the code above, we created an instance of the Zend\Form\Element\Text class (line 15). The
class constructor takes two parameters: the element’s name (“subject”) and an array of options
(here we specify the text label “Subject”).
Additionally, you may configure the element using the methods provided by the Element base
class. For example, in line 20, we set the “id” attribute with the setAttribute() method. For
your reference, the (most important) methods of the Element base class which can be used for
configuring a form element are presented in table 7.5.
When using array specification for adding an element to a form, the element will
automatically be instantiated and configured by the base class. Internally, this is
accomplished with the help of the Zend\Form\Factory factory class (illustrated by
figure 7.12).
1 <?php
2 namespace Application\Form;
3
4 // Define an alias for the class name
5 use Zend\Form\Form;
6
7 // A feedback form model
8 class ContactForm extends Form
9 {
10 // Constructor.
11 public function __construct()
12 {
13 // Add "subject" field
14 $this->add(array(
15 'type' => 'text', // Element type
16 'name' => 'subject', // Field name
17 'attributes' => array( // Array of attributes
18 'id' => 'subject',
19 ),
20 'options' => array( // Array of options
21 'label' => 'Subject', // Text label
22 ),
23 ));
24 }
25 }
Collecting User Input with Forms 204
In line 14 above, we call the form model’s add() method to add the element to form. We pass the
element specification to the add() method in the form of an array. The array has the following
typical keys:
• the type key (line 15) defines the class name to use for instantiation of the element. Here
you can use either the full class name (e.g. Zend\Form\Element\Text) or its short alias ⁶
(e.g. “text”).
• the name key (line 16) defines the name for the field (“subject”).
• the attributes key (line 17) defines the list of HTML attributes to set (here we set the “id”
attribute).
• the options array (line 18) allows you to specify the text label for the element.
• __construct() constructor will define the form name and method (POST), and initialize
the form by adding its elements;
• addElements() protected method will contain the actual code for adding form elements
and will be called by the constructor.
We put the field creation logic into the addElements() protected method to better
structure the form model’s code.
1 <?php
2 namespace Application\Form;
3
4 use Zend\Form\Form;
5
6 /**
7 * This form is used to collect user feedback data like user E-mail,
8 * message subject and text.
9 */
10 class ContactForm extends Form
11 {
12 // Constructor.
13 public function __construct()
14 {
15 // Define form name
Collecting User Input with Forms 206
16 parent::__construct('contact-form');
17
18 // Set POST method for this form
19 $this->setAttribute('method', 'post');
20
21 // Add form elements
22 $this->addElements();
23 }
24
25 // This method adds elements to form (input fields and
26 // submit button).
27 private function addElements() {
28
29 // Add "email" field
30 $this->add(array(
31 'type' => 'text',
32 'name' => 'email',
33 'attributes' => array(
34 'id' => 'body'
35 ),
36 'options' => array(
37 'label' => 'Your E-mail',
38 ),
39 ));
40
41 // Add "subject" field
42 $this->add(array(
43 'type' => 'text',
44 'name' => 'subject',
45 'attributes' => array(
46 'id' => 'subject'
47 ),
48 'options' => array(
49 'label' => 'Subject',
50 ),
51 ));
52
53 // Add "body" field
54 $this->add(array(
55 'type' => 'text',
56 'name' => 'body',
57 'attributes' => array(
58 'id' => 'body'
59 ),
60 'options' => array(
61 'label' => 'Message Body',
Collecting User Input with Forms 207
62 ),
63 ));
64
65 // Add the submit button
66 $this->add(array(
67 'type' => 'submit',
68 'name' => 'submit',
69 'attributes' => array(
70 'value' => 'Submit',
71 ),
72 ));
73 }
74 }
In line 10 above, we define the ContactForm class which extends the Form base class.
In lines 13-23, we have the constructor method. It calls the base class’ constructor (line 16) and
passes the form name as its argument (“contact-form”). In line 19, the base class’ setAttribute()
method is called allowing you to set the method name for the form (we set the POST method).
In line 22, the addElements() protected method is called, which does the actual work of adding
elements to the form. The code of the addElements() is located in lines 27-70. To add elements
to the form, we call the add() method provided by the base class. This method accepts the single
argument – an array containing configuration for an element. We add four fields: the email, the
subject, the body and the submit field.
In figure 7.14, you can see schematic graphical representation of the form model we have created.
• We want to test that the E-mail address, message subject, and body fields are always
present (because these fields are required).
• We want to ensure that the user entered a valid E-mail address like name@example.com.
• Users may add white space characters to the beginning and/or the end of the E-mail
address, so we would like to filter such characters out (perform the string trimming
operation).
• It would be useful to check for minimum and maximum allowed length of the message
subject and body text.
• For the message subject, we would like to filter out (strip) the new line characters and
HTML tags ⁷.
• We also want to strip HTML tags from the message body.
The requirements above are called validation rules. Validation rules can be divided into two
categories: filters and validators.
The filters transform the user-entered data to fix possible errors or to ensure the data conforms
to a certain format. Filters are typically applied first, validators are applied in the last turn.
Validators check whether the data is acceptable or not. If all data is correct, the form is considered
valid and the data can be safely used by the business logic layer. If a certain field is invalid, a
validator raises an error flag. In that case, the form is typically shown to the user again, and the
user is asked to correct any input errors and resend the form to server.
What happens if I don’t add a validation rule for a certain form field?
If you do not add a validation rule then the user-submitted field value will not be
checked, leaving a hole in your site’s security. It is recommended to always add a
validation rule per each form field entered by user and add as many checks per each
field as needed to keep your form secure.
An input may consist of filters and/or validators and some additional information. For
example, an input may contain the flag telling if the field is required or if its value may
be missing from HTTP request.
Analogous to adding a form model’s fields, there are two possible ways of adding inputs to the
input filter container: either via passing an instance of an input class as the argument of its add()
method, or via passing the array specification ⁸. In the next section, we will describe the latter
method (it is preferable, because it requires less code to write).
1 array(
2 'name' => '<name>',
3 'type' => '<type>',
4 'required' => <required>,
5 'filters' => array(
6 // Add filters configuration here ...
7 ),
8 'validators' => array(
9 // Add validators configuration here ...
10 )
11 )
• The name key (line 2) defines the name of the input. The name should be the same as the
name of the form model’s field. If the name of the input doesn’t match the name of the
corresponding form model’s field, the validation rule won’t be applied to the field.
• The type key (line 3) defines the class name of the input. This key is optional. By default
(when this key is omitted), the Zend\InputFilter\Input class is used. Available input
classes are shown in figure 7.15. In figure 7.15, the Input class is designed to be used
with regular scalar values, ArrayInput is used for filtering/validating array values, and
FileInput is used for checking uploaded files.
• The required key (line 4) tells whether the form field is required or optional. If the field
is required, the site user will have to fill it in; otherwise he will receive a validation error.
• the filters (line 5) and validators (line 8) keys may contain the configuration for zero,
one, or several filters and/or validators applied to the form model’s field.
⁸In the latter (array specification) case, the input will be automatically created with the help of the Zend\InputFilter\Factory class.
Collecting User Input with Forms 210
1 array(
2 'name' => '<filter_name>',
3 'priority' => <priority>
4 'options' => array(
5 // Filter options go here ...
6 )
7 ),
The name key (line 2) is the name for the filter. This may be either a full filter class name (e.g.
Zend\Filter\StringTrim or an alias (e.g. StringTrim).
The optional priority key (line 3) defines filter priority in the list of filters. The priority must
be an integer number. The filters with the highest priority will be applied first. By default, the
FilterChain::DEFAULT_PRIORITY constant (value 1000) is assigned.
The options array (line 4) is specific to a certain filter and may contain parameters for
configuring the filter.
1 array(
2 'name' => '<validator_name>',
3 'break_chain_on_failure' => <flag>
4 'options' => array(
5 // Validator options go here ...
6 )
7 ),
The name key (line 2) is the name for the validator. This may be either a full validator class name
(e.g. Zend\Validator\EmailAddress or an alias (e.g. EmailAddress).
The break_chain_on_failure optional key (line 3) defines the behavior in case the validator
check fails. If this equals to true, subsequent validators in the list will not be executed; otherwise
every validator in the list will be executed without depending on the result of other validators.
The options array (line 4) is specific to certain validator class and may contain parameters for
configuring the validator.
Once you have created and populated the input filter container, you have to attach it to the form
model. The Form base class provides the setInputFilter() method intended for this purpose
(see table 7.6).
1 <?php
2 // ...
3 use Zend\InputFilter\InputFilter;
4
5 class ContactForm extends Form
6 {
7 public function __construct()
8 {
9 // ... call this method to add validation rules
Collecting User Input with Forms 212
10 $this->addInputFilter();
11 }
12
13 // ...
14
15 // This method creates input filter (used for form filtering/validation).
16 private function addInputFilter() {
17
18 $inputFilter = new InputFilter();
19 $this->setInputFilter($inputFilter);
20
21 $inputFilter->add(array(
22 'name' => 'email',
23 'required' => true,
24 'filters' => array(
25 array('name' => 'StringTrim'),
26 ),
27 'validators' => array(
28 array(
29 'name' => 'EmailAddress',
30 'options' => array(
31 'allow' => \Zend\Validator\Hostname::ALLOW_DNS,
32 'useMxCheck' => false,
33 ),
34 ),
35 ),
36 )
37 );
38
39 $inputFilter->add(array(
40 'name' => 'subject',
41 'required' => true,
42 'filters' => array(
43 array('name' => 'StringTrim'),
44 array('name' => 'StripTags'),
45 array('name' => 'StripNewLines'),
46 ),
47 'validators' => array(
48 array(
49 'name' => 'StringLength',
50 'options' => array(
51 'min' => 1,
52 'max' => 128
53 ),
54 ),
55 ),
Collecting User Input with Forms 213
56 )
57 );
58
59 $inputFilter->add(array(
60 'name' => 'body',
61 'required' => true,
62 'filters' => array(
63 array('name' => 'StripTags'),
64 ),
65 'validators' => array(
66 array(
67 'name' => 'StringLength',
68 'options' => array(
69 'min' => 1,
70 'max' => 4096
71 ),
72 ),
73 ),
74 )
75 );
76 }
77 }
As you can see from the code above, first we declare the alias for the Zend\InputFilter\InputFilter
class (line 3).
In the form model’s constructor (line 10), we call the addInputFilter() method which we define
in lines 16-76.
The addInputFilter() method’s goal is to create the InputFilter container (line 18), attach it
to form model (line 19) and add filtering/ validation rules (lines 21-75). For attaching the input
filter to the form model, we use the setInputFilter() method provided by the Form base class.
For inserting filtering/validation rules into the input filter container, we use the add() method
provided by the InputFilter class, which takes the array specification of an input to create.
We add three inputs (per each field of our form model, except its submit button):
• For the email field, we set the required flag to true to make filling this field mandatory.
We use StringTrim filter to remove white spaces from the beginning and the end of the E-
mail address; and the EmailAddress validator for checking the user-entered E-mail address
for correctness. We configure the EmailAddress validator to allow domain names as E-
mail addresses (the \Zend\Validator\Hostname::ALLOW_DNS flag) and disable MX record
checking (set useMxCheck option to false).
• For the subject field, by analogy, we make it required, and use the StringTrim filter
to remove white spaces from the beginning and the end. Additionally, we use the
StripNewLines and StripTags filters to filter out the new line characters and HTML tags,
respectively. We constrain subject string length to be between 1 and 128 characters in
length by using the StringLength validator.
Collecting User Input with Forms 214
• For the body field, we require it to be mandatory, and we use the StripTags filter to strip
HTML tags from E-mail text. We also use the StringLength validator to constrain E-mail
text to be between 1 and 4096 characters in length.
In figure 7.16, you can find the schematic graphical representation of the input filter we’ve
created.
Above, we briefly described how to create an input filter for the form model. For
detailed information about the above mentioned (and other) filters and validators and
their usage examples, please refer to Chapter 8.
• First, you display the form and its fields on a web page, prompting user for input. Once
the user fills the form fields, he clicks the Submit button and sends the data to server.
• Next, your controller extracts the submitted data and asks the form model to validate it. If
there were input errors, you display the form again, asking the user to correct input errors.
If the data is correct, you process the data with your business logic layer and (usually)
redirect the user to another web page.
The Form base class provides several methods for accomplishing these (see table 7.7).
Collecting User Input with Forms 216
• Check whether the form data has been submitted, and if not, display the form on the web
page.
• If the data has been submitted by site user, the raw data is retrieved from POST (and/or GET
) variables in the form of an array.
• The data is assigned to the form model’s fields using the form’s setData() method.
• The filtering and validation is performed using the form’s isValid() method (this results
in executing the input filter attached to the form). If a certain field(s) is/are invalid, display
the form again and ask the user to correct their input.
• As soon as the data has been filtered/validated you retrieve the data from the form model
using the getData() method and can pass the data to other models or use it any other way.
The code example below illustrates how to implement this typical workflow in your controller’s
action method:
1 <?php
2 namespace Application\Controller;
3
4 use Application\Form\ContactForm;
5 // ...
6
7 class IndexController extends AbstractActionController {
8
9 // This action displays the feedback form
10 public function contactUsAction() {
11
12 // Create Contact Us form
13 $form = new ContactForm();
14
15 // Check if user has submitted the form
16 if($this->getRequest()->isPost()) {
17
18 // Fill in the form with POST data
19 $data = $this->params()->fromPost();
Collecting User Input with Forms 217
20 $form->setData($data);
21
22 // Validate form
23 if($form->isValid()) {
24
25 // Get filtered and validated data
26 $data = $form->getData();
27
28 // ... Do something with the validated data ...
29
30 // Redirect to "Thank You" page
31 return $this->redirect()->toRoute('application/default',
32 array('controller'=>'index', 'action'=>'thankYou'));
33 }
34 }
35
36 // Pass form variable to view
37 return new ViewModel(array(
38 'form' => $form
39 ));
40 }
41 }
In the code above, we define the contactUsAction() action method in the IndexController
class (line 10). In the action method, we create an instance of the ContactForm class (line 13).
Then, in line 16, we check whether the request is a POST request (checking the starting line of
HTTP request).
In line 19 we retrieve the raw data submitted by the user. We extract all the POST variables with
the help of the Params controller plugin. The data is returned in the form of an array and saved
into the $data variable.
The data submitted by the user may contain mistakes and should be filtered and validated before
further usage. To do that, in line 20 we set the data to the form model with the setData()
method provided by the Form base class. We validate form data with the isValid() method (line
23), which returns true upon successful validation. If the validation succeeds, we retrieve the
validated data using the getData() method (line 26) and then can pass the data to our business
logic layer.
Once we have used the validated data, in line 31, we redirect the web user to the Thank You page.
The redirect is performed with the Redirect controller plugin. The Redirect plugin’s toRoute()
method takes two parameters: the first parameter is the name of the route (“application/default”),
and the second one is the array of parameters to pass to the router. These identify the web page
where you redirect the user.
We will prepare the controller’s action and view template for the Thank You page a
little bit later.
Collecting User Input with Forms 218
In line 37, we pass the form model through the $form variable to the view template. The view
template will access this variable and will use it for rendering the form (and possible validation
errors).
Reading this section is optional and intended mostly for beginners. You may skip it and
refer directly to the next section Form Presentation.
The MailSender model will internally use the Zend\Mail component. The Zend\Mail compo-
nent is a component provided by Zend Framework 2 and designed to give you the conve-
nient functionality for composing mail messages (the Zend\Mail\Message class) and several
classes implementing available transports for sending mail (in this example, we will use the
Mail\Transport\Sendmail class which uses the sendmail program for delivering E-mails).
The sendmail¹⁰ program is a free open-source mail transfer agent for Linux/Unix
operating systems. It accepts messages that a PHP script passes to it, deciding based
upon the message header which delivery method it should use, and then passes the
message through the SMTP protocol to the appropriate mail server (like Google Mail)
for delivery to the recipient.
Start with creating the MailSender.php file under the Service directory under the module’s source
directory (see figure 7.18 for example).
The following is the code that should be put into the MailSender.php file:
⁹In DDD terms, the MailSender can be related to service models, because its goal is to manipulate data, not to store data.
¹⁰http://www.sendmail.com/sm/open_source/
Collecting User Input with Forms 219
1 <?php
2 namespace Application\Service;
3
4 use Zend\Mail;
5 use Zend\Mail\Message;
6 use Zend\Mail\Transport\Sendmail;
7
8 // This class is used to deliver an E-mail message to recipient.
9 class MailSender {
10
11 // Sends the mail message.
12 public function sendMail($sender, $recipient, $subject, $text) {
13
14 $result = false;
15 try {
16
17 // Create E-mail message
18 $mail = new Message();
19 $mail->setFrom($sender);
20 $mail->addTo($recipient);
21 $mail->setSubject($subject);
22 $mail->setBody($text);
23
24 // Send E-mail message
25 $transport = new Sendmail('-f'.$sender);
26 $transport->send($mail);
27 $result = true;
28 } catch(\Exception $e) {
29 $result = false;
30 }
31
32 // Return status
33 return $result;
34 }
35 }
In the code above, we define the Application\Service namespace (line 2), because the
MailSender class can be related to service models (its goal is to manipulate data, not to store
it).
In lines 4-6, we declare the aliases for the Mail, Message and Transport\Sendmail classes
provided by the Zend\Mail component.
In lines 9-35, we define the MailSender class. The class has the single method sendMail() (line
12), which takes four arguments: sender’s E-mail address, recipient’s E-mail address, message
subject and, finally, message body text.
Collecting User Input with Forms 220
In line 18, we create an instance of the Message class. We use the methods provided by this class
for composing the message (set its subject, body etc.) in lines 19-22.
In line 25, we create an instance of the Sendmail class, which uses the sendmail program to pass
the message to the appropriate mail server (see lines 25-26). Since the classes provided by the
Zend\Mail component may throw an exception on failure, we enclose the block of code with the
try-catch exception handler.
The sendMail() method will return true if the E-mail message sent successfully; otherwise it
will return false (line 33).
Configuring mail system for your web server is a rather complex task. It typically
requires installing sendmail and configuring the server’s MX DNS record to use certain
mail server (either local mail server, e.g. Posftix¹¹, or remote server, like Google Mail).
Because of the complexity of the topic, it is not discussed in this book. You can find
additional information on configuring mail for your particular system online.
1 <?php
2 // ...
3 use Application\Service\MailSender;
4
5 class IndexController extends AbstractActionController {
6
7 // ...
8 public function contactUsAction() {
9
10 // Create Contact Us form
11 $form = new ContactForm();
12
13 // Check if user has submitted the form
14 if($this->getRequest()->isPost()) {
15
16 // Fill in the form with POST data
17 $data = $this->params()->fromPost();
18
19 $form->setData($data);
20
21 // Validate form
22 if($form->isValid()) {
23
24 // Get filtered and validated data
¹¹http://www.postfix.org/
Collecting User Input with Forms 221
25 $data = $form->getData();
26 $email = $data['email'];
27 $subject = $data['subject'];
28 $body = $data['body'];
29
30 // Send E-mail
31 $mailSender = new MailSender();
32 if(!$mailSender->sendMail(
33 'no-reply@example.com', $email, $subject, $body)) {
34 // In case of error, redirect to "Error Sending Email" page
35 return $this->redirect()->toRoute('application/default',
36 array('controller'=>'index', 'action'=>'sendError'));
37 }
38
39 // Redirect to "Thank You" page
40 return $this->redirect()->toRoute('application/default',
41 array('controller'=>'index', 'action'=>'thankYou'));
42 }
43 }
44
45 // Pass form variable to view
46 return new ViewModel(array(
47 'form' => $form
48 ));
49 }
50
51 // This action displays the Thank You page. The user is redirected to this
52 // page on successful mail delivery.
53 public function thankYouAction() {
54 return new ViewModel();
55 }
56
57 // This action displays the Send Error page. The user is redirected to this
58 // page on mail delivery error.
59 public function sendErrorAction() {
60 return new ViewModel();
61 }
62 }
• In line 31, we instantiate the MailSender class with the new operator; in line 32, we call its
sendMail() method and pass it four parameters: the sender’s address (here we use “no-
reply@example.com”, but you can replace this with the address of your sendmail); the
recipient’s E-mail address, the E-mail subject and body.
• If mail has been sent successfully (if the sendMail() method returned true), we redirect
the user to the Thank You page (line 40). On failure (if sendMail() method returned false),
we redirect the user to the Send Error page (line 35).
• In lines 53-55, we have the thankYouAction() method which displays the Thank You page.
This page is shown if the E-mail message is sent successfully.
• In line 59-61, we have the sendErrorAction() method which shows the Error Sending
Email page. This page is shown on E-mail delivery failure.
For simple forms (which do not show error messages), you can use raw HTML tags for
rendering the form and ignore ZF2-provided form view helpers. But, form view helpers
are really unavoidable when rendering complex forms that may display validation
errors and/or add fields dynamically.
• It calls the input filter container attached to the form model, to ensure validation error
messages are available;
• It prepares any elements and/or fieldsets that require preparation ¹².
¹²Typically, this results in wrapping field names with the form/fieldset name (for example, the “email” field’s name will become “contact-
form[email]”) which technically results in a more convenient field grouping in a HTTP request body.
Collecting User Input with Forms 223
• Generic form view helpers. These classes are designed to render the whole form (Form
helper) or its single element (FormElement helper) and possible validation errors (FormElementErrors
helper).
• View helpers for rendering HTML fields of certain types. These allow you to generate
HTML markup for concrete form fields (e.g. FormButton, FormRadio, etc.) and a text label
(FormLabel).
• View helpers for rendering form fields introduced in HTML5. These are analogous to the
view helpers from the previous category, but intended for rendering HTML5 fields (e.g.
FormDate, FormUrl, etc.)
• Other view helpers. In this category, we can put the view helper classes designed for
rendering ZF2-specific fields, like FormMultiCheckbox, FormCaptcha, etc.
Other helpers
FormCaptcha Renders the CAPTCHA security field.
FormDateSelect Renders the date select field.
FormDateTimeSelect Renders the datetime select field.
FormMonthSelect Renders the month select field.
FormMultiCheckbox Renders the multi checkbox field.
FormCollection Renders the collection of elements.
In the next sections, we will provide an overview of several frequently used form view helpers
and their usage examples.
As you can see, there are two methods doing the same thing:
• The render() method produces the HTML markup for the form field. It accepts the single
argument – the instance of the element to render. You can retrieve the form element with
the form model’s get() method (see example below).
• The __invoke() method is a convenience wrapper which results in less code to write.
Collecting User Input with Forms 225
<?php
// We assume that the form model is stored in $form variable.
// Render the E-mail field with the render() method.
echo $this->formElement()->render($form->get('email')); ?>
When executed, the code above will generate the HTML code as follows:
Typically, there is no need to call view helpers for concrete HTML (or HTML5) fields
(e.g. FormText, FormSubmit, etc.) Instead, you can use the generic FormElement view
helper which determines the field type automatically and produces the needed HTML
code.
<?php
// We assume that the form model is stored in $form variable.
// Render validation errors for the E-mail field.
echo $this->formElementErrors($form->get('email'));
If there were any validation errors, this code will generate the unordered list of errors using the
<ul> HTML tag, and the list will contain as many items as there are errors for certain field. An
example of such list for the E-mail field of our feedback form is presented below:
<ul>
<li>'hostname' is not a valid hostname for the email address</li>
<li>The input does not match the expected structure for a DNS hostname</li>
<li>The input appears to be a local network name but local network names
are not allowed</li>
</ul>
<?php
// We assume that the form model is stored in $form variable.
// Render text label for the E-mail field.
echo $this->formLabel($form->get('email'));
When executed, the code above will generate the HTML code as follows:
<?php
// We assume that the form model is stored in $form variable.
// Render the E-mail field, its label and (possible) validation errors.
echo $this->formRow($form->get('email'));
When executed, the code above will generate the HTML code as follows:
Collecting User Input with Forms 227
You can render the whole form with the help of the Form’s render() method as follows:
The same effect can be achieved with the __invoke magic method (see example below):
1 <?php
2 $form = $this->form;
3 $form->prepare();
4 ?>
5
6 <?php echo $this->form()->openTag($form); ?>
7
8 <?php echo $this->formLabel($form->get('email')); ?>
9 <?php echo $this->formElement($form->get('email')); ?>
10 <?php echo $this->formElementErrors($form->get('email')); ?>
11
12 <?php echo $this->formLabel($form->get('subject')); ?>
13 <?php echo $this->formElement($form->get('subject')); ?>
14 <?php echo $this->formElementErrors($form->get('subject')); ?>
15
16 <?php echo $this->formLabel($form->get('body')); ?>
17 <?php echo $this->formElement($form->get('body')); ?>
18 <?php echo $this->formElementErrors($form->get('body')); ?>
19
20 <?php echo $this->formElement($form->get('submit')); ?>
21
22 <?php echo $this->form()->closeTag(); ?>
As you can see from the code above, we do the following things to render the form:
• In line 2, we access the $form variable passed from the controller’s action.
• In line 3, we call the Form’s prepare() method to prepare the form for rendering. Please
note that calling this method is very important. If you forget to do that, there may be some
undesired rendering problems.
• In line 6, we call the openTag() method of the Form view helper. Its purpose is to render the
opening <form> tag and its attributes. The method takes a single argument – an instance
of the form model. Paired closing </form> tag is rendered in line 23 with the help of the
closeTag() method of the Form view helper.
• In lines 8-10, we render the E-mail field’s label, the text field itself and (possible) validation
errors with the help of the FormLabel, FormElement and FormElementErrors view helpers.
Those helpers take the instance of the form model’s element as a single argument. We get
an instance of the element with the get() method provided by the Form base class.
• In lines 12-14, by analogy, we render the Subject field, its label and validation errors.
• And in lines 16-18, we render the label, the field and the validation errors for the body text
area field.
• In line 20, we render the Submit button.
When the view template renderer evaluates this code, it will produce the HTML output like
below:
Collecting User Input with Forms 229
<label for="subject">Subject</label>
<input name="subject" type="text" id="subject" value="">
If certain fields have validation errors, those errors will be outputted below the field in the form
of the <ul> unordered HTML list. For example, if you enter the “123@hostname” into E-mail
form field, you would receive the following validation errors:
1 <?php
2 $form = $this->form;
3 $form->prepare();
4
5 $form->get('email')->setAttributes(array(
6 'class'=>'form-control',
7 'placeholder'=>'name@example.com'
8 ));
9
10 $form->get('subject')->setAttributes(array(
11 'class'=>'form-control',
12 'placeholder'=>'Type subject here'
13 ));
14
15 $form->get('body')->setAttributes(array(
16 'class'=>'form-control',
17 'rows'=>6,
18 'placeholder'=>'Type message text here'
19 ));
20
21 $form->get('submit')->setAttributes(array('class'=>'btn btn-primary'));
22 ?>
23
24 <h1>Contact Us</h1>
25
26 <p>
27 Please fill out the following form to contact us.
28 We appreciate your feedback.
29 </p>
30
31 <div class="row">
32 <div class="col-md-6">
33 <?php echo $this->form()->openTag($form); ?>
34
35 <div class="form-group">
36 <?php echo $this->formLabel($form->get('email')); ?>
37 <?php echo $this->formElement($form->get('email')); ?>
38 <?php echo $this->formElementErrors($form->get('email')); ?>
39 </div>
40
41 <div class="form-group">
42 <?php echo $this->formLabel($form->get('subject')); ?>
43 <?php echo $this->formElement($form->get('subject')); ?>
44 <?php echo $this->formElementErrors($form->get('subject')); ?>
45 </div>
46
Collecting User Input with Forms 231
47 <div class="form-group">
48 <?php echo $this->formLabel($form->get('body')); ?>
49 <?php echo $this->formElement($form->get('body')); ?>
50 <?php echo $this->formElementErrors($form->get('body')); ?>
51 </div>
52
53 <?php echo $this->formElement($form->get('submit')); ?>
54
55 <?php echo $this->form()->closeTag(); ?>
56 </div>
57 </div>
In the code above, we added the .form-control CSS class to every input field in the form. We did
that with the setAttribute() method (see lines 5, 10 and 15). With that method, we also added
the “placeholder” attribute to define the nice-looking placeholder text when a field is empty. For
the “body” field, we added the “rows” attribute defining the height of the field (6 rows).
For the form’s Submit button, we use the .btn and .btn-primary CSS classes (see line 21).
We also put label-input pairs inside of <div> elements with .form-group CSS class (lines 35, 41,
47).
We put a form inside of the 6-column-width grid cell, which makes the form half the width of
the screen (look at lines 31-32).
form ul {
list-style-type:none;
padding: 0px;
margin: 0px 5px;
}
form ul li {
color: red;
}
The CSS rules above will remove bullets from the list and make validation error messages appear
in red.
Collecting User Input with Forms 232
7.13.3 Adding the “Thank You” & “Error Sending Email” Pages
The last small thing we will do is preparing the view templates for the “Thank You” and “Error
Sending Email” pages.
Add the thank-you.phtml view template in application/index/ directory under the module’s
view/ directory. Put the following HTML markup into the view template file:
<h1>Thank You!</h1>
<p>
<div class="alert alert-success">
We will respond to the E-mail address you have provided.
</div>
</p>
Next, add the send-error.phtml view template file. The HTML markup for the Error Sending
Email page is presented below:
<p>
<div class="alert alert-warning">
Sorry, but we had an unexpected problem when trying to deliver
your message. Please try again later.
</div>
</p>
7.13.4 Results
Congratulations! Now, if you open the “http://localhost/contactus” URL in your web browser,
you should see a page like that shown in figure 7.19.
If you enter some invalid data in the form and click the Submit button, you should see the
validation errors (figure 7.20).
Entering the correct E-mail, subject and message text and submitting the form results in sending
the message and displaying the Thank You page (see figure 7.21).
On a sending failure, you will see the Error Sending Email page (see figure 7.22 for example):
You can see the Contact Us form in action in the Form Demo sample application bundled
with this book.
Collecting User Input with Forms 233
7.14 Summary
Forms are the way of collecting user-entered data on web pages. A form usually consists of
elements (input field + label pairs). Elements can optionally be grouped into fieldsets.
In a MVC-based web site, form functionality is separated into form models responsible for
element definition and validation, and form presentation implemented with the help of special
view helpers.
To create a form model, you write a class deriving from the Form base class. The form model is
initialized by adding its elements with the help of the base class-provided methods.
To submit form data to the server, the user clicks the Submit button, then the data is sent as
part of a HTTP request. Once the user submits the form, you can extract the form data in your
controller and ask the form model to validate it.
For checking and filtering the user-entered data, filters and validators are utilized. You use the
InputFilter class which is the container for validation rules.
If there are input errors, you display the form again, asking the user to correct the input errors.
If the data is correct, you process the data with your business logic layer.
8. Transforming Input Data with
Filters
In this chapter, we will provide an overview of standard filters that can be used with your forms.
A filter is a class which takes some input data, processes it, and produces some output data.
In general, you can even use filters outside forms to process an arbitrary data. For
example, filters may be used in a controller action to transform the data passed as GET
and/or POST variables to certain format.
Component Description
Zend\Filter Contains various filters classes.
Zend\InputFilter Implements a container for filters/validators.
8.1.1 FilterInterface
Technically, a filter is a PHP class implementing the FilterInterface interface (it belongs to
Zend\Filter namespace). The interface definition is presented below:
1 <?php
2 namespace Zend\Filter;
3
4 interface FilterInterface
5 {
6 // Returns the result of filtering $value.
7 public function filter($value);
8 }
As you can see, the FilterInterface interface has the single method filter() (line 7), which
takes the single parameter $value. The method transforms the input data and finally returns the
resulting (filtered) value.
Transforming Input Data with Filters 237
A concrete filter class implementing the FilterInterface interface may have addi-
tional methods. For example, many filter classes have methods allowing configuration
of the filter (set filtering options).
You may notice that there is a strange filter called StaticFilter which does not inherit
from AbstractFilter base class. This is because the StaticFilter class is actually a
“wrapper” (it is designed to be a proxy to another filter without explicit instantiation
of that filter).
Standard filters provided by the Zend\Filter component, along with a brief description of each,
are listed in table 8.1.
As you can see from the table, the standard filters can be roughly divided into the following
groups:
• filters casting input data to a specified type (integer, boolean, date-time, etc.);
• filters performing manipulations on a file path (getting the base name, parent directory
name, etc.);
• filters performing compression and encryption of input data;
• filters manipulating string data (case conversion, trimming, character replacement and
removal, URL normalizing, etc.); and
• proxy filters wrapping other filters (Callback, FilterChain and StaticFilter).
¹In this section, we only consider the standard filters belonging to the Zend\Filter namespace, although there are other filters that can also
be considered standard. For example, the Zend\Filter\File namespace contains several filters applicable to processing file uploads (those filters
will be covered in the next chapter). Additionally, the Zend\I18n component defines several filter classes that are aware of the user’s locale.
²From figure 8.1, you may also notice that there are several more base filters: AbstractUnicode filter is the base class for the StringToUpper
and StringToLower filters, because it provides the string conversion functionality common to both of them. And, the Decompress filter inherits
from the Compress filter, because these filters are in fact very similar. By analogy, the Decrypt filter inherits from the Encrypt filter, because
they are the “mirror reflection” of each other as well.
Transforming Input Data with Filters 238
Decompress Decompresses the input data with the specified algorithm (the effect is inverse
to the Compress filter).
Encrypt Encrypts the input data with the specified cryptographic algorithm.
Decrypt Decrypts the input data previously encrypted with the specified cryptographic
algorithm.
Inflector Performs the modification of a word to express different grammatical
categories such as tense, mood, voice, aspect, person, number, gender, and case.
PregReplace Performs a regular expression search and replace.
StringToLower Converts the string to lowercase letters.
StringToUpper Converts the string to uppercase letters.
StringTrim Removes white spaces (space, tabs, etc.) from the beginning and the end of the
string.
StripNewlines Removes new line characters from string (ASCII codes #13, #10).
HtmlEntities Returns the string, converting characters to their
corresponding HTML entity equivalents where they exist.
StripTags Removes tags (e.g., <a></a>) and comments (e.g., <!-- -->).
UriNormalize Converts a URL string to the “normalized” form and prepends the schema part
(e.g., converts www.example.com to http://www.example.com).
Callback Allows to use a callback function as a filter.
FilterChain Allows to organize several filters in a chain.
StaticFilter Returns a value filtered through a specified filter class
without requiring separate instantiation of the filter object.
The StringTrim filter is useful for filtering user-entered string data (E-mail addresses,
user names, etc.) because site visitors tend to make typos in those data. For example,
a user may unintentionally enter a trailing space in an E-mail field, thus making an
E-mail invalid. With the StringTrim filter, you will easily cope with such input errors
and improve user experience.
As you can see from the table, the StringTrim filter, in addition to the filter() method, provides
the constructor method which you can (optionally) pass with the complete list of options to
initialize the filter, and the setCharList() and getCharList() methods which can be used for
setting specific filter options.
All standard filters have the constructor method (optionally) accepting an array of
options for configuring the filter when instantiating it manually.
Below, we provide two code examples showing equivalent methods of manually creating an
instance of the StringTrim filter, setting its options, and filtering a value.
Example 1. Passing options to the constructor method.
Transforming Input Data with Filters 241
1 <?php
2 // Optionally, define a short alias for the filter class name.
3 use Zend\Filter\StringTrim;
4
5 // Create an instance of the filter, passing options to the constructor.
6 $filter = new StringTrim(array('charlist'=>"\r\n\t "));
7
8 // Perform the trimming operation on the string.
9 $filteredValue = $filter->filter(' name@example.com ');
10
11 // The expected output of the filter is the 'name@example.com' string.
In the code above, we create the StringTrim filter object with the help of the new operator (line 6).
We pass the array of options to the constructor to set the list of characters the filter will remove
(here, we tell the filter to remove the new line characters, the tabulation character, and the space
character). Actually, passing the array of options to this filter can be omitted because the filter
already has some default character list to strip off.
In line 9, we call the filter() method and pass it the string value “ name@example.com “ to be
trimmed. The expected output of this call is the “name@example.com” string.
Example 2. Without passing options to the constructor.
1 <?php
2 // Optionally, define a short alias for the filter class name.
3 use Zend\Filter\StringTrim;
4
5 // Create an instance of the filter.
6 $filter = new StringTrim();
7
8 // Specify which characters to remove.
9 $filter->setCharList("\r\n\t ");
10
11 // Perform the trimming operation on the string
12 $filteredValue = $filter->filter(' name@example.com ');
13
14 // The expected output of the filter is the 'name@example.com' string
In the code above, we create the StringTrim filter object with the help of the new operator (line
6).
In line 9, we (optionally) call the StringTrim filter’s setCharList() method to set the list of
characters the filter will remove (here, we tell the filter to remove the new line characters, the
tabulation character, and the space character). This call is optional because the filter already has
some default character list for stripping off.
And, in line 12, we call the filter() method and pass it the string value “ name@example.com
“ to be trimmed. The expected output of this call is the “name@example.com” string.
Transforming Input Data with Filters 242
1 <?php
2 // Create and execute the StringTrim filter through the StaticFilter proxy.
3 $filteredValue = \Zend\Filter\StaticFilter::execute(' name@example.com ',
4 'StringTrim', array('charlist' => "\r\n\t "));
5
6 // The expected output of the filter is the 'name@example.com' string.
The StaticFilter class provides the execute() static method, which takes three arguments: the
input value, the name of the filter to apply, and the array of filter-specific options.
In line 3, we call the execute() method to automatically create the StringTrim filter, call its
setCharList() method, and pass the input value to its filter() method. This is very useful
because it can be accomplished in a single line of code.
1 <?php
2 // It is assumed that you call the following code inside of the form model's
3 // addInputFilter() method.
4
5 $inputFilter->add(array(
6 // ...
7 'filters' => array(
8 array(
9 'name' => 'StringTrim',
10 'options' => array(
11 'charlist' => "\r\n\t "
12 )
13 ),
14 ),
15 // ...
16 );
Transforming Input Data with Filters 243
In the code above, we call the add() method provided by the InputFilter container class (line
5). The add() method takes an array which has the filters key. You typically register the filters
under that key (line 7). Filters registered under that key are inserted in a filter chain in the order
they appear in the list.
A filter configuration typically consists of the name (line 9) and options (line 10). The name is a
fully qualified filter class name (e.g., \Zend\Filter\StringTrim) or its short alias (StringTrim).
The options is an array consisting of filter-specific options. When the factory class instantiates
the filter, it passes the list of options to the filter’s constructor method, and the constructor
initializes the filter as needed.
The FilterPluginManager class defines the short aliases for the standard filters.
A standard filter’s alias is typically the same as the class name. For example, the class
Zend\Filter\StringTrim has the short alias StringTrim.
The filter plugin manager is internally used by the InputFilter container class for instantiating
the standard filters.
The Int filter is a very simple filter that is designed to cast an arbitrary scalar data to an integer.
This filter may be useful when adding validation rules for form fields that must contain an integer
numeric values (e.g., a drop-down list or a text field containing an amount of something).
The Int class has the single filter() method.
The Int filter will not cast a non-scalar value. If you pass it an array, it will return it as
is.
Below, you can find a code example illustrating the usage of the Int filter.
1 <?php
2 // Create Int filter.
3 $filter = new \Zend\Filter\Int();
4
5 // Filter a value casting it to an integer number.
6 $filteredValue = $filter->filter('10'); // Returns (int) 10.
7 $filteredValue2 = $filter->filter(array('10', '20')); // Returns array as is.
In the code above, we pass the string “10” to the filter (line 6). The expected return value is the
integer 10.
In line 7, we pass an array to the filter. Because the Int filter works with scalar values only, it
returns the array as is (without changes) and raises a PHP warning.
The Boolean class is a filter that is designed to cast an arbitrary data to a boolean value (true or
false). This filter can be used for filtering check box form fields.
The filter provides several methods allowing to set filtering options (setCasting(), setType(),
and setTranslations()).
The setCasting() method allows to choose one of two modes in which the filter may operate.
If the flag is true, the filter will behave like the PHP (boolean) cast operator. Otherwise (if the
flag is set to false), it will cast only from types defined by the setType() method, and all other
values will be returned as is.
The setType() filter’s method allows to define from which types to cast. This method accepts
the single argument $type, which can be either an OR combination of TYPE_-prefixed constants
or an array containing the literal equivalents of the constants. Possible constants accepted by the
setType() method and their literal equivalents are listed in table 8.4:
The following code example shows two equivalent ways you can call the setType() method:
Transforming Input Data with Filters 246
<?php
use Zend\Filter\Boolean;
The setTranslations() method allows to define localized equivalents of boolean true and
false values. This method accepts a single parameter, which must be an array in the form of
key⇒value pairs, where the key is a localized string and the value is its boolean representation.
The following code example shows how to use the setTranlsations() method:
<?php
$filter->setTranslations(array(
'yes' => true, // English 'yes'
'no' => false, // English 'no'
'ja' => true, // German 'yes'
'nicht' => false, // German 'no'
'да' => true, // Russian 'yes'
'нет' => false // Russian 'no'
));
Below, we provide a code example illustrating the usage of the Boolean filter.
<?php
// Create Boolean filter.
$filter = new \Zend\Filter\Boolean();
The Null filter is designed to cast an arbitrary data to a null value if it meets specific criteria.
This may be useful when you work with a database and want to have a null value instead of
any other type. If the value cannot be treated as null, the filter will return the value as is.
The Null filter’s public methods are listed in table 8.5.
By default, the Null filter behaves like PHP’s empty() function: if the empty() function returns
a boolean true on the input data, then the filter will return the null value on that data, as well.
The setType() method can be used to set the type from which the filter will cast to null. This
method takes a single parameter, which can either be a combination of TYPE_-prefixed constants
listed in table 8.6 or an array of their literal equivalents.
The following code example illustrates two equivalent ways you can call the setType() method:
<?php
use Zend\Filter\Null;
Below, a code example showing how to use the Null filter is provided:
Transforming Input Data with Filters 248
<?php
// Create Null filter.
$filter = new \Zend\Filter\Null();
The DateTimeFormatter filter accepts a date in an arbitrary format and converts it into the
desired format.
In the code example below, we show how to create the filter, pass it a string date, and convert it
to the desired format:
<?php
// Create DateTimeFormatter filter.
$filter = new \Zend\Filter\DateTimeFormatter();
Internally, the DateTimeFormatter filter uses the DateTime class from the PHP standard
library for converting and formatting dates. For available date formats, please refer to
the PHP documentation for the DateTime class.
The BaseName filter class is just a wrapper on the basename() PHP function. It takes a string
containing the path to a file or directory and returns the trailing name component.
Below, you can find an example of the BaseName filter usage:
<?php
// Create BaseName filter.
$filter = new \Zend\Filter\BaseName();
The BaseName filter will not process a non-scalar value. If you pass it an array, it will
return the array as is and raise a PHP warning.
The Dir filter class is just a wrapper on the dirname() PHP function. It takes a string containing
the path to a file or directory and returns the the parent directory’s path.
The Dir filter will not process a non-scalar value. If you pass it an array, it will return
the array as is.
Below, a code example demonstrating the usage of the Dir filter is provided.
Transforming Input Data with Filters 250
<?php
// Create Dir filter.
$filter = new \Zend\Filter\Dir();
The RealPath filter takes an absolute or a relative file path as a string input argument. It expands
all symbolic links and resolves references to ‘/./’, ‘/../’ and extra ‘/’ characters in the input path
and returns the canonicalized absolute pathname.
The RealPath filter returns a boolean false on failure, e.g., if the file does not exist. If a
nonexisting path is allowed, you can call the setExists() method with the false parameter.
Below, a code example demonstrating the usage of the RealPath filter is provided.
<?php
// Create RealPath filter.
$filter = new \Zend\Filter\RealPath();
The RealPath filter will not process a non-scalar value. If you pass it an array, it will
return the array as is.
The Compress filter is designed to compress input data with some compression algorithm. For
example, you can use this filter to compress the data and save it as an archive file.
Filter’s public methods are listed in table 8.9.
The Compress filter itself cannot compress data. Instead, it uses a so-called adapter class. The
adapter class must implement the CompressionAlgorithmInterface interface. You attach an
adapter to the Compress filter, and the adapter implements the concrete compression algorithm.
There are several standard adapter classes available (see figure 8.2 and table 8.10 below). Those
classes live in the Zend\Filter\Compress namespace.
³http://www.bzip.org/
⁴http://www.gzip.org/
Transforming Input Data with Filters 252
Below, a code example demonstrating the usage of the Compress filter is provided.
⁵http://www.gnu.org/software/tar/tar.html
⁶https://code.google.com/p/snappy/
Transforming Input Data with Filters 253
1 <?php
2 // Create Compress filter.
3 $filter = new \Zend\Filter\Compress();
4
5 // Configure the adapter.
6 $filter->setAdapter('Zip');
7 $filter->setAdapterOptions(array(
8 'archive' => 'example.zip',
9 ));
10
11 // Compress an input data (it is assumed that you have the testfile.txt
12 // file in the current working directory.
13 $filter->filter('testfile.txt');
In the code above, we create the instance of the Compress filter (line 3), set its adapter (line 6),
set adapter’s options (line 7), and finally, compress the input file (line 13). The expected result,
the example.zip archive file, will be created in the current directory. The archive will contain the
testfile.txt file.
The Decompress filter is a “mirror reflection” of the Compress filter and can be used by
analogy. By that reason, we do not cover theDecompress filter in this section.
The Encrypt filter’s purpose is encrypting the input data with the specified algorithm. Filter’s
public methods are listed in table 8.11.
The Encrypt filter uses adapter classes to perform actual data encryption. You attach an adapter
to the Encrypt filter with the setAdapter() method, and the adapter performs the concrete
encryption. An adapter class must implement the EncryptionAlgorithmInterface interface.
There are several standard adapter classes available (see figure 8.3 below). Those classes live in
the Zend\Filter\Encrypt namespace.
Below, a code example demonstrating the usage of the Encrypt filter is provided.
1 <?php
2 // Create Encrypt filter.
3 $filter = new \Zend\Filter\Encrypt();
4
5 // Set encryption adapter.
6 $filter->setAdapter('BlockCipher');
7
8 // Encrypt an input data.
9 $filteredValue = $filter->filter('some data to encrypt');
The Decrypt filter is a “mirror reflection” of the Encrypt filter and can be used by
analogy. By that reason, we do not cover the Decrypt filter in this section.
The StringToLower filter class is designed for converting the input string data to lowercase
letters. The public methods of the filter are provided in table 8.12 below.
By default, the filter behaves like the strtolower() PHP function. Given a string, it returns
the string with all alphabetic characters converted to lowercase. The “alphabetic characters”
are determined by the system locale. This means that in, for example, the default “C” locale,
characters such as umlaut-A (Ä) will not be converted.
Calling the setEncoding() method on the filter and passing it an encoding to use forces this filter
to behave like the mb_strtolower() PHP function. By contrast to strtolower(), “alphabetic”
is determined by the Unicode character properties. Thus, the behavior of this function is not
affected by locale settings, and it can convert any characters that have ‘alphabetic’ property,
such as A-umlaut (Ä).
If the value provided is non-scalar, the value will remain unfiltered, and an E_USER_-
WARNING will be raised indicating it cannot be filtered.
Below, a code example showing how to use the StringToLower filter is provided:
<?php
// Create StringToLower filter.
$filter = new \Zend\Filter\StringToLower();
// Filter a string.
$filteredValue = $filter->filter('How to Start a Business in 10 Days');
The PregReplace filter can be used for performing a regular expression search and replace in a
string data. This filter is a wrapper over the preg_replace() PHP function. The public methods
of the filter are provided in table 8.13 below.
Transforming Input Data with Filters 256
Below, a code example showing how to use the StringToLower filter is provided:
<?php
// Create PregReplace filter.
$filter = new \Zend\Filter\PregReplace();
// Filter a string.
$filteredValue = $filter->filter('An example with multiple spaces.'\
);
// The expected filter's output is the 'An example with multiple spaces.'
The StripTags filter removes all tags (e.g. <!-- -->, <p>, <h1> or <?php ?>) from the input
string. It allows to explicitly define the tags which should not be stripped out. Additionally, it
provides an ability to specify which attributes are allowed across all allowed tags and/or specific
tags only.
Public methods of the StripTags filters are listed in table 8.14.
Below, a code example showing how to use the StripTags filter is provided:
<?php
// Create StripTags filter.
$filter = new \Zend\Filter\StripTags();
// Filter a string.
$filteredValue = $filter->filter(
'<p>Please click the following <a href="example.com">link</a>.</p>');
The StripTags will not process a non-scalar value. If the value passed to the filter is
non-scalar, the value will remain unfiltered.
The StripNewlines filter is a very simple filter which returns the input string without any
newline control characters (“\r”, “\n”).
Below, a code example showing how to use the StripNewlines filter is provided:
<?php
// Create StripNewlines filter.
$filter = new \Zend\Filter\StripNewlines();
// Filter a string.
$filteredValue = $filter->filter("A multi line\r\n string");
The StripNewlines will not process a non-scalar value. If the value passed to the filter
is non-scalar, the value will remain unfiltered.
The UriNormalize filter can be used for normalizing a URL string and (optionally) applying a
scheme part to it. The public methods of the filter are provided in table 8.15 below.
Transforming Input Data with Filters 258
1. The URL string is decomposed into its schema, host, port number, path, and query parts.
If the scheme part is missing from the original URL, the default scheme is used.
2. The scheme and host parts are converted to lowercase letters.
3. The port number is checked against the list of allowed port numbers, and if it doesn’t
belong to the list, the port number is cleared.
4. The path part of the URL is filtered, removing redundant dot segments, URL-decoding any
over-encoded characters, and URL-encoding everything that needs to be encoded and is
not.
5. The query part is sanitized, URL-decoding everything that doesn’t need to be encoded and
URL-encoding everything else.
The URL normalization procedure rules may be different for different protocols (schemes). If
the URL doesn’t contain the scheme part, the http scheme is assumed by default. You may
use the UriNormalize filter’s setDefaultScheme() method to set the default scheme for URL
normalization. It accepts any of the following schemes: http, https, file, mailto, urn, and tag.
Additionally, the UriNormalize filter’s setEnforcedScheme() allows to override the default
scheme part by the so-called “enforced scheme”, if the original URL doesn’t contain scheme
part.
Below, a code example showing how to use the UriNormalize filter is provided:
<?php
// Create UriNormalize filter.
$filter = new \Zend\Filter\UriNormalize();
The FilterChain class is internally used by the InputFilter container class for storing
the sequence of filters attached to the form model’s field.
Public methods provided by the FilterChain class are presented in table 8.16:
An example filter chain is shown in figure 8.4. It consists of the StringTrim filter followed by
the StripTags filter, which is then followed by the StripNewLines filter.
To construct the filter chain like in figure 8.4, we can use the following code:
Transforming Input Data with Filters 260
<?php
use Zend\Filter\FilterChain;
In the code above, we instantiate the FilterChain filter with the new operator (line 5). In line 8,
we set construct the chain of filters with the setOptions() method.
The method takes an array configuration which looks the same way as in InputFilter’s add()
method. The array has “filters” key where you register the filters you want to insert into the chain.
For each attached filter, you provide the following subkeys: * “name” is the fully qualified class
name of the filter (e.g., “ZendFilterStringTrim”) or its short alias (e.g., “StringTrim”); * “options”
is an array of options passed to the filter; and * “priority” is the optional key which defines the
priority of the filter in the chain. Filters with higher priority are visited first. The default value
for the priority is DEFAULT_PRIORITY.
Finally, in line 20, we call the filter() method, which walks through the chain and passes the
filtered value to each filter in turn.
The Callback filter is designed as a wrapper for your custom filtering algorithm. For example,
this may be useful when a standard filter is not suitable, and you need to apply your own filtering
algorithm to the data.
The public methods provided by the Callback filter are listed in table 8.17.
As you can see from the table, the Callback filter provides the setCallback() and setCallbackParams()
methods that can be used to set the callback function (or the callback class method) and,
optionally, pass it one or several parameters.
8.6.6.1 Example
To demonstrate the usage of the Callback filter, let’s add the phone number field to our
ContactForm form model class and attach a custom filter to it.
An international phone number typically looks like “1 (808) 456-7890”. It consists of the country
code followed by the three-digit area code enclosed into braces. The rest of the phone consists
of the seven-digit subscriber code divided in two groups separated by a dash. The country code,
the area code, and the subscriber code are separated by the space character. We will refer to this
phone format as the “international” format.
The international phone format is required for making telephone calls between different
countries (or areas). If the calls are made within the same area, the telephone number may simply
look like “456-7890” (we just omit the country code and area code). We will refer to this phone
format as the “local” phone format.
To make our filter as generic as possible, we assume that the user is required to enter the phone
in international format for some forms and in local format for other forms. Because some site
visitors may enter their phone number in a format different from what is required, we want to
apply the filter that will “normalize” the phone number for us.
To do the phone “normalization”, the filter will:
2. Pad the digits to the required length if there are too few digits.
3. Add the braces, the spaces, and the dash (when using the international format); or simply
add the dash (when using the local format).
Because ZF2 does not provide a standard filter for accomplishing such phone filtering operation,
we will use the Callback wrapper filter. To do that, we will make the following changes to the
code of our ContactForm class:
1 <?php
2 // ...
3 class ContactForm extends Form
4 {
5 // ...
6 protected function addElements() {
7 // ...
8
9 // Add "phone" field
10 $this->add(array(
11 'type' => 'text',
12 'name' => 'phone',
13 'attributes' => array(
14 'id' => 'phone'
15 ),
16 'options' => array(
17 'label' => 'Your Phone',
18 ),
19 ));
20 }
21
22 private function addInputFilter() {
23 // ...
24 $inputFilter->add(array(
25 'name' => 'phone',
26 'required' => true,
27 'filters' => array(
28 array(
29 'name' => 'Callback',
30 'options' => array(
31 'callback' => array($this, 'filterPhone'),
32 'callbackParams' => array(
33 'format' => 'intl'
34 )
35 )
36 ),
37 ),
38 )
Transforming Input Data with Filters 263
39 );
40 }
41
42 // Custom filter for a phone number.
43 public function filterPhone($value, $format) {
44
45 if(!is_scalar($value)) {
46 // Return non-scalar value unfiltered.
47 return $value;
48 }
49
50 $value = (string)$value;
51
52 if(strlen($value)==0) {
53 // Return empty value unfiltered.
54 return $value;
55 }
56
57 // First, remove any non-digit character.
58 $digits = preg_replace('#[^0-9]#', '', $value);
59
60 if($format == 'intl') {
61 // Pad with zeros if the number of digits is incorrect.
62 $digits = str_pad($digits, 11, "0", STR_PAD_LEFT);
63
64 // Add the braces, the spaces, and the dash.
65 $phoneNumber = substr($digits, 0, 1) . ' ('.
66 substr($digits, 1, 3) . ') ' .
67 substr($digits, 4, 3) . '-'.
68 substr($digits, 7, 4);
69 } else { // 'local'
70 // Pad with zeros if the number of digits is incorrect.
71 $digits = str_pad($digits, 7, "0", STR_PAD_LEFT);
72
73 // Add the dash.
74 $phoneNumber = substr($digits, 0, 3) . '-'. substr($digits, 3, 4);
75 }
76
77 return $phoneNumber;
78 }
79 }
In lines 10-19 of the code above, we add the “phone” field to the ContactForm form model. The
field is a usual text input field, and we already had some experience of working with such fields
earlier.
Then, in lines 24-39, we add a validation rule for the “phone” field of our form. Under the “filters”
Transforming Input Data with Filters 264
key (line 27), we register the Callback filter (here, we use the short alias Callback, but you can
alternatively use the fully qualified class name \Zend\Filter\Callback).
The filter takes two options (line 30): the “callback” option and the “callback_params” option.
The “callback” option is an array consisting of two elements, which represent the class and the
method to call, respectively. In this example, the callback is the filterPhone() method of the
ContactForm class. We pass the “format” parameter to the callback method with the help of
“callbackParams” option (line 32).
In lines 43-79, we define the filterPhone() callback method, which takes two arguments: the
$value is the phone number to filter, and the $format is the desired phone number format. The
$format parameter may either be ‘local’ (for local format) or ‘intl’ (for international format).
• First, in line 45, we check if the $value parameter is a scalar and not an array. If the value
is not a scalar, we return it without change.
• In line 52, we check the input value’s length. We do nothing if the user entered an empty
phone number; we just return it as is.
• Then, we remove any non-digit characters (line 58).
• If phone length is too short, we pad it with zeroes.
• We add the braces, the dash, and the spaces for international phone numbers; or just the
dash for local phone numbers.
• Finally, we return the resulting phone number.
To see how this filter works, you can open the “http://localhost/contactus” URL in your web
browser. If you enter some phone number in an incorrect format, the filter will fix the phone
number and transform it to the desired format.
As you may remember, the base concrete class for all standard filters is the
AbstractFilter class. By analogy, we will also derive our custom PhoneFilter filter
from that base class.
We plan to have the following methods in our PhoneFilter filter class (see table 8.18):
Transforming Input Data with Filters 265
To start, create the PhoneFilter.php file in the Application/Service directory under the module’s
source directory ⁷. Put the following code into that file:
1 <?php
2 namespace Application\Service;
3
4 use Zend\Filter\AbstractFilter;
5
6 // This filter class is designed for transforming an arbitrary phone number t\
7 o
8 // the local or the international format.
9 class PhoneFilter extends AbstractFilter
10 {
11 // Phone format constants.
12 const PHONE_FORMAT_LOCAL = 'local'; // Local phone format
13 const PHONE_FORMAT_INTL = 'intl'; // International phone format
14
15 // Available filter options.
16 protected $options = array(
17 'format' => self::PHONE_FORMAT_INTL
18 );
19
20 // Constructor.
21 public function __construct($options = null)
22 {
23 // Set filter options (if provided).
24 if(is_array($options)) {
25
26 if(isset($options['format']))
27 $this->setFormat($options['format']);
28 }
29 }
30
31 // Sets phone format.
32 public function setFormat($format)
⁷The PhoneFilter class may be considered as a service model because its goal is to process data, not to store it.
Transforming Input Data with Filters 266
33 {
34 // Check input argument.
35 if( $format!=self::PHONE_FORMAT_LOCAL &&
36 $format!=self::PHONE_FORMAT_INTL ) {
37 throw new \Exception('Invalid format argument passed.');
38 }
39
40 $this->options['format'] = $format;
41 }
42
43 // Returns phone format.
44 public function getFormat()
45 {
46 return $this->format;
47 }
48
49 // Filters a phone number.
50 public function filter($value)
51 {
52 if(!is_scalar($value)) {
53 // Return non-scalar value unfiltered.
54 return $value;
55 }
56
57 $value = (string)$value;
58
59 if(strlen($value)==0) {
60 // Return empty value unfiltered.
61 return $value;
62 }
63
64 // First, remove any non-digit character.
65 $digits = preg_replace('#[^0-9]#', '', $value);
66
67 $format = $this->options['format'];
68
69 if($format == self::PHONE_FORMAT_INTL) {
70 // Pad with zeros if the number of digits is incorrect.
71 $digits = str_pad($digits, 11, "0", STR_PAD_LEFT);
72
73 // Add the braces, the spaces, and the dash.
74 $phoneNumber = substr($digits, 0, 1) . ' (' .
75 substr($digits, 1, 3) . ') ' .
76 substr($digits, 4, 3) . '-' .
77 substr($digits, 7, 4);
78 } else { // self::PHONE_FORMAT_LOCAL
Transforming Input Data with Filters 267
From line 2, you can see that the filter class lives in the Application\Service namespace.
In line 8, we define the PhoneFilter class. We derive our filter class from the AbstractFilter
base class to reuse the functionality it provides. Line 4 contains the short alias for the AbstractFilter
class.
In lines 11-12, for convenience, we define the phone format constants (PHONE_FORMAT_INTL for
international format and PHONE_FORMAT_LOCAL for local format). These are the equivalents of the
“intl” and “local” strings, respectively.
In lines 15-17, we define the $options private variable, which is an array having the single key
named “format”. This key will contain the phone format option for our filter.
In lines 20-28, we have the constructor method, which takes the single argument $options.
When constructing the filter manually, you may omit this parameter. However, when the filter
is constructed by the factory class, the factory will pass filter options to the filter’s constructor
through this argument.
In lines 31-37 and 43-46, we have the setFormat() and getFormat() methods that allow to set
and retrieve the current phone format, respectively.
In lines 49-86, we have the filter() method. This method encapsulates the phone number
filtering algorithm. It takes the $value parameter, transforms it by taking the selected phone
format in account, and returns the formatted phone number.
$inputFilter->add(array(
'name' => 'phone',
'required' => true,
'filters' => array(
array(
'name' => '\Application\Service\PhoneFilter',
'options' => array(
'format' => \Application\Service\PhoneFilter::PHONE_FORMAT_INTL
)
),
// ...
),
// ...
)
);
You can see how the PhoneFilter filter works in the Form Demo sample application bundled
with this book. Open the “http://localhost/contactus” page in your web browser. If you enter
some phone number in an incorrect format, the filter will fix the phone number.
If you wish, you can use the PhoneFilter outside of forms, as shown in the code example below:
<?php
use Application\Service\PhoneFilter;
// Filter a string.
$filteredValue = $filter->filter('12345678901');
8.8 Summary
Filters are designed to take some input data, process it, and produce some output data. Zend
Framework 2 provides a lot of standard filters that can be used for creating filtering rules of your
forms (or, if you wish, to filter an arbitrary data outside of forms).
The standard filters can be roughly divided into several groups:
If a standard filter is not suitable, it is possible to create a custom filter class. In this chapter,
we have provided an example of how to write your own PhoneFilter class capable of filtering
phone numbers.
9. Checking Input Data with
Validators
In this chapter, we will provide an overview of standard validators that can be used with your
forms. A validator is a class designed to take some input data, check it for correctness, and
return a boolean result telling whether the data is correct (and error messages if the data has
some errors).
In general, you even can use validators outside forms to process an arbitrary data. For
example, validators may be used in a controller action to ensure that data passed as
GET and/or POST variables is secure and conform to certain format.
Component Description
Zend\Validator Implements various validator classes.
Zend\InputFilter Implements a container for filters/validators.
9.1.1 ValidatorInterface
In ZF2, a validator is a usual PHP class which implements the ValidatorInterface interface (it
belongs to Zend\Validator namespace). The interface definition is presented below:
1 <?php
2 namespace Zend\Validator;
3
4 interface ValidatorInterface
5 {
6 // Returns true if and only if $value meets the validation requirements.
7 public function isValid($value);
8
9 // Returns an array of messages that explain why
Checking Input Data with Validators 271
As you can see, the ValidatorInterface has two methods: the isValid() method (line 7) and
getMessages() method (line 11).
The first one, isValid() method, is intended to perform the check of the input value (the $value
parameter). If the validation of the $value passes, the isValid() method returns boolean true.
If the $value fails validation, then this method returns false.
• validators for checking value conformance to certain format (IP address, host name, E-mail
address, credit card number, etc.);
• validators for checking if a numerical value lies in a given range (less than, greater than,
between, etc.);
• validators working as “proxies” to other validators (ValidatorChain, StaticValidator
and Callback).
¹Here, we only consider the standard validator classes belonging to the Zend\Validator namespace. But, actually there are more validators
that can be considered as standard. We will cover them in further chapters.
Checking Input Data with Validators 272
Isbn Returns boolean true if and only if value is a valid International Standard
Book Number (ISBN).
Ip Returns true if value is a valid IP address; otherwise returns false.
Uri Returns true if and only if the value is an Uniform Resource Identifier (URI).
Between Returns true if the value lies in certain range; otherwise returns false.
LessThan Returns boolean true if the value is less than certain number; otherwise
returns false.
GreaterThan Returns true if and only if value is greater than certain number.
Identical Returns boolean true if a the value matches a given token.
Step Checks whether the value is a scalar and a valid step value.
Csrf This validator checks if the provided token matches the one previously
generated and stored in a PHP session.
Date Returns true if value is a valid date of the certain format.
DateStep Returns boolean true if a date is within a valid step.
InArray Returns true if value is contained in the given array; otherwise returns false.
Digits Returns boolean true if and only if $value only contains digit characters.
Hex Returns true if and only if value contains only hexadecimal digit characters.
IsInstanceOf Returns true if value is instance of certain class; otherwise returns false.
NotEmpty Returns true if value is not an empty value.
Regex Returns true if value matches against given pattern; otherwise returns false.
StringLength Returns true if the string length lies within given range.
Explode Splits the given value in parts and returns true if all parts pass the given check.
StaticValidator This validator allows to execute another validator without explicitly
instantiating it.
Callback This validator allows to execute a custom validation algorithm through the
user-provided callback function.
ValidatorChain Wrapper validator allowing to organize several validators in a chain. Attached
validators are run in the order in which they were added to the chain (FIFO).
below for possible validation errors that the EmailValidator returns if you pass it the “abc@ewr”
value (the back-slash (‘’) character indicates line breaks where code doesn’t fit book page):
array(3) {
["emailAddressInvalidHostname"] =>
string(51) "'ewr' is not a valid hostname for \
the email address"
["hostnameInvalidHostname"] =>
string(66) "The input does not match the expec\
ted structure for a DNS hostname"
["hostnameLocalNameNotAllowed"] =>
string(84) "The input appears to be a local ne\
twork name but local network names are not allowed"
}
Validator’s getMessages() method will return an array of messages that explain why the
validation failed. The array keys are validation failure message identifiers, and the array values
are the corresponding human-readable message strings.
If isValid() method was never called or if the most recent isValid() call returned true, then
the getMessages() method returns an empty array. Also, when you call isValid() several times,
the previous validation messages are cleared, so you see only validation errors from the last call.
Some validators may work with input data in certain format only (for example, a validator may
require that the input data be a string, but not an array). If you pass it data in unacceptable
format, the validator may throw an Zend\Validator\Exception\RuntimeException exception
or raise a PHP warning.
The methods provided by the EmailAddress validator are listed in table 9.2:
As you can see from table, the EmailAddress validator, additionally to the isValid() and
getMessages() methods, provides the constructor method to which you can (optionally) pass
the complete list of options for initializing the validator.
All standard validators have the constructor method (optionally) accepting an array of
options for configuring the validator when instantiating it manually.
The EmailAddress class also provides a number of methods that can be used for setting specific
validator options.
The useDomainCheck() method tells whether to check the host name for correctness, or not. By
default, this check is enabled. The setAllow() method provides an ability to specify which types
of host names are allowed. You can pass an OR combination of the ALLOW_-prefixed constants ⁴
to the setAllow() method:
Internally, the EmailAddress validator uses the Hostname validator for checking the
host name part of an E-mail address. Optionally, you can attach a custom host name
validator by using the setHostnameValidator() method, however it is unlikely you
will need to do such.
The useMxCheck() method tells whether the validator should connect to the recipient’s host and
query the DNS server for the MX record(s). If the server has no MX records, than the validation
fails. You can additionally use the useDeepMxCheck() method to tell the validator to compare the
mail server addresses extracted from the MX records against the black list of reserved domain
names, and perform additional checks per each detected address.
It is not recommended to perform MX check (and deep MX check), because that may
take a lot of time and increase the web page load time. By default, these checks are
disabled.
Below, we provide code examples showing two equivalent methods of manual creating of an
instance of the EmailAddress validator, setting its options and checking an input value:
Example 1. Passing options to the constructor method.
⁴The ALLOW_-prefixed constants are provided by the Hostname validator.
Checking Input Data with Validators 277
1 <?php
2 // Optionally, define a short alias for the validator class name.
3 use Zend\Validator\EmailAddress;
4 use Zend\Validator\Hostname;
5
6 // Create an instance of the validator, passing options to the constructor.
7 $validator = new EmailAddress(array(
8 'allow' => Hostname::ALLOW_DNS|Hostname::ALLOW_IP|Hostname::ALLOW_LOCAL,
9 'mxCheck' => true,
10 'deepMxCheck' => true
11 ));
12
13 // Validate an E-mail address.
14 $isValid = $validator->isValid('name@example.com'); // Returns true.
15 $isValid2 = $validator->isValid('abc'); // Returns false.
16
17 if(!$isValid2) {
18 // Get error messages in case of validation failure.
19 $errors = $validator->getMessages();
20 }
In the code above, we create the EmailAddres validator object with the help of the new operator
(line 7). We pass the array of options to the constructor. We use the allow key to allow an E-mail
address to be a domain name, an IP address or local network address. Also, we use the mxCheck
and deepMxCheck to enable MX record check and deep MX record check, respectively.
In line 14, we call the isValid() method and pass it the string value “name@example.com” to
be checked. The expected output of this call is the boolean true.
In line 15, we pass the “abc” string value to the validator. The validation procedure is expected to
fail (false is returned). Then, the error messages are retrieved with the getMessages() method
(line 19).
Example 2. Without passing options to the constructor.
1 <?php
2 // Optionally, define a short alias for the validator class name.
3 use Zend\Validator\EmailAddress;
4 use Zend\Validator\Hostname;
5
6 // Create an instance of the validator.
7 $validator = new EmailAddress();
8
9 // Optionally, configure the validator
10 $validator->setAllow(
11 Hostname::ALLOW_DNS|Hostname::ALLOW_IP|Hostname::ALLOW_LOCAL);
12 $validator->useMxCheck(true);
Checking Input Data with Validators 278
13 $validator->useDeepMxCheck(true);
14
15 // Validate an E-mail address.
16 $isValid = $validator->isValid('name@example.com'); // Returns true.
17 $isValid2 = $validator->isValid('abc'); // Returns false.
18
19 if(!$isValid2) {
20 // Get error messages in case of validation failure.
21 $errors = $validator->getMessages();
22 }
In the code above, we create the EmailAddres validator object with the help of the new operator
(line 7).
In lines 10-13, we configure the validator. We call the setAllow() method to allow an E-
mail address to be a domain name, an IP address or local network address. Also, we use the
useMxCheck() and useDeepMxCheck() to enable MX record check and deep MX record check,
respectively.
In line 16, we call the isValid() method and pass it the string value “name@example.com” to
be checked. The expected output of this call is the boolean true.
In line 17, we pass the “abc” string value to the validator. The validation procedure is expected
to fail. Then, the error messages are retrieved with the getMessages() method (line 21).
1 <?php
2 // Create and execute the EmailAddress validator through StaticValidator prox\
3 y.
4 $validatedValue = \Zend\Filter\StaticValidator::execute('name@example.com',
5 'EmailAddress',
6 array(
7 'allow' =>
8 Hostname::ALLOW_DNS|
9 Hostname::ALLOW_IP|
10 Hostname::ALLOW_LOCAL,
11 'mxCheck' => true,
12 'deepMxCheck' => true
13 ));
14
15 // The expected output is boolean true.
Checking Input Data with Validators 279
The StaticValidator class provides the execute() static method which takes three arguments:
the input value, the name of the filter to apply, and the array of filter-specific options.
In line 3, we call the execute() method to automatically create the EmailAddress validator, call
its setAllowDns(), useMxCheck() and useDeepMxCheck() methods, and pass the input value to
its isValid() method. This is very useful, because can be accomplished in a single call.
1 <?php
2 // It is assumed that you call the following code inside of the form model's
3 // addInputFilter() method.
4
5 $inputFilter->add(array(
6 // ...
7 'validators' => array(
8 array(
9 'name' => 'EmailAddress',
10 'options' => array(
11 'allow' => \Zend\Validator\Hostname::ALLOW_DNS,
12 'useMxCheck' => false,
13 'useDeepMxCheck' => false,
14 ),
15 ),
16 ),
17 // ...
18 );
In the code above, we call the add() method provided by the InputFilter container class (line
5). The add() method takes an array which has the validators key. You typically register the
validators under that key (line 7). Validators registered under that key are inserted into validator
chain in the order they appear in the list.
Checking Input Data with Validators 280
A validator configuration typically consists of the name (line 9) and options (line 10). The name
is a fully qualified validator class name (e.g. \Zend\Validator\EmailAddress) or its short alias
(EmailAddress). The options is an array consisting of validator-specific options. When the
factory class instantiates the validator, it passes the list of options to the validator’s constructor,
and the constructor initializes the validator as needed.
A standard validator’s alias is typically the same as class name. For example, the class
Zend\Validator\EmailAddress has the short alias EmailAddress.
The validator plugin manager is internally used by the InputFilter container class for instan-
tiating the standard validators.
9.6.1.1 Ip Validator
The Ip validator class is designed to check if the input value is a valid IP address. If the input value
is an IPv4 ⁵ address, IPv6 ⁶ address, IPvFuture ⁷ address, or IPv6 literal ⁸ address, the validator
returns boolean true; otherwise it returns false. On failure, error messages can be extracted
with the validator’s getMessages() method.
Public methods provided by the Ip validator are listed in table 9.3:
⁵An Internet Protocol version 4 (IPv4) address typically consists of four octets of the address expressed separated by periods, like
“192.168.56.101”.
⁶An Internet Protocol version 6 (IPv6) address typically consists of eight groups of four hexadecimal digits separated by colons, such as
“2001:0db8:85a3:0000:0000:8a2e:0370:7334”.
⁷IPvFuture is loosely defined in the Section 3.2.2 of RFC 3986.
⁸A literal IPv6 address is a modification of a usual IPv6 address for using inside of a URL. (The problem with original IPv6 addresses is that
the “:” and “.” characters are delimiters in URLs.)
Checking Input Data with Validators 281
By default all the above are allowed, except the IPv6 literal address.
Below, a code example demonstrating the usage of the Ip validator is provided.
<?php
use Zend\Validator\Ip;
// Create Ip validator.
$validator = new Ip();
The Hostname validator is designed to check if a given value is a host name belonging to set of
allowed host name types. The types are:
Checking Input Data with Validators 282
The public methods provided by the validator are listed in table 9.4:
You can set which host name types are allowed with the setAllow() method. It accepts a
combination of the following constants:
1. If the input value looks like an IP address, it is checked with the internal IP address valida-
tor. You can override which IP address validator to use for this by the setIpValidator()
method.
2. The host name is separated into domain parts (separated with dot “.” character).
3. The top-level domain is checked against the white list of available TLDs. (You can disable
this check with the useTldCheck() method.)
Checking Input Data with Validators 283
4. Each domain part is checked based on the rules for acceptable domain names. If a domain
name is an IDN ⁹, it is checked against the rules for valid IDNs. (You can disable IDN check
with useIdnCheck() method.)
Below, a code example demonstrating the usage of the Hostname validator is provided.
<?php
use Zend\Validator\Hostname;
The Uri validator is designed to check whether the input value is a Uniform Resource Identifier
(URI) ¹⁰. On failure, error messages can be extracted with the validator’s getMessages() method.
Don’t be confused with the term URI. In most cases, you may think of URI as of a usual
URL.
The public methods provided by the Uri validator are listed in table 9.5:
Internally, the Uri validator uses so called URI handler object, which is responsible for parsing an
URI string. By default, Zend\Uri\Uri class is used as the URI handler. (You can set your custom
URI handler with the setUriHandler() method, if you wish.)
An URI can be absolute or relative. For example, an absolute URI is “http://example.com/blog/2014/02/02/edit”,
while a relative URI is “2014/02/02/edit”. You can specify whether the validator should consider
absolute and/or relative URIs acceptable. For that, you use the setAllowAbsolute() and
setAllowRelative() methods, respectively. By default, both are treated as acceptable URI types.
Below, a code example demonstrating the usage of the Uri validator is provided.
<?php
use Zend\Validator\Uri;
// Check an URI.
$isValid = $validator->validate(
'http://site1.example.com/application/index/index');
// Returns true.
$isValid2 = $validator->validate('index/index');
// Returns true.
The Date validator is intended for checking whether the input data is a date in a given format.
On failure, error messages can be extracted with the validator’s getMessages() method.
Public methods provided by the Date validator are listed in table 9.6:
Checking Input Data with Validators 285
To set the expected date format, you can use the setFormat() method.
Internally, the DateTimeFormatter filter uses the DateTime class from the PHP standard
library for converting and formatting dates. For available date formats, please refer to
the PHP documentation for the DateTime class.
Below, a code example demonstrating the usage of the Date validator is provided.
<?php
use Zend\Validator\Date;
// Configure validator.
$validator->setFormat('Y-m-d');
This validator allows you to validate if a given string conforms some regular expression. It returns
true if the string matches the regular expression, otherwise it returns false. On failure, error
messages can be extracted with the validator’s getMessages() method.
The public methods provided by the Regex validator are listed in table 9.7:
The setPattern() method allows to set the regular expression to match against.
For regular expressions syntax and examples, it is recommended that your refer to the
PCRE Patterns section of the PHP documentation.
Below, a code example demonstrating the usage of the Regex validator is provided. It uses the
regular expression to check if the input string is a valid IPv4 address (such address typically
consists of four groups of digits separated with dots).
<?php
use Zend\Validator\Regex;
The NotEmpty validator allows to check if input value is not empty. This is often useful when
working with form elements or other user input, where you can use it to ensure required elements
have values associated with them.
The public methods provided by the NotEmpty validator are listed in table 9.8:
Checking Input Data with Validators 287
The setType() method specifies which variable types to consider as an empty value. This method
accepts the single argument $type which can be either an OR combination of the constants listed
in table 9.9, or an array containing the literal equivalents of those constants.
Below, a code example demonstrating the usage of the NotEmpty validator is provided.
Checking Input Data with Validators 288
<?php
use Zend\Validator\NotEmpty;
// Configure validator.
$validator->setType(NotEmpty::ALL);
The Between validator checks whether a number lies in a certain range (min, max), either
inclusively (by default) or exclusively.
The public methods provided by the Between validator are listed in table 9.10:
The range can be set with the setMin() and setMax() methods.
By default, the validator performs inclusive comparisons (to check if the value belongs to the
given range, it compares if the value is greater or equal to its lower bound, and if the value is
lower or equal to its upper bound). You can change this with the setInclusive() method. It tells
the validator whether to perform inclusive comparisons (pass true as the argument) or exclusive
comparisons (pass false as the argument).
Below, a code example demonstrating the usage of the Between validator is provided.
Checking Input Data with Validators 289
<?php
use Zend\Validator\Between;
// Configure validator.
$validator->setMin(1);
$validator->setMax(10);
$validator->setInclusive(true);
The InArray validator checks whether the input value belongs to the given array of values. The
public methods provided by the InArray validator are listed in table 9.11:
The setHaystack() method allows to set the array of allowed values. The isValid() method
will search through this array for the presence of the input $value.
If the array contains nested values and you want to search among them recursively, then use
setRecursive() method. This method takes the single boolean flag. If the flag is true, than the
search will be performed recursively; otherwise the nested levels will be ignored.
The setStrict() method provides an ability to tell the validator how to compare the input value
and the values in array. This may be a combination of the following constants:
Below, a code example demonstrating the usage of the InArray validator is provided.
<?php
use Zend\Validator\InArray;
// Configure validator.
$validator->setHaystack(array(1, 3, 5));
// Perform validation.
$isValid1 = $validator->isValid(1); // returns true.
$isValid2 = $validator->isValid(2); // returns false.
The StringLength validator checks whether the input string length belongs to the given range,
inclusively. It returns true if and only if the string length of value is at least the min option and
no greater than the max option (when the max option is not null).
The public methods provided by the StringLength validator are listed in table 9.12:
By default, the StringLength validator considers any string length as valid. Use the setMin()
and/or setMax() methods to set lower and upper limits for the allowable string length. There are
three possible ways you can do that:
• Use only the setMin() method to allow strings with a lower-bound minimum length, but
unbound upper length;
• Use only the setMax() method to allow strings with zero minimum length and an upper-
bound maximum length;
Checking Input Data with Validators 291
• Use both the setMin() and setMax() methods to allow strings with a length laying
between the lower and upper bounds.
By default, the PHP engine uses the UTF-8 encoding for strings. If your input string uses a
different encoding, you should specify it encoding with the setEncoding() validator’s method.
Below, a code example demonstrating the usage of the StringLength validator is provided.
<?php
use Zend\Validator\StringLength;
The ValidatorChain class is internally used by the InputFilter container class for
storing the sequence of validators attached to a form model’s field.
Public methods provided by the ValidatorChain class are presented in table 9.13:
An example validator chain is shown in figure 9.2. It consists of the NotEmpty validator followed
by the StringLength validator, which in turn is followed by the Date validator. When this chain
is executed, first, the ‘NotEmpty’ validator is run checking that the value is a non-empty value,
then the StringLength validator is run checking that the length of the input string belongs to
range (1, 16), inclusively, and finally, the Date validator is run checking that the input value is a
date in format “YYYY-MM-DD”.
To construct the filter chain like in figure 9.2, we can use the following code:
<?php
// Instantiate the validator chain.
$validator = new \Zend\Validator\ValidatorChain();
As you can see from the table, the Callback validator provides the setCallback() and
setCallbackOptions() methods that can be used to set the callback function or class method
and (optionally) pass it one or several parameters.
9.6.4.1 Example
To demonstrate the usage of the Callback validator, let’s add the phone number validator to our
ContactForm form model class. The validator would check a telephone number entered by site
visitor.
The validator needs to be able to check for two common phone number formats:
Because ZF2 does not provide a standard validator for accomplishing such phone filtering
operation, we will use the Callback wrapper validator. To do that, we will make the following
changes to the code of our ContactForm class:
1 <?php
2 // ...
3 class ContactForm extends Form
4 {
5 // ..
6 protected function addElements() {
7 // ...
8
9 // Add "phone" field
10 $this->add(array(
11 'type' => 'text',
12 'name' => 'phone',
13 'attributes' => array(
14 'id' => 'phone'
15 ),
16 'options' => array(
Checking Input Data with Validators 294
63 return ($matchCount!=0)?true:false;
64 }
65 }
In the code above, we create the phone field in our ContactForm (if you already have such field,
just ignore this).
In line 40, we add the PhoneValidator validator to the input filter’s validator chain for the
“phone” field.
In lines 45-58, we have the validatePhone() callback method. The method accepts three
arguments: the $value parameter is the phone number to validate, the $context parameter
receives the values of every field of the form (it may be needed for some validators to refer
to the values of other form fields, too); and the $format parameter is the expected format of the
phone number (“intl” or “local”).
Inside of the callback method, we do the following:
1. Calculate the correct length of the phone, check whether the length is correct for the
selected phone number format.
2. Match the phone number against the regular expression pattern for the selected phone
format.
As you might remember, the base concrete class for all standard validators is the
AbstractValidator class. By analogy, we will also derive our custom PhoneValidator
validator from that base class.
We plan to have the following methods in our PhoneValidator validator class (see table 9.18):
• If a non-scalar value is passed to the validator, it will generate the error message “The
phone number must be a scalar value”;
• If the international phone format is selected, and the phone number entered doesn’t
match this format, the validator will generate the message “The phone number must be in
international format”;
• If the local phone format is selected and the phone number entered doesn’t match the
format, the validator will generate the message “The phone number must be in local
format”.
To start, create the PhoneValidator.php file in the Application/Service directory under the
module’s source directory ¹¹. Put the following code into that file:
1 <?php
2 namespace Application\Service;
3
4 use Zend\Validator\AbstractValidator;
5
6 // This validator class is designed for checking a phone number for
7 // conformance to the local or to the international format.
8 class PhoneValidator extends AbstractValidator {
9
10 // Phone format constants.
11 const PHONE_FORMAT_LOCAL = 'local'; // Local phone format.
12 const PHONE_FORMAT_INTL = 'intl'; // International phone format.
13
14 // Available validator options.
15 protected $options = array(
16 'format' => self::PHONE_FORMAT_INTL
17 );
18
19 // Validation failure message IDs.
20 const NOT_SCALAR = 'notScalar';
21 const INVALID_FORMAT_INTL = 'invalidFormatIntl';
22 const INVALID_FORMAT_LOCAL = 'invalidFormatLocal';
23
24 // Validation failure messages.
25 protected $messageTemplates = array(
26 self::NOT_SCALAR => "The phone number must be a scalar value",
27 self::INVALID_FORMAT_INTL
28 => "The phone number must be in international format",
29 self::INVALID_FORMAT_LOCAL => "The phone number must be in local format",
30 );
¹¹The PhoneValidator class may be considered as a service model, because its goal is to process data, not to store it.
Checking Input Data with Validators 297
31
32 // Constructor.
33 public function __construct($options = null) {
34
35 // Set filter options (if provided).
36 if(is_array($options)) {
37
38 if(isset($options['format']))
39 $this->setFormat($options['format']);
40 }
41
42 // Call the parent class constructor.
43 parent::__construct($options);
44 }
45
46 // Sets phone format.
47 public function setFormat($format) {
48
49 // Check input argument.
50 if($format!=self::PHONE_FORMAT_LOCAL &&
51 $format!=self::PHONE_FORMAT_INTL) {
52 throw new \Exception('Invalid format argument passed.');
53 }
54
55 $this->options['format'] = $format;
56 }
57
58 // Validates a phone number.
59 public function isValid($value) {
60
61 if(!is_scalar($value)) {
62 $this->error(self::NOT_SCALAR);
63 return $false; // Phone number must be a scalar.
64 }
65
66 // Convert the value to string.
67 $value = (string)$value;
68
69 $format = $this->options['format'];
70
71 // Determine the correct length and pattern of the phone number,
72 // depending on the format.
73 if($format == self::PHONE_FORMAT_INTL) {
74 $correctLength = 16;
75 $pattern = '/^\d \(\d{3}\) \d{3}-\d{4}$/';
76 } else { // self::PHONE_FORMAT_LOCAL
Checking Input Data with Validators 298
77 $correctLength = 8;
78 $pattern = '/^\d{3}-\d{4}$/';
79 }
80
81 // First check phone number length
82 $isValid = false;
83 if(strlen($value)==$correctLength) {
84 // Check if the value matches the pattern.
85 if(preg_match($pattern, $value))
86 $isValid = true;
87 }
88
89 // If there were an error, set error message.
90 if(!$isValid) {
91 if($format==self::PHONE_FORMAT_INTL)
92 $this->error(self::INVALID_FORMAT_INTL);
93 else
94 $this->error(self::INVALID_FORMAT_LOCAL);
95 }
96
97 // Return validation result.
98 return $isValid;
99 }
100 }
From line 2, you can see that the validator class lives in the Application\Service namespace.
In line 8, we define the PhoneValidator class. We derive our validator class from the AbstractValidator
base class to reuse the functionality it provides. Line 4 contains the short alias for the AbstractValidator
class.
In lines 11-12, for convenience, we define the phone format constants (PHONE_FORMAT_INTL for
international format and PHONE_FORMAT_LOCAL for local format). These are the equivalents of the
“intl” and “local” strings, respectively.
In lines 15-17, we define the $options private array variable which is an array having the single
key named “format”. This key will contain the phone format option for our validator.
In lines 20-22, we define the error message identifiers. We have three identifiers (NOT_SCALAR,
INVALID_FORMAT_INTL and INVALID_FORMAT_LOCAL), because our validator may generate three
different error messages. These identifiers are intended for distinguishing different error mes-
sages by machine, not by human.
In lines 25-30, we have the $messageTemplates array variable that contains mapping before
error message identifiers and their textual representations. The textual messages are intended
for displaying to a human.
In lines 33-44, we have the constructor method which takes the single argument $options. When
constructing the validator manually, you may omit this parameter. But, when the validator is
Checking Input Data with Validators 299
constructed by the factory class, the factory will pass validation options to validator’s constructor
through this argument.
In lines 47-56 and 59-64, we have the setFormat() and getFormat() methods that allow to set
and retrieve the current phone format, respectively.
In lines 59-99, we have the isValid() method. This method encapsulates the phone number
checking algorithm. It takes the $value parameter, performs the regular expression match, and
returns true on success.
On failure, the isValid() method it returns the boolean false, and the list of errors can be
retrieved by the getMessages() method.
You might notice that we didn’t define the getMessages() method in our
PhoneValidator class. This is because that we inherited this method from the
AbstractValidator base class. Inside of our isValid() method, for generating error
messages, we also used the error() protected method provided by the base class (lines
62, 92, 94).
$inputFilter->add(array(
'name' => 'phone',
'required' => true,
'validators' => array(
array(
array(
'name' => '\Application\Service\PhoneValidator',
'options' => array(
'format' => PhoneValidator::PHONE_FORMAT_INTL
)
),
),
// ...
),
// ...
)
);
You can see how the PhoneValidator validator works in the Form Demo sample application
bundled with this book. Open the “http://localhost/contactus” page in your web browser. If you
Checking Input Data with Validators 300
enter some phone number in an incorrect format, the validator will display an error (see figure
9.3).
If you wish, you can use the PhoneValidator outside of forms, as shown in code example below:
<?php
use Application\Service\PhoneValidator;
if(!$isValid2) {
// Get validation errors.
$errors = $validator->getMessages();
}
Let’s assume we implement a payment gateway system and need to create a web page displaying
a payment history for the given credit card on given date. This page can be handled by some
paymentHistoryAction() action of a controller class, and the credit card number and date will be
extracted from GET variables. For the paymentHistoryAction() method, we need to implement
some security checks:
• we want to ensure that the credit card number looks like a typical credit card number
“4532-7103-4122-1359” (conforms to ISO/IEC 7812 standard);
• and that the date is in ‘YYYY-MM-DD’ format.
1 <?php
2 namespace Application\Controller;
3
4 use Zend\Mvc\Controller\AbstractActionController;
5 use Zend\View\Model\ViewModel;
6 use Zend\Filter\StaticFilter;
7 use Zend\Validator\StaticValidator;
8
9 class IndexController extends AbstractActionController {
10
11 // An action which shows the history of a credit
12 // card operations on certain date.
13 public function paymentHistoryAction() {
14
15 // Get parameters from GET.
16 $cardNumber = (string)$this->params()->fromQuery('card', '');
17 $date = (string)$this->params()->fromQuery('date', date("Y-m-d"));
18
19 // Validate credit card number.
20 $isCardNumberValid = StaticValidator::execute($cardNumber, 'CreditCard');
21 if(!$isCardNumberValid) {
22 throw new \Exception('Not a credit card number.');
23 }
24
25 // Convert date to the right format.
26 $date = StaticFilter::execute($date, 'DateTimeFormatter',
27 array('format'=>'Y-m-d'));
28
29 // The rest of action code goes here...
30
31 return new ViewModel();
32 }
33 }
Checking Input Data with Validators 302
Inside the action method, we use the params() controller plugin (lines 16-17) to retrieve two
variables from $_GET super-global array: the card variable (credit card number) and the date
variable (the date).
In line 20, we validate the credit card number with the help of the CreditCard validator. If the
card number is not acceptable, we throw an exception indicating an error (line 22).
In line 26, we use the DateTimeFormatter filter to convert the date to the right format.
9.9 Summary
Validators are designed to take some input data, check it for correctness, and return a boolean
result telling whether the data is correct (and error messages if the data has some errors).
In Zend Framework 2, there are several groups of standard validators:
In some cases, a standard validator is not suitable, and you need to apply your own checking
algorithm to the input data. In such case, you may use either the Callback validator or write
your own custom validator class.
10. Uploading Files with Forms
In this chapter, you will learn about uploading files with forms. First, we will review the basic
theory like HTTP file upload capability and binary content transfer encoding, and then provide
a complete working Image Gallery example showing how to upload images to a web server.
ZF2 components covered in this chapter:
Component Description
Zend\Form Contains base form model classes.
Zend\Filter Contains various filters classes.
Zend\Validator Implements various validator classes.
Zend\InputFilter Implements a container for filters/validators.
¹HTTP file uploads are described in RFC-1867. This mechanism allows to upload large files by using binary content transfer encoding. The
“multipart/form-data” encoding type is utilized for this purpose.
²The HTTP GET method is inefficient for file uploads, because URL length has some upper limit. Also, URL-encoding file data greatly
increases the URL length.
Uploading Files with Forms 304
The file element has the Browse… button allowing to pick a file for upload. When the site user
picks some file and clicks the Submit button on the form, the web browser will send an HTTP
request to the web server, and the request will contain the data of the file being uploaded. The
example below illustrates how the HTTP request may look like:
As you can see from the example above, the HTTP request with “multipart/form-data” encoding
type looks analogous to a usual HTTP request (has the status line, the headers, and the content
area), however it has the following important differences:
• Line 5 sets the “Content-Type” header with “multipart/form-data” value; The form is
assembled of the fields marked by the “boundary” – a unique randomly generated sequence
of characters delimiting form fields of each other.
• Lines 8-17 represent the content of the HTTP request. The form fields are delimited by the
“boundary” sequences (lines 8, 13, 17). The data of the file being uploaded are transmitted
in binary format (line 12), and that allows to reduce the content size to its minimum.
By default, PHP engine’s settings do not allow to upload large files (larger than 2MB).
In order to upload large files, you may need to edit the php.ini configuration file
and modify the post_max_size and upload_max_filesize parameters (please refer to
Appendix A for information on how to do that). Setting these with 100M allows to
upload files up to 100 Mb in size, and this would typically be sufficient. If you plan
to upload very large files up to 1 GB in size, than better set these with 1024M. Do not
forget to restart your Apache Web Server after editing the configuration file.
The $_FILES array is analogous to the $_GET and $_POST super-globals. The latter two
are used to store the GET and POST variables, respectively, while the first one is used
to store information about uploaded files.
For example, for the above mentioned simple upload form, the $_FILES super-global array will
look as follows (the output is generated with the var_dump() PHP function):
1 array (size=1)
2 'myfile' =>
3 array (size=5)
4 'name' => string 'somefile.txt' (length=12)
5 'type' => string 'text/plain' (length=10)
6 'tmp_name' => string 'C:\Windows\Temp\phpDC66.tmp' (length=27)
7 'error' => int 0
8 'size' => int 18
As you can see from the example above, the $_FILES array contains an entry per each uploaded
file. For each uploaded file, it contains the following information:
Uploading Files with Forms 306
PHP engine stores the uploaded files in a temporary location which is cleaned up as soon as the
PHP script execution ends. So, if you want to save the uploaded files to some directory for later
use, you need to utilize the move_uploaded_file() PHP function. The move_uploaded_file()
function takes two arguments: the first one is the name of the temporary file, and the second one
is the destination file name.
You might be confused why you cannot use the usual rename() PHP function for
moving the temporary uploaded file to its destination path. PHP has special function
for moving uploaded files for security reasons. The move_uploaded_file() function is
analogous to rename() function, but it takes some additional checks to ensure the file
was really transferred through HTTP POST request, and that the upload process has
finished without errors.
The following code example shows how to move the file uploaded with the simple form we have
considered above:
1 $destPath = '/path/to/your/upload/dir';
2 $result = move_uploaded_file($_FILES['myfile']['tmp_name'], $destPath);
3 if(!$result) {
4 // Some error occurred.
5 }
Above, in line 1, we set the the $destPath with the directory name where to save the uploaded
file.
In line 2, we call the move_uploaded_file() function and pass it two arguments: the path to the
temporary file and the destination path.
In line 3, we check the returned value of the function. If the operation is successful, the function
will return true. If some error occurs (for example, if directory permissions are insufficient to
save the file), the boolean false will be returned.
³MIME type, also known as “content type” is a standard identifier used on the Internet to indicate the type of data that a file contains. For
example the “text/plain” MIME type is assigned to a text file, while the “application/octet-stream” MIME type is assigned to a binary file.
Uploading Files with Forms 307
1 <?php
2 //...
3 class IndexController extends AbstractActionController
4 {
5 // An example controller action intended for handling file uploads.
6 public function uploadAction() {
7
8 // Get the whole $_FILES array.
9 $files = $this->getRequest()->getFiles();
10
11 // The same, but with Params controller plugin.
12 $files = $this->params()->fromFiles();
13
14 // Get a single entry of the $_FILES array.
15 $files = $this->params()->fromFiles('myfile');
16 }
17 }
In line 9 of the code above, we use the getRequest() method of the controller class for accessing
the Request object, and the getFiles() method of the request object to retrieve the information
about all upload files at once.
In line 12, we do the same thing with the Params controller plugin. We use its fromFiles()
method to get the information about all uploaded files.
If needed, you can extract the information for the specific file only. In line 15, we use the same
fromFiles() method and pass it the name of the file field to retrieve. This retrieves the single
file entry from the $_FILES super-global array.
In the code above, we call the add() method provided by the Form base class and pass it the
configuration array describing the element. The type key of the array (line 5) must be either
Zend\Form\Element\File class name or its short alias “file”.
• the file(s) were really uploaded through HTTP POST request, and were not just copied
from some directory;
• the file(s) were uploaded successfully (the error code is zero);
• the file names and/or extensions are acceptable (e.g., you may want to save JPEG files only,
and reject all others);
• the file size lies in the allowed range (e.g., you may want to ensure that the file is not too
big);
• total count of uploaded files doesn’t exceed some allowed limit.
For doing the checks like above, ZF2 provides a number of useful file validators (listed in table
10.1). Those validator classes belong to Zend\Validator component and live in Zend\Validator\File
namespace.
Uploading Files with Forms 309
As you can see from the table above, file validators may be roughly divided in the following
groups:
Uploading Files with Forms 310
• validators checking whether the file(s) were really uploaded through HTTP POST and
upload status is OK;
• validators checking the uploaded file count and file size;
• validators checking the file extension and MIME type;
• validators checking whether the file is a graphical image and checking image dimensions;
• and validators checking the file hash (or check sum) ⁴.
Please note that since file validators live in Zend\Validator\File namespace, their
short aliases (that you use when creating a validator with the factory) start with File
prefix. For example, the IsImage validator has FileIsImage alias.
We will show how to use some of these file validators in the Image Gallery code example later
in this chapter.
From the table, you can see that filters can be divided into the following groups:
• filters for moving uploaded files from a temporary location to their persistent directory;
• filters for encryption and decryption of files;
• filters for converting text files to upper-case and lower-case letters.
Please note that since file filters live in Zend\Filter\File namespace, their short
aliases (that you use when creating a filter with the factory) start with File prefix.
For example, the RenameUpload filter has FileRenameUpload alias.
⁴A file hash is used for checking file data integrity (for example, to ensure that file data is not corrupted). There are several hash algorithms
available (MD5, SHA-1, CRC32, etc.)
Uploading Files with Forms 311
The Encrypt and Decrypt filters allow to apply various encryption/decryption algorithms to the
uploaded file (concrete algorithm is attached by specifying the certain adapter). The LowerCase
and UpperCase filters are suitable for converting text files to lower- and upper-case, respectively
⁵.
The Rename filter allows to rename and/or move an arbitrary file (not only uploaded file). It uses
the rename() PHP function internally, and that’s why it is in general not recommended to use
this filter with uploaded files because of security reasons.
The RenameUpload filter seems to be much more useful than other filters, because it allows to
encapsulate the call of the move_uploaded_file() function and move/rename the uploaded file
from a temporary location to its persistent directory. We will show how to use the RenameUpload
filter in the Image Gallery code example later in this chapter.
1. for storing validation rules for uploaded files, a special class called FileInput should be
utilized instead of the Input class;
2. and, validators are applied before filters (!).
10.6.1 FileInput
For storing validation rules for uploaded files, you must use the FileInput class instead of the
usual Input class.
In your form model’s addInputFilter() private method, you add the validation rules for the
file input as follows:
1 $inputFilter->add(array(
2 'type' => 'Zend\InputFilter\FileInput',
3 'name' => 'file', // Element's name.
4 'required' => true, // Whether the field is required.
5 'filters' => array( // Filters.
6 // Put filter info here.
7 ),
8 'validators' => array( // Validators.
9 // Put validator info here.
10 )
11 );
⁵In the author’s opinion, the above mentioned four filters are not very useful when working with uploaded files, because you rarely need
to encrypt an uploaded file or convert it to lower case letters.
Uploading Files with Forms 312
Above, we set the “type” key (line 2) with the value Zend\InputFilter\FileInput class name.
The rest of keys is analogous to those we used before when adding validation rules for a form
model.
The behaviour of FileInput class differs from the Input in the following aspects:
1. It expects the data you pass as input to be in the $_FILES array format (an array entry
with tmp_name, error, type keys).
2. A Zend\Validator\File\Upload validator is automatically inserted before all other val-
idators into the validator chain of the input.
3. The validators inserted to the validator chain of the input are executed before the filters
inserted into its filter chain. This is opposite to the behaviour of the Input class).
For file uploads, validators are executed before filters. This behaviour is inverse to the
usual behaviour.
When working with uploaded files, we first need to check that data extracted from $_FILES
super-global array is correct, and then do anything else with the files (moving the file into a
storage directory, renaming it, etc.) Because of that, file validators need to be run first turn, and
filters to be executed last.
To see how this is performed, recall the typical workflow for a form:
• First, we call the setData() method to fill in the form with data.
• Call the isValid() method to execute filters and validators in the input filter attached to
form.
• On successful validation, call the getData() to extract the filtered and validated data from
the input filter attached to form.
• On failure, call the getMessages() to retrieve the validation error messages.
When using a FileInput input, the workflow is the same, however it is important to understand
what happens on each of its steps:
Please note that for FileInput input, the attached filters are only run if the getData()
method is called.
When you use both Input and FileInput inputs in your form’s input filter (which is a common
case), the filters are still executed first for usual inputs, but validators are executed first for file
inputs.
1 <?php
2 //...
3 class IndexController extends AbstractActionController
4 {
5 // This is the "upload" action displaying the Upload page.
6 public function uploadAction()
7 {
8 // Create the form model.
9 $form = new YourForm();
10
11 // Check if user has submitted the form.
12 if($this->getRequest()->isPost()) {
13
14 // Make certain to merge the files info!
15 $request = $this->getRequest();
16 $data = array_merge_recursive(
17 $request->getPost()->toArray(),
18 $request->getFiles()->toArray()
19 );
20
21 // Pass data to form.
22 $form->setData($data);
23
24 // Execute file validators.
25 if($form->isValid()) {
26
27 // Execute file filters.
28 $data = $form->getData();
29
30 // Redirect the user to another page.
Uploading Files with Forms 314
31 return $this->redirect()->toRoute('application/default',
32 array('controller'=>'index', 'action'=>'index'));
33 }
34 }
35
36 // Render the page.
37 return new ViewModel(array(
38 'form' => $form
39 ));
40 }
41 }
As you can see from the code above, the uploadAction() looks like a usual controller action
implementing a typical form workflow, however it has some aspects specific to file uploads
(marked with bold):
• In line 9, we create an instance of the ImageForm form model with the help of the new
operator.
• In line 12, we check whether the request is an HTTP POST request. If so, we get the data
from $_POST and $_FILES super-global PHP arrays and merge them into the single
array (lines 15-19). This is required to correctly handle uploaded files, if any. Then
we pass this array to the form model with the setData() method (line 22).
• In line 25, we call the form model’s isValid() method. This method runs the input filter
attached to the form model. For FileInput inputs, this will execute attached validators
only.
• If the data is valid, we call the getData() method (line 28). For the FileInput inputs, this
will run the attached file filters. The file filters, for example, could move the uploaded
files to the directory of residence.
• On success, in line 31, we redirect the user to the “index” action of the controller.
In the controller action above, you should remember three things: 1) merge $_POST and
$_FILES super-global arrays before you pass them to the form’s setData() method; 2)
use isValid() form’s method to check uploaded files for correctness (run validators);
3) use getData() form’s method to run file filters.
You can see the working Image Gallery example in the Form Demo sample application
bundled with this book.
Uploading Files with Forms 315
• the file field will allow the user to pick an image file for upload;
• and the submit button field allowing to send the form data to server.
The code of the ImageForm form model is presented below. It should be put to ImageForm.php
file stored in Application/Form directory under the module’s source directory:
1 <?php
2 namespace Application\Form;
3
4 use Zend\Form\Form;
5
6 // This form is used for uploading an image file.
7 class ImageForm extends Form
8 {
9 // Constructor.
10 public function __construct()
11 {
12 // Define form name.
13 parent::__construct('image-form');
14
15 // Set POST method for this form.
16 $this->setAttribute('method', 'post');
17
18 // Set binary content encoding.
19 $this->setAttribute('enctype', 'multipart/form-data');
20
21 $this->addElements();
22 }
23
24 // This method adds elements to form.
25 protected function addElements() {
26
27 // Add "file" field.
28 $this->add(array(
29 'type' => 'file',
30 'name' => 'file',
31 'attributes' => array(
32 'id' => 'file'
33 ),
Uploading Files with Forms 317
We have already discussed the form model creation and the code above should not cause any
problems in its understanding. We just want to attract the attention of the reader that in line 19,
we set the “multipart/form-data” value for the “enctype” attribute of the form to make the form
use binary encoding for its data.
• check if the uploaded file really was uploaded through HTTP POST method using the
UploadFile validator;
• check that the uploaded file is an image (JPEG, PNG, GIF, etc.) using the IsImage validator;
• check that image dimensions are within some allowed boundaries; we will do that with
the ImageSize validator;
• move the uploaded file to its residence directory using the RenameUpload filter.
To add form validation rules, modify the code of the ImageForm class as follows:
Uploading Files with Forms 318
1 <?php
2 namespace Application\Form;
3
4 use Zend\InputFilter\InputFilter;
5
6 // This form is used for uploading an image file.
7 class ImageForm extends Form
8 {
9 // Constructor
10 public function __construct()
11 {
12 // ...
13
14 // Add validation rules
15 $this->addInputFilter();
16 }
17
18 // ...
19
20 // This method creates input filter (used for form filtering/validation).
21 private function addInputFilter() {
22
23 $inputFilter = new InputFilter();
24 $this->setInputFilter($inputFilter);
25
26 // Add validation rules for the "file" field.
27 $inputFilter->add(array(
28 'type' => 'Zend\InputFilter\FileInput',
29 'name' => 'file',
30 'required' => true,
31 'validators' => array(
32 array(
33 'name' => 'FileUploadFile',
34 ),
35 array(
36 'name' => 'FileIsImage',
37 ),
38 array(
39 'name' => 'FileImageSize',
40 'options' => array(
41 'minWidth' => 128,
42 'minHeight' => 128,
43 'maxWidth' => 4096,
44 'maxHeight' => 4096
45 )
46 ),
Uploading Files with Forms 319
47 ),
48 'filters' => array(
49 array(
50 'name' => 'FileRenameUpload',
51 'options' => array(
52 'target'=>'./data/upload',
53 'useUploadName'=>true,
54 'useUploadExtension'=>true,
55 'overwrite'=>true,
56 'randomize'=>false
57 )
58 )
59 ),
60 )
61 );
62 }
63 }
• UploadFile validator (line 33) checks whether the uploaded file was really uploaded using
the HTTP POST method.
• IsImage validator (line 36) checks whether the uploaded file is an image file (PNG, JPG,
GIF, etc.). It does that by extracting MIME information from file data.
• ImageSize validator (line 39) allows to check that image dimensions lie in an allowed
range. In the code above, we check that the image is between 128 pixels and 4096 pixels in
width, and that the image height lies between 128 pixels and 4096 pixels.
In line 50, we add the RenameUpload filter and configure it (line 52) to save the uploaded file to
the APP_DIR/data/upload directory. The filter will use the same file name for the destination file
as the name of the original file (useUploadName option). If the file with such name already exists,
the filter will overwrite it (overwrite option).
For the IsImage validator to work, you have to enable PHP fileinfo extension. This
extension is already enabled in Linux Ubuntu, but in Windows, you have to manually
open the php.ini file and uncomment the following line:
;extension=php_fileinfo.dll
class ImageManager and put it to Application\Service namespace. We will also register this
service in the service manager component of the web application.
The ImageManager service class will have the following public methods (listed in table 10.3):
Method Description
getSaveToDir() Returns path to the directory where we save the image
files.
getSavedFiles() Returns the array of saved file names.
getImagePathByName($fileName) Returns the path to the saved image file.
getImageFileInfo($filePath) Retrieves the file information (size, MIME type) by
image path.
getImageFileContent($filePath) Returns the image file content. On error, returns
boolean false.
resizeImage($filePath, $desiredWidth) Resizes the image, keeping its aspect ratio.
In fact, we could put the code we plan to add into the service into the controller actions,
but that would make the controller fat and purely testable. By introducing the service
class, we improve the separation of concerns and code reusability.
Add the ImageManager.php file to the Application/Service directory under the module’s source
directory. Add the following code to the file:
1 <?php
2 namespace Application\Service;
3
4 // The image manager service.
5 class ImageManager
6 {
7
8 // The directory where we save image files.
9 private $saveToDir = './data/upload/';
10
11 // Returns path to the directory where we save the image files.
12 public function getSaveToDir()
13 {
14 return $this->saveToDir;
15 }
16 }
As you can see from the code above, we define the ImageManager class in line 5. It has the
private $uploadDir property ⁶ which contains the path to the directory containing our uploaded
files (line 9) (we store uploaded files in APP_DIR/data/upload directory).
⁶Although the ImageManager class is a service and focused on providing services, it can have properties intended for its internal use.
Uploading Files with Forms 321
The getSaveToDir() public method (line 12) allows to retrieve the path to the upload directory.
Next, we want to add the getSavedFiles() public method to the service class. The method will
scan the upload directory and return an array containing the names of the uploaded files. To add
the getSavedFiles() method, modify the code in the following way
1 <?php
2 //...
3
4 // The image manager service.
5 class ImageManager
6 {
7 //...
8
9 // Returns the array of uploaded file names.
10 public function getSavedFiles()
11 {
12 // The directory where we plan to save uploaded files.
13
14 // Check whether the directory already exists, and if not,
15 // create the directory.
16 if(!is_dir($this->saveToDir)) {
17 if(!mkdir($this->saveToDir)) {
18 throw new \Exception('Could not create directory for uploads: ' .
19 error_get_last());
20 }
21 }
22
23 // Scan the directory and create the list of uploaded files.
24 $files = array();
25 $handle = opendir($this->saveToDir);
26 while (false !== ($entry = readdir($handle))) {
27
28 if($entry=='.' || $entry=='..')
29 continue; // Skip current dir and parent dir.
30
31 $files[] = $entry;
32 }
33
34 // Return the list of uploaded files.
35 return $files;
36 }
37 }
In the getSavedFiles() method above, we first check if the upload directory exists (line 16), and
if not, we try to create it (line 17). Then, we get the list of files in the directory (lines 24-32) and
return it to the caller.
Uploading Files with Forms 322
Next, we add the three methods for getting information about an uploaded file:
• the getImagePathByName() method will take the file name and prepend the path to upload
directory to that file name;
• the getImageFileInfo() method will retrieve MIME information about the file and its
size in bytes;
• and the getImageFileContent() will read file data and return them as a string.
1 <?php
2 //...
3
4 // The image manager service.
5 class ImageManager
6 {
7 //...
8
9 // Returns the path to the saved image file.
10 public function getImagePathByName($fileName)
11 {
12 // Take some precautions to make file name secure.
13 str_replace("/", "", $fileName); // Remove slashes.
14 str_replace("\\", "", $fileName); // Remove back-slashes.
15
16 // Return concatenated directory name and file name.
17 return $this->saveToDir . $fileName;
18 }
19
20 // Returns the image file content. On error, returns boolean false.
21 public function getImageFileContent($filePath)
22 {
23 return file_get_contents($filePath);
24 }
25
26 // Retrieves the file information (size, MIME type) by image path.
27 public function getImageFileInfo($filePath)
28 {
29 // Try to open file
30 if (!is_readable($filePath)) {
31 return false;
32 }
33
34 // Get file size in bytes.
35 $fileSize = filesize($filePath);
Uploading Files with Forms 323
36
37 // Get MIME type of the file.
38 $finfo = finfo_open(FILEINFO_MIME);
39 $mimeType = finfo_file($finfo, $filePath);
40 if($mimeType===false)
41 $mimeType = 'application/octet-stream';
42
43 return array(
44 'size' => $fileSize,
45 'type' => $mimeType
46 );
47 }
48 }
Finally, we want to add the image resizing functionality to the ImageManager class. The image
resizing functionality will be used for creating small thumbnail images. Add the resizeImage()
method to the ImageManager class as follows:
1 <?php
2 //...
3 class ImageManager
4 {
5 //...
6
7 // Resizes the image, keeping its aspect ratio.
8 public function resizeImage($filePath, $desiredWidth = 240)
9 {
10 // Get original image dimensions.
11 list($originalWidth, $originalHeight) = getimagesize($filePath);
12
13 // Calculate aspect ratio
14 $aspectRatio = $originalWidth/$originalHeight;
15 // Calculate the resulting height
16 $desiredHeight = $desiredWidth/$aspectRatio;
17
18 // Resize the image
19 $resultingImage = imagecreatetruecolor($desiredWidth, $desiredHeight);
20 $originalImage = imagecreatefromjpeg($filePath);
21 imagecopyresampled($resultingImage, $originalImage, 0, 0, 0, 0,
22 $desiredWidth, $desiredHeight, $originalWidth, $originalHeight);
23
24 // Save the resized image to temporary location
25 $tmpFileName = tempnam("/tmp", "FOO");
26 imagejpeg($resultingImage, $tmpFileName, 80);
27
28 // Return the path to resulting image.
Uploading Files with Forms 324
29 return $tmpFileName;
30 }
31 }
The resizeImage() method above takes two arguments: $filePath (the path to the image file),
and $desiredWidth (the width of the thumbnail image). Inside the method, we first calculate an
appropriate thumbnail image height (lines 11-16) preserving its aspect ratio. Then, we resize the
original image as needed and save it to a temporary file (lines 19-26).
As the ImageManager class is ready, you have to register the ImageManager service in the
service manager component of the web application by adding the following lines to the
module.config.php configuration file:
<?php
return array(
// ...
'service_manager' => array(
// ...
'invokables' => array(
// Register the ImageManager service
'ImageManager'=>'Application\Service\ImageManager',
),
),
// ...
);
By doing this, you will be able to get a singleton instance of the service in any controller of your
web application as shown in the code example below:
$imageManager = $this->getServiceLocator()->get('ImageManager');
To start, create the ImageController.php file in the Application/Controller directory under the
module’s source directory. Put the following stub code into the file:
1 <?php
2 namespace Application\Controller;
3
4 use Zend\Mvc\Controller\AbstractActionController;
5 use Zend\View\Model\ViewModel;
6 use Application\Form\ImageForm;
7
8 // This controller is designed for managing image file uploads.
9 class ImageController extends AbstractActionController
10 {
11 // This is the default "index" action of the controller. It displays the
12 // Image Gallery page which contains the list of uploaded images.
13 public function indexAction()
14 {
15 }
16
17 // This action shows the image upload form. This page allows to upload
18 // a single file.
19 public function uploadAction()
20 {
21 }
22
23 // This is the 'file' action that is invoked when a user wants to
24 // open the image file in a web browser or generate a thumbnail.
25 public function fileAction()
26 {
27 }
28 }
In the code above, we defined the ImageController class living in the Application\Controller
namespace and added three action method stubs into the class: indexAction() (line 13),
uploadAction() (line 19) and fileAction() (line 25). Next, we will populate those action
methods with the code.
First, we will complete the uploadAction() method of our controller. This action method will
handle the Upload a New Image web page containing the upload form. The form will provide an
ability to upload an image file to the gallery.
Change the ImageController.php file as follows:
Uploading Files with Forms 326
1 <?php
2 //...
3 class ImageController extends AbstractActionController
4 {
5 //...
6 public function uploadAction()
7 {
8 // Create the form model.
9 $form = new ImageForm();
10
11 // Check if user has submitted the form.
12 if($this->getRequest()->isPost()) {
13
14 // Make certain to merge the files info!
15 $request = $this->getRequest();
16 $data = array_merge_recursive(
17 $request->getPost()->toArray(),
18 $request->getFiles()->toArray()
19 );
20
21 // Pass data to form.
22 $form->setData($data);
23
24 // Validate form.
25 if($form->isValid()) {
26
27 // Move uploaded file to its destination directory.
28 $data = $form->getData();
29
30 // Redirect the user to "Image Gallery" page.
31 return $this->redirect()->toRoute('application/default',
32 array('controller'=>'image', 'action'=>'index'));
33 }
34 }
35
36 // Render the page.
37 return new ViewModel(array(
38 'form' => $form
39 ));
40 }
41 }
$_POST and $_FILES super-global PHP arrays and merge them into the single array (lines 15-19).
This is required to correctly handle uploaded files, if any. Then we pass this array to the form
model with the setData() method (line 22).
In line 25, we call the form model’s isValid() method. This method runs the input filter attached
to the form model. Since we have only one file input in the input filter, this will only run our
three file validators: UploadFile, IsImage and ImageSize.
If the data is valid, we call the getData() method (line 28). For our file field, this will run the
RenameUpload filter, which moves our uploaded file to its persistent directory.
After that, in line 31, we redirect the user to the “index” action of the controller (we will populate
that action method a little bit later.
Now, its time to add the view template for the “upload” action. Add the upload.html view
template file under the application/image directory under the module’s view directory:
1 <?php
2 $form = $this->form;
3 $form->get('submit')->setAttributes(array('class'=>'btn btn-primary'));
4 $form->prepare();
5 ?>
6
7 <h1>Upload a New Image</h1>
8
9 <p>
10 Please fill out the following form and press the <i>Upload</i> button.
11 </p>
12
13 <div class="row">
14 <div class="col-md-6">
15 <?php echo $this->form()->openTag($form); ?>
16
17 <div class="form-group">
18 <?php echo $this->formLabel($form->get('file')); ?>
19 <?php echo $this->formElement($form->get('file')); ?>
20 <?php echo $this->formElementErrors($form->get('file')); ?>
21 </div>
22
23 <?php echo $this->formElement($form->get('submit')); ?>
24
25 <?php echo $this->form()->closeTag(); ?>
26 </div>
27 </div>
In the code of the view template, we first set “class” attribute on the form (line 3). This is to apply
nice-looking Twitter Bootstrap styles to the form’s Submit button.
Uploading Files with Forms 328
Then, we render the form with the common view helpers that we discussed in Chapter 7. For
rendering the “file” field, we use the generic FormElement view helper.
Typically, you use the FormElement generic view helper for rendering the file field.
The FormElement internally calls the FormFile view helper, which performs the actual
rendering.
The second action method we will complete is the indexAction(). This action will handle the
Image Gallery page containing the list of uploaded files and their small thumbnails. For each
image, there will be a button “Show In Natural Size” for opening the image in another tab of the
web browser.
Change the ImageController.php file as follows:
1 <?php
2 //...
3 class ImageController extends AbstractActionController
4 {
5 //...
6 public function indexAction()
7 {
8 // Get the singleton of the image manager service.
9 $imageManager = $this->getServiceLocator()->get('ImageManager');
10
11 // Get the list of already saved files.
12 $files = $imageManager->getSavedFiles();
13
14 // Render the view template.
15 return new ViewModel(array(
16 'files'=>$files
17 ));
18 }
19 }
In the code above, we first get the singleton instance of the ImageManager service (line 9). We
do that with the getServiceLocator() method provided by the controller’s base class. The
getServiceLocator() method returns the ServiceLocatorInterface interface, which provides
an ability to retrieve any service registered in the service manager.
Next, in line 12, we use the getSavedFiles() method of the ImageManager class for retrieving
the list of uploaded images and pass them to the view for rendering (line 15).
Please note how “slim” and clear this controller action is! We achieved this by moving
the image management functionality to the ImageManager service model.
Uploading Files with Forms 329
Add the index.phtml view template to image/index directory under the module’s view directory.
The contents of the file is shown below:
1 <h1>Image Gallery</h1>
2
3 <p>
4 This page displays the list of uploaded images.
5 </p>
6
7 <p>
8 <a href="<?php echo $this->url('application/default',
9 array('controller'=>'image', 'action'=>'upload')); ?>"
10 class="btn btn-primary" role="button">Upload More</a>
11 </p>
12
13 <hr/>
14
15 <?php if(count($files)==0): ?>
16
17 <p>
18 <i>There are no files to display.</i>
19 </p>
20
21 <?php else: ?>
22
23 <div class="row">
24 <div class="col-sm-6 col-md-12">
25
26 <?php foreach($files as $file): ?>
27
28 <div class="img-thumbnail">
29
30 <img src="<?php echo $this->url('application/default',
31 array('controller'=>'image', 'action'=>'file'),
32 array('query'=>array('name'=>$file, 'thumbnail'=>true))); ?>">
33
34 <div class="caption">
35 <h3><?php echo $file; ?></h3>
36 <p>
37 <a target="_blank" href="<?php echo $this->url('application/default',\
38
39 array('controller'=>'image', 'action'=>'file'),
40 array('query'=>array('name'=>$file))); ?>"
41 class="btn btn-default" role="button">Show in Natural Size</a>
42 </p>
43 </div>
Uploading Files with Forms 330
44 </div>
45
46 <?php endforeach; ?>
47 </div>
48 </div>
49
50 <?php endif; ?>
51
52 <hr/>
In the code above, we create the HTML markup for the Upload More button (line 5).
Under the button, we use check whether the $files array is empty (line 10). If the array is empty,
we output the “There are no files to display” message; otherwise we walk through the files and
output the thumbnails of each uploaded images.
For rendering a thumbnail, we use the <img> tag. We set its src attribute with the URL pointing
to the “file” action of our ImageController controller. We pass two parameters to the action via
the query part of the URL: the image name and thumbnail flag.
For styling the thumbnails, we use the Twitter Bootstrap provided “.img-thumbnail” CSS class.
For additional information about these Twitter Bootstrap styles, please refer to the
Bootstrap official documentation.
Below each thumbnail, we put the “Show in Natural Size” link, which points to the “file” action
of our ImageController controller. When site visitor clicks the link, he will be shown with the
image in natural size, and the image will be opened in another browser’s tab (note the target="_-
blank" attribute of the link).
The last action we will populate is the ImageController::fileAction() method. That method
will allow to preview an uploaded image or generate a small thumbnail of the image. The action
method will take two GET parameters:
1 <?php
2 //...
3 class ImageController extends AbstractActionController
4 {
5 //...
6 public function fileAction()
7 {
8 // Get the file name from GET variable.
9 $fileName = $this->params()->fromQuery('name', '');
10
11 // Check whether the user needs a thumbnail or a full-size image.
12 $isThumbnail = (bool)$this->params()->fromQuery('thumbnail', false);
13
14 // Get the singleton of the image manager service.
15 $imageManager = $this->getServiceLocator()->get('ImageManager');
16
17 // Get path to image file.
18 $fileName = $imageManager->getImagePathByName($fileName);
19
20 if($isThumbnail) {
21
22 // Resize the image.
23 $fileName = $imageManager->resizeImage($fileName);
24 }
25
26 // Get image file info (size and MIME type).
27 $fileInfo = $imageManager->getImageFileInfo($fileName);
28 if ($fileInfo===false) {
29 // Set 404 Not Found status code
30 $this->getResponse()->setStatusCode(404);
31 return;
32 }
33
34 // Write HTTP headers.
35 $response = $this->getResponse();
36 $headers = $response->getHeaders();
37 $headers->addHeaderLine("Content-type: " . $fileInfo['type']);
38 $headers->addHeaderLine("Content-length: " . $fileInfo['size']);
39
40 // Write file content.
41 $fileContent = $imageManager->getImageFileContent($fileName);
42 if($fileContent!==false) {
43 $response->setContent($fileContent);
44 } else {
45 // Set 500 Server Error status code.
46 $this->getResponse()->setStatusCode(500);
Uploading Files with Forms 332
47 return;
48 }
49
50 if($isThumbnail) {
51 // Remove temporary thumbnail image file.
52 unlink($fileName);
53 }
54
55 // Return Response to avoid default view rendering.
56 return $this->getResponse();
57 }
58 }
In the code above, we first get the “name” and “thumbnail” parameters from $_GET super-global
array (lines 9, 12). If the parameters are missing, their default values are used instead.
Then, in line 15, we retrieve a singleton instance of our ImageManager service that we previously
registered in the service manager.
In line 18, we use the getImagePathByName() method provided by the ImageManager service to
get the absolute path to the image by its name.
If a thumbnail is requested, we resize the image with the resizeImage() method of the
ImageManager (line 23). That method returns path to a temporary file containing the thumbnail
image.
Then, we get the information about the image file (its MIME type and file size) with the
getImageFileInfo() method of the ImageManager (line 27).
Finally, we create a Response object, fill its headers with image information, set its content with
data of the image file (lines 35-48), and return the Response object from the controller action
(line 56).
Note that returning the Response object disables the default rendering of the view
template for this action method. By this reason, we do not create the file.phtml view
template file.
1 <?php
2 return array(
3 //...
4 'controllers' => array(
5 'invokables' => array(
6 'Application\Controller\Image' =>
7 'Application\Controller\ImageController',
8 //...
9 ),
10 ),
11 //...
12 );
10.8.5 Results
Finally, adjust directory permissions to make the APP_DIR/data directory writeable by the
Apache Web Server. In Linux Ubuntu, this is typically accomplished by the following shell
commands (replace the APP_DIR placeholder with the actual directory name of your web
application):
chown -R www-data:www-data APP_DIR/data
Above, the chown and chmod commands set the Apache user to be the owner of the directory and
allow the web server to write to the directory, respectively.
If you now enter the URL http://localhost/application/image/index into your web browser’s
navigation bar, you will see the image gallery page like shown in figure 10.4.
Clicking the Upload More button will open the Upload a New Image page where you can peek
an image file for upload. If you pick an unacceptable file (not an image, or too big image), you
will see validation errors (see the figure 10.5 below).
If the upload is completed successfully, you will be redirected back to the Image Gallery page
and see the uploaded image in the list of thumbnails. Clicking the View Full Size button will open
the image in a new browser tab (see the figure 10.6 below for example).
Uploading Files with Forms 335
You may find the Image Gallery complete example in the Form Demo sample web
application bundled with this book.
10.9 Summary
File uploads is a standard HTML form feature. Uploading files is accomplished by setting form
content encoding to binary encoding type. Zend Framework 2 provides convenient functionality
for doing file uploads and validating the uploaded files.
11. Advanced Usage of Forms
In previous chapters, you’ve learned about form usage basics: what HTML forms are and how
you define form models and form presentation in Zend Framework 2. In this chapter, you will
learn some advanced form usage topics such as security form elements (CAPTCHA and CSRF),
and so on.
ZF2 components covered in this chapter:
Component Description
Zend\Captcha Implements various CAPTCHA algorithms.
Zend\Form Contains base form model classes.
Zend\Filter Contains various filters classes.
Zend\Validator Implements various validator classes.
Zend\InputFilter Implements a container for filters/validators.
11.1.1 CAPTCHA
A CAPTCHA (stands for “Completely Automated Public Turing test to tell Computers and
Humans Apart”) is a challenge-response test used in web sites for determining whether the user
is a human or a robot.
There are several types of CAPTCHA. The most widely used one requires that the user type the
letters of a distorted image that is shown on the web page (see figure 11.1 for some examples).
The goal of the CAPTCHA test is to protect your form from filling and submission by an
automated process (so called robot). Usually, such robots send spam messages to forums, hack
passwords on site login forms, or perform some other malicious actions.
The CAPTCHA test allows to reliably distinguish humans from robots, because humans
are easily able to recognise and reproduce characters from the distorted image, while
robots are not (at the current stage of evolution of computer vision algorithms).
In Zend Framework 2, there are several CAPTCHA types available (they all belong to the
Zend\Captcha component):
• Dumb. This is a very simple CAPTCHA algorithm which requires that site user enter the
word letters in reverse order. We will not consider this type in details here, because it
provides too low protection level.
• Image. A CAPTCHA algorithm distorting an image with addition of some noise in form
of dots and line curves (figure 11.1, a).
• ReCaptcha. An adapter providing the access to reCAPTCHA service (figure 11.1, c).
The reCAPTCHA¹ is a free service that is provided by Google for generating distorted
images and using them for CAPTCHA test.
• Figlet. An unusual CAPTCHA type using FIGlet program instead of an image distortion
algorithm. The FIGlet is an open-source program which generates the CAPTCHA image
of many small ASCII letters (figure 11.1, b).
The Zend\Captcha component provides a unified interface for all CAPTCHA types (the AdapterInterface
interface). The AbstractAdapter base class implements that interface, and all other CAPTCHA
algorithms are derived from the abstract adapter class ². The class inheritance diagram is shown
in figure 11.2 below.
As you can see from the figure 11.2, there is another base class for all CAPTCHA types that utilize
some secret word of characters: the AbastractWord class. This base class provides methods for
generating random sequence of characters and for adjusting word generation options.
¹http://recaptcha.net
²The adapter is a design pattern that translates one interface for a class into a compatible interface, which helps two (or several) incompatible
interfaces to work together. Typically, CAPTCHA algorithms have different public methods, but since they all implement AbstractAdapter
interface, the caller may use any CAPTCHA algorithm in the same common manner (by calling the methods provided by the base interface).
Advanced Usage of Forms 338
ZF2 provides the dedicated form element class and view helper class for letting you use
CAPTCHA fields on your forms.
To add a CAPTCHA field to a form model, you use the Captcha class that belongs to Zend\Form
component and lives in Zend\Form\Element namespace.
The Captcha element class can be used with any CAPTCHA algorithm (listed in the previous
section) from Zend\Captcha component. For this purpose, the element class has the setCaptcha()
method which takes either an instance of a class implementing Zend\Captcha\AdapterInterface
interface, or an array containing CAPTCHA configuration ³. By the setCaptcha() method, you
can attach the desired CAPTCHA type to the element.
You add the Captcha element to a form model as usual, with the add() method provided by the
Zend\Form\Form base class. As usual, you can pass it either an instance of the Zend\Form\Element\Captcha
class or provide an array of configuration options specific to certain CAPTCHA algorithm (in
that case, the element and its associated CAPTCHA algorithm will automatically be instantiated
and configured by the factory class).
The code example below shows how to use the latter method (passing a configuration array).
We prefer this method because it requires less code to write. It is assumed that you call this code
inside of form model’s addElements() protected method:
³In the latter case (configuration array), the CAPTCHA algorithm will be automatically instantiated and initialized by the factory class
Zend\Captcha\Factory.
Advanced Usage of Forms 339
1 <?php
2 // Add the CAPTCHA field to the form model
3 $this->add(array(
4 'type' => 'captcha',
5 'name' => 'captcha',
6 'options' => array(
7 'label' => 'Human check',
8 'captcha' => array(
9 'class' => '<captcha_class_name>', //
10 // Certain-class-specific options follow here ...
11 ),
12 ),
13 ));
In the example above, we call the add() method provided by the Form base class and pass it an
array describing the element to insert (line 3):
• The type key of the array (line 4), as usual, may either be a full name of the element
(Zend\Form\Element\Captcha) or its short alias (“captcha”).
• The name key (line 5) is the value for the “name” attribute of the HTML form field.
• The options key contains the options for the attached CAPTCHA algorithm. The class
key (line 9) may either contain the full CAPTCHA class name (e.g. Zend\Captcha\Image)
or its short alias (e.g. “Image”). Other, adapter-specific, options may be added to the key
as well. We will show how to do that a little bit later.
For generating the HTML markup for the element, you may use the FormCaptcha view helper
class (belonging to Zend\Form\View\Helper namespace). But, as you might learn from the
previous chapter, typically you use the generic FormElement view helper instead, like shown
in the code below:
It is assumed that you call the view helper inside of your view template.
Next, we provide three examples illustrating how to use different CAPTCHA types provided
by ZF2: the Image, Figlet and ReCaptcha. We will show how to add a CAPTCHA field to the
feedback form that we used in examples of the previous chapter.
Image CAPTCHA requires that you have PHP GD extension installed with PNG
support and FT fonts. For information on how to install the extension, please refer
to Appendix A.
To add the Image CAPTCHA to your form model, call the form’s add() method as follows:
Advanced Usage of Forms 340
1 <?php
2 namespace Application\Form;
3 // ...
4
5 class ContactForm extends Form
6 {
7 // ...
8 protected function addElements() {
9 // ...
10
11 // Add the CAPTCHA field
12 $this->add(array(
13 'type' => 'captcha',
14 'name' => 'captcha',
15 'attributes' => array(
16 ),
17 'options' => array(
18 'label' => 'Human check',
19 'captcha' => array(
20 'class' => 'Image',
21 'imgDir' => 'public/img/captcha',
22 'suffix' => '.png',
23 'imgUrl' => '/img/captcha/',
24 'imgAlt' => 'CAPTCHA Image',
25 'font' => './data/font/thorne_shaded.ttf',
26 'fsize' => 24,
27 'width' => 350,
28 'height' => 100,
29 'expiration' => 600,
30 'dotNoiseLevel' => 40,
31 'lineNoiseLevel' => 3
32 ),
33 ),
34 ));
35 }
36 }
Above, the captcha key of the configuration array (see line 20) contains the following parameters
for configuring the Image CAPTCHA algorithm attached to the form element:
• the class parameter (line 21) should be either the full CAPTCHA adapter class name
(\Zend\Captcha\Image) or its short alias (Image).
• the imgDir parameter (line 22) should be the path to the directory where to save the
generated distorted images (in this example, we will save the images to the APP_DIR/pub-
lic/img/captcha directory).
Advanced Usage of Forms 341
• the suffix parameter (line 23) defines the extension for a generated image file (“.png” in
this example).
• the imgUrl parameter (line 24) defines the base part of the URL for opening generated
CAPTCHA images in a web browser. In this example, site visitors will be able to access
CAPTCHA images using URLs like “http://localhost/img/captcha/<ID>”, where ID is a
unique ID of certain image.
• the imgAlt parameter (line 25) is an (optional) alternative text to show if CAPTCHA image
can’t be loaded by the web browser (the “alt” attribute of <img> tag).
• the font parameter (line 26) is the path to the font file. You can download a free TTF
font, for example, from here⁴. In this example, we use Thorne Shaded font, which we
downloaded and put into the APP_DIR/data/font/thorne_shaded.ttf file.
• the fsize parameter (line 27) is a positive integer number defining the font size.
• the width (line 28) and height parameters (line 29) define the with and height (in pixels)
of the generated image, respectively.
• the expiration parameter (line 30) defines the expiration period (in seconds) of the
CAPTCHA images. Once an image expires, it is removed from disk.
• the dotNoiseLevel parameter (line 31) and lineNoiseLevel parameter (line 32) define the
image generation options (dot noise level and line noise level, respectively).
To render the CAPTCHA field, add the following lines to your contact-us.phtml view template
file:
<div class="form-group">
<?php echo $this->formLabel($form->get('captcha')); ?>
<?php echo $this->formElement($form->get('captcha')); ?>
<?php echo $this->formElementErrors($form->get('captcha')); ?>
<p class="help-block">Enter the letters above as you see them.</p>
</div>
Finally, create the APP_DIR/public/img/captcha directory that will store generated CAPTCHA
images. Adjust directory permissions to make the directory writeable by the Apache Web Server.
In Linux Ubuntu, this is typically accomplished by the following shell commands (replace the
APP_DIR placeholder with the actual directory name of your web application):
mkdir APP_DIR/public/img/captcha
chown -R www-data:www-data APP_DIR
chmod -R 775 APP_DIR
Above, the mkdir command creates the directory, and chown and chmod commands set the
Apache user to be the owner of the directory and allow the web server to write to the directory,
respectively.
Now, if you open the “http://localhost/contactus” page in your web browser, the CAPTCHA
image will be generated based on a random sequence of letters and digits saved in session. You
should see something like in the figure 11.3 below.
⁴http://www.1001freefonts.com/
Advanced Usage of Forms 342
When you fill the form fields in and press the Submit button, the letters entered into the Human
check field will be transferred to server as part of HTTP request. Then, on form validation, the
Zend\Form\Element\Captcha class will compare the submitted letters to those stored in PHP
session. If the letters are identical, the form is considered valid; otherwise form validation fails.
Once the PHP renderer processes the view template, it generates HTML markup for the
CAPTCHA element as shown below:
<div class="form-group">
<label for="captcha">Human check</label>
<img width="350" height="100" alt="CAPTCHA Image"
src="/img/captcha/df344b37500dcbb0c4d32f7351a65574.png">
<input name="captcha[id]" type="hidden"
value="df344b37500dcbb0c4d32f7351a65574">
<input name="captcha[input]" type="text">
<p class="help-block">Enter the letters above as you see them.</p>
</div>
1 <?php
2 // Add the CAPTCHA field
3 $this->add(array(
4 'type' => 'captcha',
5 'name' => 'captcha',
6 'attributes' => array(
7 ),
8 'options' => array(
9 'label' => 'Human check',
10 'captcha' => array(
11 'class' => 'Figlet',
12 'wordLen' => 6,
13 'expiration' => 600,
14 ),
15 ),
16 ));
Above, the captcha key of the configuration array (see line 9) contains the following parameters
for configuring the Figlet CAPTCHA algorithm attached to the form element:
• the class parameter (line 10) should be either the full CAPTCHA adapter class name
(\Zend\Captcha\Figlet) or its short alias (Figlet).
• the wordLen parameter (line 11) defines the length of the secret word to be generated.
• the expiration parameter (line 12) defines the CAPTCHA expiration period (in seconds).
Now, open the “http://localhost/contactus” page in your web browser. Once that is done, you
should see a page like in the figure 11.4 below.
Once the PHP renderer processes the view template, it generates HTML markup for the
CAPTCHA element like shown below:
<div class="form-group">
<label for="captcha">Human check</label>
<pre>
__ _ __ __ _ _ ___ _ _ __ __
| || | || \ \\/ // | \ / || / _ \\ | || | || \ \\/ //
| '--' || \ ` // | \/ || | / \ || | || | || \ ` //
| .--. || | || | . . || | \_/ || | \\_/ || | ||
|_|| |_|| |_|| |_|\/|_|| \___// \____// |_||
`-` `-` `-`' `-` `-` `---` `---` `-`'
</pre>
<input name="captcha[id]" type="hidden"
value="b68b010eccc22e78969764461be62714">
<input name="captcha[input]" type="text">
<p class="help-block">Enter the letters above as you see them.</p>
</div>
Advanced Usage of Forms 344
cd APP_DIR
php composer.phar require zendframework/zendservice-recaptcha *
⁵The ZendService\Recaptcha is not part of ZF2 “core” distribution, it is so called “service component” and, because of that, it requires
additional installation.
Advanced Usage of Forms 345
The commands above make your site’s directory to be the current working directory and run
Composer dependency manager to install the component.
Next, you have to register on recaptcha.net⁶ web-site (figure 10.5) and get the public and private
keys from there.
To add the reCAPTCHA element to your form model, replace the form element definition from
the previous example with the following code:
⁶http://www.google.com/recaptcha
Advanced Usage of Forms 346
15 ),
16 ),
17 ));
Above, the captcha key of the configuration array (see line 9) contains the following parameters
for configuring the ReCaptcha CAPTCHA algorithm attached to the form element:
• the class parameter (line 10) should be either the full CAPTCHA adapter class name
(\Zend\Captcha\ReCaptcha) or its short alias (ReCaptcha).
• the privKey parameter (line 12) should be the public key you’ve got from recaptcha.net.
• the pubKey parameter (line 14) should be the private key you’ve got from recaptcha.net.
Please note, that in code above, you need to set the privKey parameter with the value
of the public key acquired from recaptcha.net, and the pubKey parameter with the value
of the private key. Exactly in this order; otherwise it will not work.
Now, if you open the “http://localhost/contactus” page in your web browser, you should see
something like in the figure 11.6 below.
able to send unauthorized commands from a user that the web-site trusts. This attack is typically
performed on pages containing forms for submission of some sensitive data (e.g. money transfer
forms, shopping carts etc.)
To better understand how this attack works, take a look at figure 11.7.
1. You log into your account at payment gateway web site https://payment.com. Please note
that the SSL-protected connection (HTTPS) is used here, but it doesn’t protect from such
kind of attacks.
2. Typically, you set check on the “Remember Me” check box of the login form to avoid
entering user name and password too often. Once you logged in to your account, your
web browser saves your session information to a cookie variable on your machine.
3. On the payment gateway site, you use the payment form https://payment.com/moneytransfer.php
to buy some goods. Please note that this payment form will later be used as a vulnerability
allowing to perform the CSRF attack.
4. Next you use the same web browser to visit some web site you like. Assume the web site
contains cool pictures http://coolpictures.com. Unfortunately, this web site is infected by a
malicious script, masqueraded by an <img src="image.php"> HTML tag. Once you open
the HTML page in your web browser, it loads all its images, thus executing the malicious
image.php script.
5. The malicious script checks the cookie variable, and if it presents, it performs the “session
riding” and can act on behalf of the logged in user. It is now able to submit the payment
form to the payment gateway site.
Advanced Usage of Forms 348
The above described CSRF attack is possible it the web form on the payment gateway
site does not check the source of the HTTP request. The people who maintain the
payment gateway site must put more attention in making its forms more secure.
To prevent CSRF attacks to a form, one has to require a special token with the form, as follows:
1. For certain form, generate a random sequence of bytes (token) and save it server-side in
PHP session data.
2. Add a hidden field to form and set its value with the token.
3. Once the form is submitted by the user, compare the hidden value passed in the form with
the token saved server-side. If they match, consider the form data secure.
If a malicious user will try to attack the site by submitting the form, he will not be able
to put right token in the form submissions, because the token is not stored in cookies.
In Zend Framework 2, to add a CSRF protection to your form model, you use the Zend\Form\Element\Csrf
form element class.
The Csrf element has no visual representation (you will not see it on the screen).
To insert a CSRF element to your form model, add the following lines in its addElements()
method:
Above, we use the Form’s add() method (line 2), to which we pass a configuration array
describing the CSRF element. The element will be automatically instantiated and initialized by
the factory.
In line 3, we specify the class name for the CSRF element. This either may be the full class name
(Zend\Form\Element\Csrf) or a short alias (“csrf”).
Advanced Usage of Forms 349
In line 4, we set the “name” attribute for the element. In this example, we use “csrf” name, but
you may use any other name, on your choice.
In line 6, inside of csrf_options array, we specify the options specific to Zend\Form\Element\Csrf
class. We set the timeout option to 600 (look at line 7), which means the CSRF check expires in
600 seconds (10 minutes) after form creation.
To render the CSRF field, in your view template .phtml file , add the following line:
When the PHP renderer evaluates the view template, it generates the HTML markup for the
CSRF field like shown below:
As you can see from the HTML markup code above, the form now contains a hidden
field with a randomly generated token. Since the attacker script doesn’t know this
token, it won’t be able to submit its correct value, thus the CSRF attack becomes
prevented.
11.2 Summary
In this chapter, we have discussed some advanced form usage capabilities.
Zend Framework 2 provides two classes whose purpose is enhancing form security: Captcha and
Csrf. A CAPTCHA is a type of challenge-response test used to determine whether or not the user
is a human. CAPTCHA elements are used on form to prevent form submission by a malicious
automated process (a robot). The latter element, Csrf, is used for Cross-Site Request Forgery
(abbreviated as CSRF) hacker attack prevention.
12. Database Management with
Doctrine ORM
Doctrine is an open-source PHP library providing convenient methods for managing your
database in object-oriented way. For working with relational databases, Doctrine provides a
component named Object Relational Mapper (shortly, ORM). With Doctrine ORM you map your
database table to a PHP class (in terms of Domain Driven Design, it is also called an entity class)
and a row from that table is mapped to an instance of the entity class. If you are new to Doctrine,
it is recommended that you also refer to Appendix D for introductory information about the
Doctrine library architecture.
• Stores blog posts in a database and provides user interface for accessing and managing
those posts.
• It is assumed that the blog has the single author of its posts, while comments can be added
by multiple blog readers.
• The web site has two pages: Home page and Admin page. The first one displays the list of
recently added posts, while the latter one allows to add, edit, view and delete posts.
For example screen shots of the Blog web site, please look at the figures 12.1 and 12.2 below:
Database Management with Doctrine ORM 351
To download the Blog application, visit this page¹ and click the Download ZIP button to download
the code as a ZIP archive. When download is complete, unpack the archive to some directory.
Then navigate to the blog directory containing the source code of the Blog web application:
/using-zend-framework-2-book
/blog
...
The Blog is a sample web site which can be installed on your machine. To install the example,
you can either edit your default Apache virtual host file or create a new one. After editing the
file, restart the Apache HTTP Server and open the web site in your web browser.
For the Blog example to work, you have to create a MySQL database. Instructions on
how to do that are provided in the next section.
¹https://github.com/olegkrivtsov/using-zend-framework-2-book
Database Management with Doctrine ORM 352
For OS-specific instructions on how to install MySQL server and client, please refer to
Appendix A.
Once you install MySQL, type the following command from your command shell to log into
MySQL client console:
mysql -u root -p
When asked for, type the password of the root user (the password of the root user is the one
you’ve specified during MySQL server installation). On successful login, you should see the
following welcome message:
Database Management with Doctrine ORM 353
Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
Now you are able to type MySQL client commands (like show databases, show tables, etc.) or
SQL queries (like SELECT or INSERT) in the MySQL prompt and see their output.
If you want to quit of the MySQL prompt, type quit and press Enter.
MySQL commands are case insensitive, so you could type create schema blog;
with the same result. We recommend using upper case for SQL queries, since this is a
common convention.
Next, we create the user named blog and grant it all privileges for accessing and modifying the
blog database and all its tables:
In the command above, replace the password placeholder with the new password for the blog
user. This password should be different than the password of the root user.
Database Management with Doctrine ORM 354
Here, we create the second user blog, because it is not recommended to give the web
application to log into database under the root user. The root user has unlimited rights
and it would be just insecure to give the application an ability to do any actions it
wants. The blog user will have permissions to modify the blog database only, which
is sufficient in our case.
You can check that the database has been created by typing the following command and pressing
Enter:
show databases;
You should be able to see the output like below (note the blog line in the list of databases):
+--------------------+
| Database |
+--------------------+
| information_schema |
| blog |
| mysql |
| performance_schema |
+--------------------+
MySQL client allows to enter multi-line commands easily. Just press Enter when you
want to move the caret to the next line. The command is considered to be fully entered
when the semicolon (;) character is encountered.
Database Management with Doctrine ORM 355
Let’s fill the tables we have created with some sample data:
If necessary, you can easily remove the schema and all tables and data it contains by
typing the following command from MySQL prompt:
DROP SCHEMA blog;
Figure 12.3 graphically illustrates what entities we have in the schema and what relations
between those entities present.
As you can see from figure 12.3, the post table is related to comment table as one-to-many, because
a single post may have many comments. This is also called the “one-to-many” relation.
The post table is also related to the tag table as many-to-many. A single post may have many
tags, and a single tag may belong to many posts as well. Many-to-many relation is typically
implemented through an auxiliary table (post_tag table in our case).
APP_DIR/data/ directory and type the following command from your command shell (but not
from MySQL prompt):
mysql -uroot -p blog < schema.mysql.sql
When prompted for password, enter the password of the root user and type Enter.
Once this is done, log into MySQL client and type the following commands:
use blog;
show tables;
You should see the list of tables created, something like below:
+----------------+
| Tables_in_blog |
+----------------+
| comment |
| post |
| post_tag |
| tag |
+----------------+
4 rows in set (0.00 sec)
The cd command above is used to make the APP_DIR directory current working directory.
And the require command tells Composer to add the package doctrine/doctrine-orm-module
as a dependency to your web application, and to download and install that dependency. The
asterisk (*) parameter means that any version of the package is acceptable.
Specifying the asterisk as a version, will result in installing the latest available version
of Doctrine, which typically is the desired behavior.
Once you run the commands above, Composer will first modify the composer.json file and create
the following line under its require key:
{
...
"require": {
"doctrine/doctrine-orm-module": "*",
...
},
...
}
Then Composer will try to locate the dependency packages, download them to the local machine
and install the files into the APP_DIR/vendor directory.
Composer will output lines indicating installation process to the terminal:
As you can see from the output above, when you install DoctrineORMModule component,
Composer automatically installs the DoctrineModule and all necessary Doctrine components
(Doctrine\DBAL, Doctrine\ORM, etc.)
Database Management with Doctrine ORM 360
When the installation has been finished, you can find the Doctrine files in your APP_DIR/vendor
directory (see the figure 12.4 below).
You use the php composer.phar require command for the first time you install
Doctrine. Once the composer.json (and composer.lock) files have been modified by
Composer, you are able to install (or update) all dependencies as usual by typing the
php composer.phar install or php composer.phar update commands, respectively,
from your command shell.
1 <?php
2 return array(
3 'modules' => array(
4 'Application',
5 // Add the Doctrine integration modules.
6 'DoctrineModule',
7 'DoctrineORMModule',
8 ),
9 //...
10 );
Database Management with Doctrine ORM 361
The lines above let ZF2 know that it should load the DoctrineModule module and DoctrineORM-
Module module on application’s start up (we talked about ZF2 application life cycle in Chapter
3).
• the connection key contains the list of all databases that the web application is able to
connect to. For each database connection it contains parameters like driver class name,
host, user name, password and database name.
By default, there is only one connection named orm_default, and you may add more
database connections if required.
• the configuration key contains ORM settings like caching configuration and locations of
auto-generated entity proxy classes for each available connection.
• the driver key contains the information about where to locate entity classes for each
available database connection.
⁵The tree in figure 12.5 may be different than you have in your own application, because some keys were omitted here for simplicity.
Database Management with Doctrine ORM 362
• the entitymanager key contains settings used for instantiating an entity manager for each
database connection.
• the eventmanager key contains settings for Doctrine event manager for each available
connection.
Database Management with Doctrine ORM 363
Doctrine uses its own implementation of event manager. If you want, you can create
an event listener class and hooks some events. However, this advanced topic and we
do not cover it in this book.
• For storing application-wide Doctrine settings, you typically use the APP_DIR/config/au-
toload/global.php or APP_DIR/config/autoload/local.php config files. The first one suits
well for storing settings not depending on particular environment, while the latter one suits
well for storing environment-dependent settings (like database connection parameters).
• For storing Doctrine settings specific to certain module, you use the module.config.php
config file located inside the config directory of that module. This is suitable, for example,
for storing the entity location settings.
When ZF2-based web site loads its configuration, it merges all configs into a single big array,
thus forming the final Doctrine config “tree”.
This connection is shared between all modules of the web application. If you want to
create module-specific connection, consider adding the key to the module.config.php
file instead.
Database Management with Doctrine ORM 364
1 <?php
2 return array(
3 'doctrine' => array(
4 'connection' => array(
5 'orm_default' => array(
6 'driverClass' => 'Doctrine\DBAL\Driver\PDOMySql\Driver',
7 'params' => array(
8 'host' => '127.0.0.1',
9 'user' => 'blog',
10 'password' => '<password>',
11 'dbname' => 'blog',
12 )
13 ),
14 ),
15 ),
16 );
Above, we have the doctrine key and connection subkey. The connection subkey contains the
orm_default subkey which is the default connection.
• The driverClass key provides the class name to use as a driver to the database. Since
we use MySQL database, we specify the Doctrine\DBAL\Driver\PDOMySql\Driver class
name.
For your reference, in table 12.3, you can find several other often used database drivers.
Each driver class supports its own set of parameters, so please refer to certain driver’s
code (and related documentation) for additional information.
Method Description
Doctrine\DBAL\Driver\PDOSqlite\Driver SQLite driver using PDO PHP extension.
Doctrine\DBAL\Driver\PDOMySql\Driver MySQL driver using PDO PHP extension.
Doctrine\DBAL\Driver\PDOOracle\Driver Oracle driver using PDO PHP extension.
Doctrine\DBAL\Driver\PDOPgSql\Driver PostgreSQL driver using PDO PHP extension.
Doctrine\DBAL\Driver\PDOSqlsrv\Driver MS SQL Server driver using PDO PHP extension.
Database Management with Doctrine ORM 365
• User entity is designed to store information about a web-site visitor. It may contain
properties like username, password, first name, last name, etc.
• License entity is designed to store information about a software license. It may contain
data like unique license key, reference to user who purchased the license, license creation
date, etc.
• Payment entity may contain properties related to a purchase of some goods. The properties
are: transaction ID, money amount, money currency, etc.
In Doctrine ORM, an entity class is mapped on a certain database table. For example, the User
entity is usually mapped on the user table (if needed, the table name may be arbitrary).
For our Blog example application, we will create three entity classes:
• Post entity will contain data related to specific blog post. Its properties are exactly the
same that we used when defining the post table in blog database schema. The entity class
will also have public getter and setter methods designed for retrieving/setting the data.
• by analogy, Comment entity will contain data related to a comment to blog post.
• and Tag entity will contain data related to a tag.
Database Management with Doctrine ORM 366
12.5.1 Annotations
An annotation is a special kind of a PHP comment that is preprocessed by Doctrine ORM. In other
words, annotations is metadata attached to an entity class that can be read by the Doctrine ORM
at run-time. Annotations provide verbose information about an entity. Annotations describe an
entity and tell Doctrine ORM how to map it on a database table.
A Docblock annotation is a C++ style comment starting with slash (/) and two asterisks (*).
This “starter” characters are required, otherwise Doctrine won’t recognize the annotation. An
example of annotation can be found below:
1 /**
2 * This is Docblock annotation comment.
3 */
Doctrine reads Docblock annotations with the help of its Doctrine\Annotations component.
You might have already faced with Docblock annotations if you use phpDocumentor⁶
or Doxygen⁷ documentation generation tools. In those tools, annotation comments are
serving the same goal: to describe a PHP class and its properties and methods. Then the
tool goes through your code and builds an HTML documentation automatically based
entirely on code and annotations analysis.
For example, below, we provide the basic example of a Doctrine entity class. You can see that
the class and its properties are marked with Docblock annotations with special tags (a tag starts
with ‘@’ character).
1 <?php
2 namespace Application\Entity;
3
4 use Doctrine\ORM\Mapping as ORM;
5
6 /**
7 * @ORM\Entity
8 * @ORM\Table(name="post")
9 */
10 class Post
11 {
12 /**
13 * @ORM\Id
14 * @ORM\GeneratedValue
15 * @ORM\Column(name="id")
16 */
⁶http://www.phpdoc.org/
⁷http://www.stack.nl/~dimitri/doxygen/
Database Management with Doctrine ORM 367
17 protected $id;
18
19 /**
20 * @ORM\Column(name="title")
21 */
22 protected $title;
23
24 /**
25 * @ORM\Column(name="content")
26 */
27 protected $content;
28
29 /**
30 * @ORM\Column(name="status")
31 */
32 protected $status;
33
34 /**
35 * @ORM\Column(name="date_created")
36 */
37 protected $dateCreated;
38 }
• @ORM\Entity tag (line 7) declares that this class is a Doctrine ORM entity;
• @ORM\Table(name="post") tag (line 8) tells Doctrine ORM that this entity class is mapped
on the post database table;
• @ORM\Id tells that this properties is actually a unique identifier of the entity (see line 13);
⁸Doctrine-provided annotation tags are implemented as classes living inside of Doctrine/ORM/Mapping namespace. This is to avoid
annotation naming collisions (assume the case when some other component has an annotation named Entity or Table, the collision would
happen).
Database Management with Doctrine ORM 368
• @ORM\GeneratedValue is used to tell Doctrine ORM that this property uses some auto-
generated sequence for initializing itself (line 15). In MySQL, this typically means that the
corresponding table column uses AUTO_INCREMENT initializer.
• @ORM\Column(name="<column_name>") is used to tell Doctrine ORM on which table
column to map this particular property (lines 15, 20, 25, 30, 37).
The complete list of Doctrine-provided tags used in annotations can be found by the
following link⁹.
1 <?php
2 namespace Application\Entity;
3
4 use Doctrine\ORM\Mapping as ORM;
5
6 /**
7 * This class represents a single post in a blog.
8 * @ORM\Entity
9 * @ORM\Table(name="post")
10 */
11 class Post
12 {
13 // Post status constants.
14 const STATUS_DRAFT = 1; // Draft.
15 const STATUS_PUBLISHED = 2; // Published.
16
17 /**
18 * @ORM\Id
19 * @ORM\GeneratedValue
20 * @ORM\Column(name="id")
⁹http://docs.doctrine-project.org/en/2.0.x/reference/annotations-reference.html
Database Management with Doctrine ORM 369
21 */
22 protected $id;
23
24 /**
25 * @ORM\Column(name="title")
26 */
27 protected $title;
28
29 /**
30 * @ORM\Column(name="content")
31 */
32 protected $content;
33
34 /**
35 * @ORM\Column(name="status")
36 */
37 protected $status;
38
39 /*
40 * @ORM\Column(name="date_created")
41 */
42 protected $dateCreated;
43
44 // Returns ID of this post.
45 public function getId()
46 {
47 return $this->id;
48 }
49
50 // Sets ID of this post.
51 public function setId($id)
52 {
53 $this->id = $id;
54 }
55
56 // Returns title.
57 public function getTitle()
58 {
59 return $this->title;
60 }
61
62 // Sets title.
63 public function setTitle($title)
64 {
65 $this->title = $title;
66 }
Database Management with Doctrine ORM 370
67
68 // Returns status.
69 public function getStatus()
70 {
71 return $this->status;
72 }
73
74 // Sets status.
75 public function setStatus($status)
76 {
77 $this->status = $status;
78 }
79
80 // Returns post content.
81 public function getContent()
82 {
83 return $this->content;
84 }
85
86 // Sets post content.
87 public function setContent($content)
88 {
89 $this->content = $content;
90 }
91
92 // Returns the date when this post was created.
93 public function getDateCreated()
94 {
95 return $this->dateCreated;
96 }
97
98 // Sets the date when this post was created.
99 public function setDateCreated($dateCreated)
100 {
101 $this->dateCreated = $dateCreated;
102 }
103 }
• Status constants (lines 14 and 15). These constants conveniently represent possible values
the $status property may receive (1 for Draft, 2 for Published).
• Entity class has protected properties ($title, $content, $dateCreated, etc.). These are
data that a typical blog post has (see table 12.4 below for reference of properties together
with their brief descriptions).
Database Management with Doctrine ORM 371
Please note that for properties we (by convention) use camel-case names (like
$dateCreated), while for database columns we use “canonicalized” names (in lower-
case and with underscores separating words in a name, like date_created).
• Entity class and its properties are marked with Docblock annotations read by Doctrine
ORM at run-time allowing it to know how to map this entity and its properties on the
database table and its columns.
• Entity class has getter and setter methods (lines 45-102) allowing to access/modify
the protected properties (see the table 12.5 for reference of methods and their brief
descriptions).
Method Description
getId() Returns ID of this post.
setId($id) Sets ID of this post.
getTitle() Returns title.
setTitle($title) Sets title.
getStatus() Returns status (draft/published).
setStatus($status) Sets status.
getContent() Returns post content.
setContent($content) Sets post content.
getDateCreated() Returns the date when this post was created.
setDateCreated() Sets the date when this post was created.
Note that we do not mark entity class methods with Doctrine annotations. There is
just no need to do that. However, you may mark methods with usual comments and
non-Doctrine Docblock annotations, if you strongly wish.
1 <?php
2 namespace Application\Entity;
3
4 use Doctrine\ORM\Mapping as ORM;
5
6 /**
7 * This class represents a comment related to a blog post.
8 * @ORM\Entity
9 * @ORM\Table(name="comment")
10 */
11 class Comment
12 {
13 /**
14 * @ORM\Id
15 * @ORM\Column(name="id")
16 * @ORM\GeneratedValue
17 */
18 protected $id;
19
20 /**
21 * @ORM\Column(name="content")
22 */
23 protected $content;
24
25 /**
26 * @ORM\Column(name="author")
27 */
28 protected $author;
29
30 /**
31 * @ORM\Column(name="date_created")
32 */
33 protected $dateCreated;
34
35 // Returns ID of this comment.
36 public function getId()
37 {
38 return $this->id;
39 }
40
41 // Sets ID of this comment.
42 public function setId($id)
43 {
44 $this->id = $id;
Database Management with Doctrine ORM 373
45 }
46
47 // Returns comment text.
48 public function getContent()
49 {
50 return $this->content;
51 }
52
53 // Sets status.
54 public function setContent($comment)
55 {
56 $this->comment = $comment;
57 }
58
59 // Returns author's name.
60 public function getAuthor()
61 {
62 return $this->author;
63 }
64
65 // Sets author's name.
66 public function setAuthor($author)
67 {
68 $this->author = $author;
69 }
70
71 // Returns the date when this comment was created.
72 public function getDateCreated()
73 {
74 return $this->dateCreated;
75 }
76
77 // Sets the date when this comment was created.
78 public function setDateCreated($dateCreated)
79 {
80 $this->dateCreated = $dateCreated;
81 }
82 }
Next, create Tag.php file and put the following code inside of it:
Database Management with Doctrine ORM 374
1 <?php
2 namespace Application\Entity;
3
4 use Doctrine\ORM\Mapping as ORM;
5
6 /**
7 * This class represents a tag.
8 * @ORM\Entity
9 * @ORM\Table(name="tag")
10 */
11 class Tag
12 {
13 /**
14 * @ORM\Id
15 * @ORM\GeneratedValue
16 * @ORM\Column(name="id")
17 */
18 protected $id;
19
20 /**
21 * @ORM\Column(name="name")
22 */
23 protected $name;
24
25 // Returns ID of this tag.
26 public function getId()
27 {
28 return $this->id;
29 }
30
31 // Sets ID of this tag.
32 public function setId($id)
33 {
34 $this->id = $id;
35 }
36
37 // Returns name.
38 public function getName()
39 {
40 return $this->name;
41 }
42
43 // Sets name.
44 public function setName($name)
45 {
46 $this->title = $name;
Database Management with Doctrine ORM 375
47 }
48 }
Since the Comment and Tag entities are analogous to the Post entity, we don’t provide detailed
description of the code above.
Please note that we do not create an entity for the fourth auxiliary table post_tag. That
table will be indirectly used further in this chapter when defining relations between
entities.
In Doctrine, to express a relation between two entities, you add a private property paired with
Docblock annotation.
For detailed information about relations between entities in Doctrine, please read this
page¹⁰ of Doctrine documentation.
12.6.3.1 OneToMany/ManyToOne
First, let’s define one-to-many relation between the Post and Comment entities. Modify the
Post.php file and add the following lines:
1 <?php
2 // ...
3 use Doctrine\Common\Collections\ArrayCollection;
4 use Application\Entity\Comment;
5
6 /**
7 * This class represents a single post in a blog.
8 * @ORM\Entity
9 * @ORM\Table(name="post")
10 */
11 class Post
12 {
¹⁰http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html
Database Management with Doctrine ORM 376
13 // ...
14
15 /**
16 * @ORM\OneToMany(targetEntity="\Application\Entity\Comment", mappedBy="pos\
17 t")
18 * @ORM\JoinColumn(name="id", referencedColumnName="post_id")
19 */
20 protected $comments;
21
22 /**
23 * Constructor.
24 */
25 public function __construct()
26 {
27 $this->comments = new ArrayCollection();
28 $this->tags = new ArrayCollection();
29 }
30
31 /**
32 * Returns comments for this post.
33 * @return array
34 */
35 public function getComments()
36 {
37 return $this->comments;
38 }
39
40 /**
41 * Adds a new comment to this post.
42 * @param $comment
43 */
44 public function addComment($comment)
45 {
46 $this->comments[] = $comment;
47 }
48 }
As you can see from the code above, we added the $comments property (line 20). This property
will be the collection of comments related to certain post.
We initialize the $comments property in class constructor (lines 25-29). By assigning it with a
new instance of Doctrine\Common\Collections\ArrayCollection class.
A Doctrine ArrayCollection is an array of objects, like usual PHP array, but with
additional features required by Doctrine. It is implemented in DoctrineCommon
component.
Database Management with Doctrine ORM 377
In lines 15-19, we add Doctrine annotations to the $comments property, so Doctrine knows how
to get all comments associated with the post:
• the @ORM\OneToMany tag defines that this is the one-to-many relation between the Post
entity and the (target) Comment entity.
• the @ORM\JoinColumn tag specifies which column to use for joining the tables associated
with the entities.
The getComments() method (lines 35-37) allows to do get all comments associated with the post.
We also added the addComment() method (lines 44-47) for adding new comment to post. You can
notice that we use the [] operator, just like we do with a typical PHP array.
Vice versa, we define the other side of this relation by modifying the Comment entity as follows:
1 <?php
2 // ...
3 use Doctrine\Common\Collections\ArrayCollection;
4
5 // ...
6 class Comment
7 {
8 /**
9 * @ORM\ManyToOne(targetEntity="Application\Entity\Post", inversedBy="comme\
10 nts")
11 * @ORM\JoinColumn(name="post_id", referencedColumnName="id")
12 */
13 protected $post;
14
15 /*
16 * Returns associated post.
17 * @return \Application\Entity\Post
18 */
19 public function getPost()
20 {
21 return $this->post;
22 }
23
24 /**
25 * Sets associated post.
26 * @param \Application\Entity\Post $post
27 */
28 public function setPost($post)
29 {
30 $this->post = $post;
31 $post->addComment($this);
32 }
33 }
Database Management with Doctrine ORM 378
In the code above, we added the $post private property to the entity class. This is not a collection,
but a single instance of Post class, because single comment always belongs to single post. The
annotation tags @ORM\ManyToOne and @ORM\JoinColumn are analogous to those we covered before.
12.6.3.2 ManyToMany
Let’s now express the many-to-many relation between the Post and Tag entities. For this relation,
we indirectly use the auxiliary post_tag table.
Modify the Post entity as follows:
1 <?php
2 //...
3 use Application\Entity\Tag;
4
5 //...
6 class Post
7 {
8 //...
9
10 /**
11 * @ORM\ManyToMany(targetEntity="\Application\Entity\Tag", inversedBy="post\
12 s")
13 * @ORM\JoinTable(name="post_tag",
14 * joinColumns={@ORM\JoinColumn(name="post_id", referencedColumnName="\
15 id")},
16 * inverseJoinColumns={@ORM\JoinColumn(name="tag_id", referencedColumn\
17 Name="id")}
18 * )
19 */
20 protected $tags;
21
22 // Constructor.
23 public function __construct()
24 {
25 //...
26 $this->tags = new ArrayCollection();
27 }
28
29 // Returns tags for this post.
30 public function getTags()
31 {
32 return $this->tags;
33 }
34
35 // Adds a new tag to this post.
Database Management with Doctrine ORM 379
1 <?php
2 //...
3 use Doctrine\Common\Collections\ArrayCollection;
4
5 class Tag
6 {
7 // ...
8
9 /**
10 * @ORM\ManyToMany(targetEntity="\Application\Entity\Post", mappedBy="tags")
11 */
12 protected $posts;
13
14 // Constructor.
15 public function __construct()
16 {
17 $this->posts = new ArrayCollection();
18 }
19
20 // Returns posts associated with this tag.
21 public function getPosts()
22 {
Database Management with Doctrine ORM 380
23 return $this->posts;
24 }
25
26 // Adds a post into collection of posts related to this tag.
27 public function addPost($post)
28 {
29 $this->posts[] = $post;
30 }
31 }
In the code above, we by analogy define the other side of the relation and getter/setter methods
for retrieving the collection of posts associated with the tag, and adding posts associated with
the given tag.
1 <?php
2 namespace Application;
3
4 return array(
5 // ...
6 'doctrine' => array(
7 'driver' => array(
8 __NAMESPACE__ . '_driver' => array(
9 'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver',
10 'cache' => 'array',
11 'paths' => array(__DIR__ . '/../src/' . __NAMESPACE__ . '/Entity')
12 ),
13 'orm_default' => array(
14 'drivers' => array(
15 __NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_driver'
16 )
17 )
18 )
19 )
20 );
Above, in line 2, we specify the namespace Application. This should be the name of the current
module.
Note that usually we do not specify namespace in config files, but in this particular
case it is convenient to do. When we have namespace defined, we can use the __-
NAMESPACE__ placeholder which expands into that namespace.
Database Management with Doctrine ORM 381
In line 6, we have doctrine key, under which we have the driver subkey. In line 11, we tell
Doctrine ORM that our entities are stored inside of Application/Entity directory under the
module’s src directory.
EntityManager is registered as a service in the Zend Framework 2 service manager (see Chapter
3 for additional information about service manager). In your controller class, you retrieve the
EntityManager from service manager as follows (if you need a different connection than orm_-
default, just replace the orm_default with the required connection name):
The most used methods provided by the EntityManager class are listed in table 12.6 below.
Method Description
persist($entity) Places new entity into repository (makes it managed).
remove($entity) Removes an entity instance from repository.
flush() Flushes all changes to objects that have been queued up to now to
the database.
createQuery($dql) Creates a new Query object.
getRepository($entityName) Gets the repository for an entity class.
When you call persist() or remove(), EntityManager remembers your changes in memory, but
doesn’t apply changes to database automatically (by performance reasons). To apply changes to
database in a single transaction, you use the flush() method.
For example, look at the code example below that shows how to create an instance of the Post
entity and save it to database:
Database Management with Doctrine ORM 382
The createQuery() method is designed for creating a query from a DQL string. It returns the
Query object. You then execute the query and get results (an array of entities matching search
conditions).
The getRepository() method is designed to get repository by entity class name. Please look
below for example where we get the repository for our Post entity:
$repository = $entityManager->getRepository('\Application\Entity\Post')
The most used methods provided by the EntityRepository class are listed in table 12.6.
Method Description
findAll() Finds all entities in the repository.
find($id) Finds an entity by its identifier.
findBy($criteria, $orderBy, $limit, Finds entities by a set of criteria.
$offset)
Database Management with Doctrine ORM 383
Method Description
findOneBy($criteria, $orderBy) Finds a single entity by a set of criteria.
createQueryBuilder($alias) Creates a new QueryBuilder instance that is
prepopulated for this entity name.
The findAll() method gets all entities from repository. For simple example of its usage, look
below:
The find() method is the simplest method of searching for an entity. It retrieves an entity by its
ID (primary key).
In the example below, we select post with ID = 1.
The findBy() takes a search criteria (and optional sorting order and limit) arguments and returns
a collection of entities matching criteria. The findOneBy() method is very similar to findBy(),
but it returns the first entity matching the criteria.
In the code example below, we use the findBy() method for selecting 50 most recent published
posts:
And the most complex search method is the createQueryBuilder(). That method allows to
create complex DQL queries. For information on using query builder, please refer to Doctrine
documentation.
If standard find methods are not sufficient (or if you have complex search criterias and DQL
queries), you can create your own repository by extending the standard EntityRepository class
and encapsulate the search logic there. We will show how to do that later when implementing
tag cloud feature for our Blog sample.
1 <?php
2 namespace Application\Controller;
3
4 use Zend\Mvc\Controller\AbstractActionController;
5 use Zend\View\Model\ViewModel;
6
7 class IndexController extends AbstractActionController {
8
9 // This is the default "index" action of the controller. It displays the
10 // Posts page containing the recent blog posts.
11 public function indexAction()
12 {
13 // Get Doctrine entity manager
14 $entityManager = $this->getServiceLocator()
15 ->get('doctrine.entitymanager.orm_default');
16
17 // Get recent posts
18 $posts = $entityManager->getRepository('\Application\Entity\Post')
19 ->findBy(array('status'=>Post::STATUS_PUBLISHED),
20 array('dateCreated'=>'DESC'));
21
22 // Render the view template
23 return new ViewModel(array(
24 'posts' => $posts
25 ));
26 }
27 }
In the code above, we retrieve the EntityManager service from ZF2 service manager (line 14).
We get the repository of the Post entities with entity manager’s getRepository() method (line
18). With the findBy() method provided by repository, we select published posts sorted by date
in descending order (line 19). And, in line 23 we pass the selected posts to the view for rendering.
Next, modify the index.phtml view template file in application/index directory under module’s
view directory and put the following content into it:
1 <h1>Posts</h1>
2
3 <?php foreach($posts as $post): ?>
4
5 <h3>
6 <a href="#">
7 <?php echo $post->getTitle(); ?>
8 </a>
9 </h3>
10
Database Management with Doctrine ORM 385
11 <p>
12 <?php echo $post->getContent(); ?>
13 </p>
14
15 <?php endforeach; ?>
In the view template above, we go in turn through the posts we selected and render each one’s
title and content. That simple!
Now, if you open the Blog web application in your browser, you should be able to see the
following page containing the list of posts (look at figure 12.3 below).
• the PostForm form model will be used for entering and validation of post title, content,
status and tags;
• the PostManager service model will contain business logic for saving new post to database;
Database Management with Doctrine ORM 386
• the PostController::addAction() controller action method will be used for getting form
data, and calling PostManager for saving the data to database.
• and add.phtml view template will render the form.
1 <?php
2
3 namespace Application\Form;
4
5 use Zend\Form\Form;
6 use Zend\InputFilter\InputFilter;
7
8 /**
9 * This form is used to collect post data.
10 */
11 class PostForm extends Form
12 {
13 /**
14 * Constructor.
15 */
16 public function __construct()
17 {
18 // Define form name
19 parent::__construct('post-form');
20
21 // Set POST method for this form
22 $this->setAttribute('method', 'post');
23
24 $this->addElements();
25 $this->addInputFilter();
26
27 }
28
29 /**
30 * This method adds elements to form (input fields and submit button).
31 */
32 protected function addElements()
33 {
34
Database Management with Doctrine ORM 387
127 $inputFilter->add(array(
128 'name' => 'content',
129 'required' => true,
130 'filters' => array(
131 array('name' => 'StripTags'),
132 ),
133 'validators' => array(
134 array(
135 'name' => 'StringLength',
136 'options' => array(
137 'min' => 1,
138 'max' => 4096
139 ),
140 ),
141 ),
142 )
143 );
144
145 $inputFilter->add(array(
146 'name' => 'tags',
147 'required' => true,
148 'filters' => array(
149 array('name' => 'StringTrim'),
150 array('name' => 'StripTags'),
151 array('name' => 'StripNewLines'),
152 ),
153 'validators' => array(
154 array(
155 'name' => 'StringLength',
156 'options' => array(
157 'min' => 1,
158 'max' => 1024
159 ),
160 ),
161 ),
162 )
163 );
164 }
165 }
As you can see from the code above, the PostForm class defines a ZF2 form with title, content,
tags, and status fields. It also has the Submit button.
Since we covered forms in details in Chapter 7, here we do not explain the code
presented above deeply.
Database Management with Doctrine ORM 390
The PostManager service will contain business logic of the Blog sample. This business
logic includes, but not limited to, adding new post to blog.
Create the PostManager.php file inside the Application/Service directory under the module’s
source directory. Put the following content into that file:
1 <?php
2 namespace Application\Service;
3 use Zend\ServiceManager\ServiceManager;
4 use Zend\ServiceManager\ServiceManagerAwareInterface;
5 use Application\Entity\Post;
6 use Application\Entity\Comment;
7 use Application\Entity\Tag;
8 use Zend\Filter\StaticFilter;
9
10 // The PostManager service is responsible for adding new posts.
11 class PostManager implements ServiceManagerAwareInterface
12 {
13 // Service manager.
14 private $serviceManager = null;
15
16 // Sets service manager.
17 public function setServiceManager(ServiceManager $serviceManager)
18 {
19 $this->serviceManager = $serviceManager;
20 }
21
22 // Returns service manager.
23 public function getServiceLocator()
24 {
25 return $this->serviceManager;
26 }
27
28 // This method adds a new post.
29 public function addNewPost($title, $content, $tags, $status)
30 {
31 // Get Doctrine entity manager.
32 $entityManager = $this->getServiceLocator()
Database Management with Doctrine ORM 391
33 ->get('doctrine.entitymanager.orm_default');
34
35 // Create new Post entity.
36 $post = new Post();
37 $post->setTitle($title);
38 $post->setContent($content);
39 $post->setStatus($status);
40 $currentDate = date('Y-m-d H:i:s');
41 $post->setDateCreated($currentDate);
42
43 // Add the entity to entity manager.
44 $entityManager->persist($post);
45
46 // Add tags to post
47 $this->addTagsToPost($tags, $post);
48
49 // Apply changes to database.
50 $entityManager->flush();
51 }
52
53 // Adds/updates tags in the given post.
54 private function addTagsToPost($tagsStr, $post)
55 {
56 // Get Doctrine entity manager.
57 $entityManager = $this->getServiceLocator()
58 ->get('doctrine.entitymanager.orm_default');
59
60 // Remove tag associations (if any)
61 $tags = $post->getTags();
62 foreach ($tags as $tag) {
63 $post->removeTag($tag);
64 }
65
66 // Add tags to post
67 $tags = explode(',', $tagsStr);
68 foreach ($tags as $tagName) {
69
70 $tagName = StaticFilter::execute($tagName, 'StringTrim');
71 if (empty($tagName)) {
72 continue;
73 }
74
75 $tag = $entityManager->getRepository('\Application\Entity\Tag')
76 ->findOneBy(array('name' => $tagName));
77 if ($tag == null)
78 $tag = new Tag();
Database Management with Doctrine ORM 392
79 $tag->setName($tagName);
80 $tag->addPost($post);
81
82 $entityManager->persist($tag);
83
84 $post->addTag($tag);
85 }
86 }
87 }
As you can see from the code above, our service model implements the ServiceManagerAwareInterface
interface (line 11). When we use service manager to instantiate this service, it will automatically
call the setServiceManager() method on the service and pass an instance of ServiceManager
to it as an argument (line 17). We save the service manager as a private property for further use.
In lines 29-51, we have the addNewPost() public method which takes post title, content, comma-
separated list of tags, and status as arguments. It then creates a new instance of Post entity
(line 36) and fills its properties with user-provided data. It uses the EntityManager’s persist()
method (line 44) to add the newly created entity to entity manager. The addTagsToPost() private
method is called (line 47) to assign the post with one or several tags. And the flush() method is
used for applying changes to database in a single transaction (line 50).
The addTagsToPost() private method contains logic for removing old associations between the
post and tags (lines 61-64), then parsing comma-separated list of tags (line 67), and assigning
new tags to the post (lines 75-84).
Finally, we register PostManager service by modifying module.config.php configuration file as
follows:
1 <?php
2 //...
3 return array(
4 //...
5 'service_manager' => array(
6 //...
7 'invokables' => array(
8 'post_manager'=>'Application\Service\PostManager',
9 ),
10 ),
11 //...
12 );
Now, you can retrieve a singleton instance of the PostManager service using controller-provided
getServiceLocator() method, as shown in example below:
$postManager = $this->getServiceLocator()->get('post_manager');
Database Management with Doctrine ORM 393
Above, in line 12, we create an instance of PostForm form. In line 14, we get singleton instance
of the PostManager service.
In line 17, we check whether this is a POST request. If the request is a POST request, we fill
the form with input data and validate the data. In case of valid data, we call the addNewPost()
method on the PostManager service (line 31), and redirect the user to the list of posts.
Finally, we add the view template. Create the add.phtml file in application/post directory under
module’s view directory and put the following content into it:
1 <?php
2 $form = $this->form;
3 $form->get('title')->setAttributes(array(
4 'class'=>'form-control',
5 'placeholder'=>'Enter post title here'
6 ));
7 $form->get('content')->setAttributes(array(
8 'class'=>'form-control',
9 'placeholder'=>'Type content here',
10 'rows'=>6
11 ));
12 $form->get('tags')->setAttributes(array(
13 'class'=>'form-control',
14 'placeholder'=>'comma, separated, list, of, tags'
15 ));
16 $form->get('status')->setAttributes(array(
17 'class'=>'form-control'
18 ));
19 $form->get('submit')->setAttributes(array('class'=>'btn btn-primary'));
20 $form->prepare();
21
22 ?>
23
24 <h1>Add New Post</h1>
25
26 <p>
27 Please fill out the following form and click the <i>Create</i> button.
28 </p>
29
30 <div class="row">
31 <div class="col-md-6">
Database Management with Doctrine ORM 395
Now, if you open the URL http://localhost/application/post/add in your web browser, you should
see the Add New Post page like shown in figure 12.5 below:
Database Management with Doctrine ORM 396
Filling the form and clicking the Create button results in saving the new post to database. Then
you are able to see the newly created post in the list of posts at the Home page.
• to create a form that would allow to enter post title, content, etc. For this page, we can
successfully reuse the PostForm form we created earlier (we just rename the Create button
caption into Save).
• to add updatePost() method to the PostManager service. The method would find the post
by ID in database and update its data;
• to add convertTagsToString() method to the PostManager service. This method would
take the post entity, and on output produce string containing comma-separated list of tags;
• to add the PostController::editAction() action method that would take user input, pass
it to models and return data for rendering;
• and add the edit.phtml view template file that would render the form.
1 <?php
2 //...
3 class PostManager implements ServiceManagerAwareInterface
4 {
5 //...
6
7 // This method allows to update data of a single post.
8 public function updatePost($post, $title, $content, $tags, $status)
9 {
10 // Get Doctrine entity manager.
11 $entityManager = $this->getServiceLocator()
12 ->get('doctrine.entitymanager.orm_default');
13
14 $post->setTitle($title);
15 $post->setContent($content);
16 $post->setStatus($status);
17
18 // Add tags to post
19 $this->addTagsToPost($tags, $post);
20
21 // Apply changes to database.
22 $entityManager->flush();
23 }
24
25 // Converts tags of the given post to comma separated list (string).
26 public function convertTagsToString($post)
27 {
28 $tags = $post->getTags();
29 $tagCount = count($tags);
Database Management with Doctrine ORM 398
30 $tagsStr = '';
31 $i = 0;
32 foreach ($tags as $tag) {
33 $i ++;
34 $tagsStr .= $tag->getName();
35 if ($i < $tagCount)
36 $tagsStr .= ', ';
37 }
38
39 return $tagsStr;
40 }
41 }
Above, we have the updatePost() method (lines 8-24) that takes an existing Post entity, the new
title, content, status and the list of tags. It then updates entity’s properties and saves changes to
database using flush() method.
Note that the updatePost() method doesn’t use the persist() method of entity
manager, because here we have existing post, not a new one.
Then, we have the convertTagsToString() method (lines 26-39) which takes the post, goes
through Tag entities associated with the post and formats and returns the comma-separated
list of tags.
1 <?php
2 namespace Application\Controller;
3 //...
4 use Application\Form\PostForm;
5 use Application\Entity\Post;
6
7 class PostController extends AbstractActionController
8 {
9 // This action displays the page allowing to edit a post.
10 public function editAction()
11 {
12 // Create the form.
13 $form = new PostForm();
14
15 // Get post ID.
16 $postId = $this->params()->fromRoute('id', -1);
Database Management with Doctrine ORM 399
17
18 // Get Doctrine entity manager.
19 $entityManager = $this->getServiceLocator()
20 ->get('doctrine.entitymanager.orm_default');
21
22 // Get PostManager service.
23 $postManager = $this->getServiceLocator()
24 ->get('post_manager');
25
26 // Find existing post in the database.
27 $post = $entityManager->getRepository('\Application\Entity\Post')
28 ->findOneBy(array('id'=>$postId));
29 if ($post == null) {
30 $this->getResponse()->setStatusCode(404);
31 return;
32 }
33
34 // Check whether this post is a POST request.
35 if ($this->getRequest()->isPost()) {
36
37 // Get POST data.
38 $data = $this->params()->fromPost();
39
40 // Fill form with data.
41 $form->setData($data);
42 if ($form->isValid()) {
43
44 // Get validated form data.
45 $data = $form->getData();
46
47 // Use post manager service to add new post to database. \
48
49 $postManager->updatePost($post, $data['title'], $data['content'],
50 $data['tags'], $data['status']);
51
52 // Redirect the user to "index" page.
53 return $this->redirect()->toRoute('application/default',
54 array('controller'=>'post', 'action'=>'admin'));
55 }
56 } else {
57 $data = array(
58 'title' => $post->getTitle(),
59 'content' => $post->getContent(),
60 'tags' => $postManager->convertTagsToString($post),
61 'status' => $post->getStatus()
62 );
Database Management with Doctrine ORM 400
63
64 $form->setData($data);
65 }
66
67 // Render the view template.
68 return new ViewModel(array(
69 'form' => $form,
70 'post' => $post
71 ));
72 }
73 }
In the code above, we extract the post ID using the fromRoute() method of the params()
controller plugin. Then we search for post having such ID using the findOneBy() method
provided by the entity repository.
Then we check if this is a POST request. If this is the POST request, we fill in and validate the
form with POST data. Then we use the updatePost() method of the PostManager service.
Finally, create the application/post/edit.phtml file under the module’s view directory. Place the
following content there:
1 <?php
2 $form = $this->form;
3 $form->get('title')->setAttributes(array(
4 'class'=>'form-control',
5 'placeholder'=>'Enter post title here'
6 ));
7 $form->get('content')->setAttributes(array(
8 'class'=>'form-control',
9 'placeholder'=>'Type content here',
10 'rows'=>6
11 ));
12 $form->get('tags')->setAttributes(array(
13 'class'=>'form-control',
14 'placeholder'=>'comma, separated, list, of, tags'
15 ));
16 $form->get('status')->setAttributes(array(
17 'class'=>'form-control'
18 ));
19 $form->get('submit')->setAttributes(array('class'=>'btn btn-primary'));
20 $form->get('submit')->setValue('Save');
21 $form->prepare();
22
23 ?>
24
25 <h1>Edit Post</h1>
Database Management with Doctrine ORM 401
26
27 <p>
28 Please fill out the following form and click the *Save* button.
29 </p>
30
31 <div class="row">
32 <div class="col-md-6">
33 <?php echo $this->form()->openTag($form); ?>
34
35 <div class="form-group">
36 <?php echo $this->formLabel($form->get('title')); ?>
37 <?php echo $this->formElement($form->get('title')); ?>
38 <?php echo $this->formElementErrors($form->get('title')); ?> \
39
40 </div>
41
42 <div class="form-group">
43 <?php echo $this->formLabel($form->get('content')); ?>
44 <?php echo $this->formElement($form->get('content')); ?>
45 <?php echo $this->formElementErrors($form->get('content')); ?> \
46
47 </div>
48
49 <div class="form-group">
50 <?php echo $this->formLabel($form->get('tags')); ?>
51 <?php echo $this->formElement($form->get('tags')); ?>
52 <?php echo $this->formElementErrors($form->get('tags')); ?> \
53
54 <p class="help-block">Separate tags with comma.</p>
55 </div>
56
57 <div class="form-group">
58 <?php echo $this->formLabel($form->get('status')); ?>
59 <?php echo $this->formElement($form->get('status')); ?>
60 <?php echo $this->formElementErrors($form->get('status')); ?> \
61
62 </div>
63
64 <?php echo $this->formElement($form->get('submit')); ?>
65
66 <?php echo $this->form()->closeTag(); ?>
67 </div>
68 </div>
Now, if you open the URL http://localhost/application/post/edit/<id> in your web browser, you
should be able to see the Edit Post page that allows to edit an existing post (see the figure 12.5
Database Management with Doctrine ORM 402
below):
associations. Site visitor will be able to trigger the action by entering the following URL in
browser’s navigation bar: http://localhost/application/post/delete/<id>, where <id> is the unique
identifier of the post. Finally, the action redirects the site visitor to the Admin page.
1 <?php
2 //...
3 class PostManager implements ServiceManagerAwareInterface
4 {
5 //...
6
7 // Removes post and all associated comments.
8 public function removePost($post)
9 {
10 $entityManager = $this->getServiceLocator()
11 ->get('doctrine.entitymanager.orm_default');
12
13 // Remove associated comments
14 $comments = $post->getComments();
15 foreach ($comments as $comment) {
16 $entityManager->remove($comment);
17 }
18
19 // Remove tag associations (if any)
20 $tags = $post->getTags();
21 foreach ($tags as $tag) {
22 $post->removeTagAssociation($tag);
23 }
24
25 $entityManager->remove($post);
26
27 $entityManager->flush();
28 }
29 }
In the code above, we first retrieve all comments associated with the post using the getComments()
method of the Post entity. Then we call EntityManager’s remove() method and pass it each
comment that we want to remove.
Next, we get all tags associated with the post by calling Post’s getTags() method. We remove
association between the post and tag (but not tag itself!) with the help of the Post’s removeTag()
method (see below for the code of the method).
Database Management with Doctrine ORM 404
Finally, we remove the post itself by calling the EntityManager’s remove() method. We apply
changes to database with the flush() method.
And here is the code of the Post::removeTagAssociation() method:
1 <?php
2
3 //..
4 class PostController extends AbstractActionController
5 {
6 // This "delete" action displays the Delete Post page.
7 public function deleteAction()
8 {
9 $postId = $this->params()->fromRoute('id', -1);
10
11 $entityManager = $this->getServiceLocator()
12 ->get('doctrine.entitymanager.orm_default');
13
14 $post = $entityManager->getRepository('\Application\Entity\Post')
15 ->findOneBy(array('id'=>$postId));
16 if ($post == null) {
17 $this->getResponse()->setStatusCode(404);
18 return;
19 }
20
21 $postManager = $this->getServiceLocator()->get('post_manager');
22 $postManager->removePost($post);
23
24 // Redirect the user to "index" page.
25 return $this->redirect()->toRoute('application/default',
26 array('controller'=>'post', 'action'=>'admin'));
27 }
28 }
Database Management with Doctrine ORM 405
• to create the form that would allow to enter the comment and its author’s name;
• to modify the PostManager and add all necessary business logic;
• to create PostController::viewAction() controller’s action;
• and to create the view.phtml view template.
Database Management with Doctrine ORM 407
1 <?php
2 namespace Application\Form;
3
4 use Zend\Form\Form;
5 use Zend\InputFilter\InputFilter;
6
7 /**
8 * This form is used to collect comment data.
9 */
10 class CommentForm extends Form
11 {
12 // Constructor.
13 public function __construct()
14 {
15 // Define form name
16 parent::__construct('comment-form');
17
18 // Set POST method for this form
19 $this->setAttribute('method', 'post');
20
21 $this->addElements();
22 $this->addInputFilter();
23
24 }
25
26 // This method adds elements to form (input fields and submit button).
27 protected function addElements()
28 {
29 // Add "author" field
30 $this->add(array(
31 'type' => 'text',
32 'name' => 'author',
33 'attributes' => array(
34 'id' => 'author'
35 ),
36 'options' => array(
37 'label' => 'Author',
38 ),
39 ));
40
Database Management with Doctrine ORM 408
87
88 $inputFilter->add(array(
89 'name' => 'comment',
90 'required' => true,
91 'filters' => array(
92 array('name' => 'StripTags'),
93 ),
94 'validators' => array(
95 array(
96 'name' => 'StringLength',
97 'options' => array(
98 'min' => 1,
99 'max' => 4096
100 ),
101 ),
102 ),
103 )
104 );
105 }
106 }
As you see from the code above, the CommentForm form contains the author, comment fields, and
the Submit button.
Since we covered forms in details in Chapter 7, here we do not explain the code
presented above deeply.
1 <?php
2 //...
3
4 /**
5 * The PostManager service is responsible for adding new posts.
6 */
7 class PostManager implements ServiceManagerAwareInterface
8 {
9 //...
10
11 // Returns count of comments for given post as properly formatted string.
Database Management with Doctrine ORM 410
1 <?php
2 namespace Application\Controller;
3
4 use Zend\Mvc\Controller\AbstractActionController;
5 use Zend\View\Model\ViewModel;
6 use Application\Form\PostForm;
7 use Application\Entity\Post;
8 use Application\Form\CommentForm;
9 use Application\Entity\Comment;
10
11 /**
12 * This is the Post controller class of the Blog application.
13 * This controller is used for managing posts (adding/editing/viewing/deletin\
14 g).
15 */
16 class PostController extends AbstractActionController
17 {
18 /**
19 * This action displays the "View Post" page allowing to see the post title
20 * and content. The page also contains a form allowing
21 * to add a comment to post.
22 */
23 public function viewAction()
24 {
25 $postId = $this->params()->fromRoute('id', -1);
26
27 $entityManager = $this->getServiceLocator()->get('doctrine.entitymanager.\
28 orm_default');
29 $postManager = $this->getServiceLocator()->get('post_manager');
30
31 $post = $entityManager->getRepository('\Application\Entity\Post')
32 ->findOneBy(array('id'=>$postId));
33
34 if ($post == null) {
35 $this->getResponse()->setStatusCode(404);
36 return;
37 }
38
39 $commentCount = $postManager->getCommentCountStr($post);
40
41 // Create the form.
42 $form = new CommentForm();
43
44 // Check whether this post is a POST request.
45 if($this->getRequest()->isPost()) {
46
Database Management with Doctrine ORM 412
Finally, add the view.phtml view template file and put the following content there:
1 <?php
2 $form = $this->form;
3 $form->get('author')->setAttributes(array(
4 'class'=>'form-control',
5 'placeholder'=>'Author\'s name'
6 ));
7 $form->get('comment')->setAttributes(array(
8 'class'=>'form-control',
9 'rows'=>6,
10 'placeholder'=>'Text'
11 ));
12 $form->get('submit')->setAttributes(array('class'=>'btn btn-primary'));
13 $form->prepare();
Database Management with Doctrine ORM 413
14 ?>
15
16 <a href="
17 <?php echo $this->url('application/default',
18 array('controller'=>'index', 'action'=>'index')); ?>">
19 << Back to list of posts
20 </a>
21
22 <h1>
23 <?php echo $post->getTitle(); ?>
24 </h1>
25
26 <p class="comments-header">
27 <?php echo $postManager->getCommentCountStr($post); ?> |
28 <a href="#comment">
29 Add new comment
30 </a>
31 </p>
32
33 <p>
34 <?php echo $post->getContent(); ?>
35 </p>
36
37 <p>
38 Tags: <?php echo $postManager->convertTagsToString($post); ?>
39 </p>
40
41 <h3><?php echo $postManager->getCommentCountStr($post); ?></h3>
42
43 <?php foreach ($post->getComments() as $comment): ?>
44
45 <hr>
46
47 <p>
48 <?php echo $comment->getAuthor() ?> on
49 <?php echo $comment->getDateCreated(); ?>
50 </p>
51
52 <p>
53 <?php echo $comment->getContent(); ?>
54 </p>
55
56 <?php endforeach; ?>
57
58 <hr>
59
Database Management with Doctrine ORM 414
60 <a name="comment"></a>
61 <h3>Leave Reply</h3>
62
63 <div class="row">
64 <div class="col-md-8">
65 <?php echo $this->form()->openTag($form); ?>
66
67 <div class="form-group">
68 <?php echo $this->formLabel($form->get('author')); ?>
69 <?php echo $this->formElement($form->get('author')); ?>
70 <?php echo $this->formElementErrors($form->get('author')); ?> \
71
72 </div>
73
74 <div class="form-group">
75 <?php echo $this->formLabel($form->get('comment')); ?>
76 <?php echo $this->formElement($form->get('comment')); ?>
77 <?php echo $this->formElementErrors($form->get('comment')); ?> \
78
79 </div>
80
81 <?php echo $this->formElement($form->get('submit')); ?>
82
83 <?php echo $this->form()->closeTag(); ?>
84 </div>
85 </div>
1 <?php
2
3 //..
4 class PostController extends AbstractActionController
5 {
6 /**
7 * This "admin" action displays the Manage Posts page. This page contains
8 * the list of posts with an ability to edit/delete any post.
9 */
10 public function adminAction()
11 {
Database Management with Doctrine ORM 415
Finally, add the corresponding view template file admin.phtml to the application/post directory
under module’s view directory:
1 <h1>Manage Posts</h1>
2
3 <p>
4 <a class="btn btn-default" href="
5 <?php echo $this->url('application/default',
6 array('controller'=>'post', 'action'=>'add')); ?>">
7 New Post
8 </a>
9 </p>
10
11 <table class="table table-striped">
12
13 <tr>
14 <th>ID</th>
15 <th>Post Title</th>
16 <th>Date Created</th>
17 <th>Status</th>
18 <th>Actions</th>
19 </tr>
20
21 <?php foreach ($posts as $post): ?>
22
23 <tr>
24 <td><?php echo $post->getId(); ?></td>
25 <td>
Database Management with Doctrine ORM 416
• to create the PostRepository custom entity repository class that would encapsulate the
logic of filtering posts by tag;
Database Management with Doctrine ORM 418
• to modify the PostManager and add functionality for calculating font sizes for the tag
cloud;
• to add controller’s action and corresponding view template.
It is also possible to put the DQL queries to controller class, but that would make
controllers “fat”. Since we use MVC pattern, we strive to avoid that.
DQL is similar to SQL in sense that it allows to write and execute queries to database,
but result of a query is an array of objects rather than an array of table rows. For more
information on DQL and its usage examples, please refer to this page¹¹.
For our Blog sample web application, we need a custom repository which allows to find
published posts having at least one tag (to calculate total count of tagged posts), and, to find
published posts filtered by particular tag. We plan to encapsulate this search logic into the custom
PostRepository repository.
Doctrine works with custom repositories transparently. This means, that you retrieve
the repository from EntityManager as usual and still can use its findBy(), findOneBy()
and other methods.
Below, you can find the code of PostRepository class that has two public methods:
• the findPostsHavingAnyTag() method is designed to select all posts that have status
Published and have one or more tags assigned;
• and the findPostsByTag() method is designed to return all published posts that have the
particular tag assigned (to filter posts by the given tag).
¹¹http://docs.doctrine-project.org/en/latest/reference/dql-doctrine-query-language.html
Database Management with Doctrine ORM 419
1 <?php
2 namespace Application\Repository;
3
4 use Doctrine\ORM\EntityRepository;
5 use Application\Entity\Post;
6
7 // This is the custom repository class for Post entity.
8 class PostRepository extends EntityRepository
9 {
10 // Finds all published posts having any tag.
11 public function findPostsHavingAnyTag()
12 {
13 $entityManager = $this->getEntityManager();
14
15 $dql = "SELECT p FROM \Application\Entity\Post p JOIN p.tags t WHERE p.st\
16 atus=".Post::STATUS_PUBLISHED." ORDER BY p.dateCreated DESC";
17 $query = $entityManager->createQuery($dql);
18 $posts = $query->getResult();
19
20 return $posts;
21 }
22
23 // Finds all published posts having the given tag.
24 public function findPostsByTag($tagName)
25 {
26 $entityManager = $this->getEntityManager();
27
28 $dql = "SELECT p FROM \Application\Entity\Post p JOIN p.tags t WHERE p.st\
29 atus=".Post::STATUS_PUBLISHED." AND t.name='".$tagName."' ORDER BY p.dateCrea\
30 ted DESC";
31 $query = $entityManager->createQuery($dql);
32 $posts = $query->getResult();
33
34 return $posts;
35 }
36 }
To let Doctrine know that it should use the custom repository for Post entity, modify the Post
entity’s annotation as follows:
Database Management with Doctrine ORM 420
1 <?php
2 //...
3
4 /**
5 * This class represents a single post in a blog.
6 * @ORM\Entity(repositoryClass="\Application\Repository\PostRepository")
7 * @ORM\Table(name="post")
8 */
9 class Post
10 {
11 //...
12 }
Above, in line 6, we use the repositoryClass parameter of the @ORM\Entity tag to tell Doctrine
that it should use PostRepository repository.
1 <?php
2 //...
3 class PostManager implements ServiceManagerAwareInterface
4 {
5 //...
6
7 // Calculates frequencies of tag usage.
8 public function getTagCloud()
9 {
10 $tagCloud = array();
11
12 $entityManager = $this->getServiceLocator()
13 ->get('doctrine.entitymanager.orm_default');
14
15 $posts = $entityManager->getRepository('\Application\Entity\Post')
16 ->findPostsHavingAnyTag();
17 $totalPostCount = count($posts);
18
19 $tags = $entityManager->getRepository('\Application\Entity\Tag')
20 ->findAll();
21 foreach ($tags as $tag) {
22
23 $postsByTag = $entityManager->getRepository('\Application\Entity\Post')
24 ->findPostsByTag($tag->getName());
Database Management with Doctrine ORM 421
25
26 $postCount = count($postsByTag);
27 if ($postCount > 0) {
28 $tagCloud[$tag->getName()] = $postCount;
29 }
30 }
31
32 $normalizedTagCloud = array();
33
34 // Normalize
35 foreach ($tagCloud as $name=>$postCount) {
36 $normalizedTagCloud[$name] = $postCount/$totalPostCount;
37 }
38
39 return $normalizedTagCloud;
40 }
41 }
1 <?php
2 //...
3 class IndexController extends AbstractActionController
4 {
5 public function indexAction()
6 {
7 $tagFilter = $this->params()->fromQuery('tag', null);
8
9 // Get Doctrine entity manager
10 $entityManager = $this->getServiceLocator()
11 ->get('doctrine.entitymanager.orm_default');
12
13 if ($tagFilter) {
14
15 // Filter posts by tag
16 $posts = $entityManager->getRepository('\Application\Entity\Post')
17 ->findPostsByTag($tagFilter);
18
19 } else {
20 // Get recent posts
21 $posts = $entityManager->getRepository('\Application\Entity\Post')
22 ->findBy(array('status'=>Post::STATUS_PUBLISHED),
23 array('dateCreated'=>'DESC'));
Database Management with Doctrine ORM 422
24 }
25
26 // Get post manager service.
27 $postManager = $this->getServiceLocator()->get('post_manager');
28
29 // Get popular tags.
30 $tagCloud = $postManager->getTagCloud();
31
32 // Render the view template.
33 return new ViewModel(array(
34 'posts' => $posts,
35 'postManager' => $postManager,
36 'tagCloud' => $tagCloud
37 ));
38 }
39 }
The action method will retrieve the tag from the GET variable tag if the variable doesn’t present
in HTTP request, all posts are retrieved as usual. If the variable present, we use our custom
repository’s findPostsByTag() method to filter posts.
In line 15, we call the PostManager::getTagCloud() that returns array of tags and their
frequencies. We use this information for rendering the cloud.
1 <?php
2 $this->headTitle('Posts');
3
4 $this->mainMenu()->setActiveItemId('home');
5
6 $this->pageBreadcrumbs()->setItems(array(
7 'Home'=>$this->url('home')
8 ));
9 ?>
10
11 <h1>Posts</h1>
12
13 <div class="row">
14
15 <div class="col-md-8">
16
17 <?php foreach($posts as $post): ?>
18
Database Management with Doctrine ORM 423
19 <h3>
20 <a href="<?php echo $this->url('application/default',
21 array('controller'=>'post', 'action'=>'view',
22 'id'=>$post->getId())); ?>">
23 <?php echo $post->getTitle(); ?>
24 </a>
25 </h3>
26
27 <p>
28 Tags: <?php echo $postManager->convertTagsToString($post); ?>
29 </p>
30
31 <p class="comments-header">
32 <?php echo $postManager->getCommentCountStr($post); ?> |
33 <a href="<?php echo $this->url('application/default',
34 array('controller'=>'post', 'action'=>'view',
35 'id'=>$post->getId()), array('fragment'=>'comment')); ?>">
36 Add new comment
37 </a>
38 </p>
39
40 <p>
41 <?php echo $post->getContent(); ?>
42 </p>
43
44 <?php endforeach; ?>
45
46 </div>
47
48 <div class="col-md-4">
49 <div class="panel panel-default">
50 <div class="panel-heading">
51 <h3 class="panel-title">Popular Tags</h3>
52 </div>
53 <div class="panel-body">
54 <?php foreach($this->tagCloud as $tagName=>$frequency): ?>
55
56 <a href="<?php echo $this->url('application/default',
57 array('controller'=>'index', 'action'=>'index'),
58 array('query'=>array('tag'=>$tagName))); ?>">
59
60 <span style="font-size:<?php echo $frequency*3 ?>em">
61 <?php echo $tagName; ?>
62 </span>
63 </a>
64
Database Management with Doctrine ORM 424
This chapter is currently being developed and is not yet complete. You will get all
updates for free as they appear. If you see some mistake, please contact the author
using the following E-mail address: olegkrivtsov@gmail.com. The author will be more
than happy to answer you and fix the mistake.
12.15 Summary
Doctrine is not part of Zend Framework 2, and we cover its usage in this book, because it provides
an easy way of accessing a database.
In this chapter, we’ve considered the usage of the Object Relational Mapper (ORM) component
of Doctrine library. The ORM is designed for database management in object-oriented style.
With ORM, you map a database table to a PHP class called entity, and columns of that table are
mapped to the properties of the class.
To load data from the database, you retrieve an entity from its repository. The repository is a class
that can be considered as a collection of all available entities. When you request the repository
for an entity, it connects to the database, loads the data from the table mapped to the entity, and
assigns entity’s fields with the data.
Appendix A. Configuring Web
Development Environment
Here we will provide instructions on how to prepare your environment for developing ZF2-based
applications. If you already have the configured environment, you can skip this.
Please also consider browsing the tutorials¹² online. Those tutorials provide step-by-
step instructions on how to install a ZF2-based web application to Amazon EC2 cloud
and to PHPCloud service.
Configuring the development environment is the first thing you have to do when beginning with
creating your first web-site. This includes installing a web server, the PHP engine with required
extensions and a database.
For the purpose of running the code examples created in this book, we will use Apache HTTP
Server (v.2.2 or later), PHP engine (v.5.3 or later) with XDebug extension and MySQL database
(v.5.5 or later).
We also provide instructions for installing NetBeans IDE, which is a convenient integrated
development environment for PHP development. It allows for an easier navigation, editing and
debugging of your PHP application. The NetBeans IDE is written in Java and can be installed in
Windows, Linux and other platforms supporting a compatible Java machine.
software into their repository, so if you want to install one, you will need to download it from
somewhere, read the manual, and possibly (if you are not lucky) compile it yourself.
There is another Linux distribution, which, in the author’s opinion, suits better for PHP
development. Its name is Linux Ubuntu. Ubuntu is being developed by Canonical Ltd. Linux
Ubuntu has two editions: Desktop edition and Server edition. Ubuntu Desktop is a distribution
containing graphics environment, while Ubuntu Server edition has console terminal only. For
the purpose of PHP development, you will need Desktop edition.
Canonical typically releases a new version of Linux Ubuntu each 6 months, in April and October,
and a “long term support” (LTS) version each 2 years. For example, at the moment of writing
this text, the latest version is Ubuntu 14.04 Trusty Tahr LTS (released in April 2014).
Non-LTS releases have short support period (about 9 months), but they have the newest versions
of the PHP software out of the box. On the other hand, LTS releases have longer support period
(5 years), but a little outdated PHP software out of the box. For PHP development, the author
would recommend to use the latest non-LTS version of Ubuntu Desktop, because it has the newest
version of PHP and other software available from repository. The disadvantage of using such a
version is that you will need to upgrade it to the next release every 9 months (as support period
expires).
For your information, table A.1 lists PHP versions avaiable for installation from repository in
different Linux distributions:
When choosing between 32-bit and 64-bit versions of the system, remember that the 64 bit
version of Linux Ubuntu will have more compatibility issues than its 32-bit counterpart. The
support of drivers can also cause problems on the 64-bit platform.
install packages in Debian-based Linux distributions (e.g. Ubuntu Linux), you use Advanced
Packaging Tool (APT). In Red Hat provided distributions (e.g. Fedora or CentOS), you use YUM
(RPM package manager). Below, detailed installation instructions for these operating systems are
provided.
Debian or Linux Ubuntu
First of all, it is recommended that you update your system by installing the latest available
updates. To do this, from a command shell, run the following commands:
sudo apt-get update
sudo apt-get upgrade
The commands above runs the APT tool and install the newest system packages updates. The
sudo command (stands for “Super User DO”) allows to run another command, apt-get in our
case, as system administrator (root). You typically use sudo when you need to elevate your
privileges to install a package or edit some configuration file.
The sudo command may request you for password. When prompted, enter the pass-
word under which you log into the system and press Enter.
The commands above may ask you for confirmation when installing a package. It is
recommended to answer Yes (press “y” and then press Enter).
To be able to navigate the directory structure and edit files, it is recommended to install
Midnight Commander (convenient file manager and text editor). To install Midnight
Commander in Debian or Linux Ubuntu, type the following:
sudo apt-get install mc
The following command installs Midnight Commander in Fedora, CentOS or Red Hat
Linux:
sudo yum install mc
After installation, you can edit a text file with the command like this:
mcedit /path/to/file
If you need administrative permissions to edit the file, prepend the sudo command to
the command above.
<?php
phpinfo();
?>
Open the file in your web browser. The standard PHP information page should display (see figure
A.1 for example).
Type the following to edit php.ini in Fedora, CentOS or Red Hat Linux:
sudo mcedit /etc/php.ini
Appendix A. Configuring Web Development Environment 429
For the development environment, it is recommended to set the following error handling and
logging parameters as below. This will force PHP to display errors on your PHP pages to screen.
error_reporting = E_ALL
display_errors = On
display_startup_errors = On
Set your time zone settings (replace <your_time_zone> placeholder with your time zone, for
example, UTC or America/New_York):
date.timezone = <your_time_zone>
post_max_size=128M
upload_max_filesize=128M
When ready, save your changes by pressing the F2 key and then press F10 to exit from Midnight
Commander’s editor.
The command above creates a symbolic link in the mods-enabled directory, this way you enable
modules in modern versions of Apache web server.
Finally, restart Apache web server to apply your changes.
Please note that right now you don’t need to create a virtual host, we’ll do that in
Chapter 2 when installing the Zend Skeleton Application. Now you just need to have
an idea of how virtual hosts are created in different Linux distributions.
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
allow from all
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
</VirtualHost>
All you have to do is just to edit this virtual host file when needed and restart Apache to apply
changes.
You can also copy this file and create another virtual host, when you need several web sites to
operate on the same machine. For example, to create another virtual host file named vhost2 ,
type the following from your command shell:
cd /etc/apache2/sites-available
Appendix A. Configuring Web Development Environment 432
cd ../sites-enabled
The virtual host’s name starts with a prefix (like 000, 010, etc.), which defines the
priority. Apache web server tries to direct an HTTP request to each virtual host in
turn (first to 000-default, then to 010-vhost2), and if a certain virtual host cannot serve
the request, the next one is tried and so on.
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
xdebug.profiler_enable = 1
Finally, restart the Apache server to apply your changes. Then open the phpinfo.php in your
browser and look for XDebug section (it should look like in the figure A.2):
In Fedora, CentOS or Red Hat Linux
In these Linux distributions, installing XDebug is a little more difficult. Install XDebug package
with the following command:
yum install php-pecl-xdebug
Appendix A. Configuring Web Development Environment 433
[xdebug]
xdebug.remote_enable=1
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.remote_host=127.0.0.1
xdebug.remote_port=9000
xdebug.profiler_enable = 1
Restart the Apache web server to apply your changes. Then check the phpinfo.php in your
browser. If installation was successfull, you’ll see some XDebug-related information.
Appendix A. Configuring Web Development Environment 434
The commands above install MySQL server component, MySQL client component and MySQL
extension module for PHP, respectively.
Fedora, CentOS or Red Hat Linux
In order to install MySQL database, type the following:
sudo yum install mysql-server
The commands above install MySQL server component, MySQL client component and MySQL
extension module for PHP, respectively.
Run the following commands to add MySQL server to autostart and start the server:
sudo chkconfig --level 235 mysqld on
¹³http://www.mysql.com/
Appendix A. Configuring Web Development Environment 435
The MySQL command prompt will appear. In the command prompt enter the following
command and press Enter (in the command below, replace the <your_password> placeholder
with some password):
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('<your_password>');
Now we need to create a new database schema that will store the tables. To do this, type the
following:
CREATE SCHEMA test_db;
The command above creates empty schema that we will populate later.
Next, we want to create another database user named test_user that will be used by the ZF2-
based web site for connecting to the database. To create the user, type the following (in the
command below, replace the <your_password> placeholder with some password):
GRANT ALL PRIVILEGES ON test_db.* TO 'test_user'@'localhost' IDENTIFIED BY '<your_-
password>';
The command above creates the user named ‘test_user’ and grants the user all privileges on the
‘test_db’ database schema.
Finally, type quit to exit the MySQL prompt.
We need to download the binary installer for Windows, so on the page that appears, click binaries
link (see the figure A.4).
On the next page click win32 link. In this page, look for httpd-2.2.22-win32-x86-openssl-0.9.8t.msi
download link and click it to download the MSI installer (see the figure A.5).
Appendix A. Configuring Web Development Environment 437
Run the installer and follow the instructions of the wizard. If everything is OK, you should see
the Apache icon in the system tray (see the figure A.6).
Next, download the current stable version (version 5.5 recommended) of PHP from PHP web
site¹⁵. Click the Windows 5.5.0 binaries and source link and download VC11 x86 Thread Safe ZIP
archive. Then unpack the archive to C:\Program Files (x86)\PHP.
In order to enable PHP engine, you have to edit the web server’s configuration file (httpd.conf ).
Typically, the Apache config file is located in C:Program Files (x86)Apache Software Founda-
tionApache2.2\conf folder. You need to add the following lines to your Apache httpd.conf
configuration file to load the PHP-Module for Apache 2.2:
After editing the config file, restart Apache. You can do this by clicking the Apache tray icon
and choosing Restart from the context menu.
¹⁵http://www.php.net/downloads.php
Appendix A. Configuring Web Development Environment 438
Typically, the Apache document root directory is C:Program Files (x86)Apache Software
FoundationApache2.2\htdocs.
<?php
phpinfo();
?>
Open the file in your browser. The standard PHP information page should display (figure A.8).
and remove the hash (#) sign from the beginning to uncomment the line. It should now look like
this:
Appendix A. Configuring Web Development Environment 440
#Virtual hosts
#Include conf/extra/httpd-vhosts.conf
#Virtual hosts
Include conf/extra/httpd-vhosts.conf
The line you uncommented forces Apache to include the additional config file named httpd-
vhosts.conf. Open C:Program FilesApache Software FoundationApache2.2\conf\extra\httpd-vhosts.conf
in a text editor. This file contains an example virtual host configuration (see below).
#
# Use name-based virtual hosting.
#
NameVirtualHost *:80
#
# VirtualHost example:
# Almost any Apache directive may go into a VirtualHost
# container. The first VirtualHost section is used for
# all requests that do not match a ServerName or ServerAlias
# in any <VirtualHost> block.
#
<VirtualHost *:80>
ServerAdmin webmaster@dummy-host.localhost
DocumentRoot "C:/Program Files (x86)/Apache Software Foundation\
/Apache2.2/htdocs"
ServerName dummy-host.localhost
ServerAlias www.dummy-host.localhost
ErrorLog "logs/dummy-host.localhost-error.log"
CustomLog "logs/dummy-host.localhost-access.log" common
</VirtualHost>
Appendix A. Configuring Web Development Environment 441
You can edit it as needed and restart Apache to apply the changes.
Right now, you don’t need to edit this file, we’ll do that in Chapter 2 when installing
the Hello World application. Now you just need to understand how to create virtual
hosts.
xdebug.remote_enable=on
xdebug.remote_handler=dbgp
xdebug.remote_host=localhost
xdebug.remote_port=9000
xdebug.profiler_enable = 1
Finally, restart the Apache server to apply your changes. Then open the phpinfo.php in your
browser and look for XDebug section (it should look like in the figure A.9):
¹⁶http://www.xdebug.org/download.php
Appendix A. Configuring Web Development Environment 442
If you prefer to administer MySQL server using a graphical tool, you can download
and install MySQL Workbench (GUI Tool) from MySQL download page.
Now we need to create a new database schema that will store the tables. To do this, type the
following in the MySQL client window:
CREATE SCHEMA test_db;
The command above creates an empty database schema that we will populate later. If the
command is executed successfully, the following message is displayed:
Query OK, 1 rows affected (0.05 sec)
Next, we want to create another database user named test_user that will be used by the web
site for connecting to the database. To create the user, type the following (in the command below,
replace the <your_password> placeholder with some password):
GRANT ALL PRIVILEGES ON test_db.* TO 'test_user'@'localhost' IDENTIFIED BY '<your_-
password>';
The command above creates the user named test_user and grants the user all privileges on the
test_db database schema.
If you found the installation procedure described above to be boring, you might want
to use an integrated solution (stack) for installing Apache HTTP Server, PHP engine
and MySQL database server using a single click. For an example of such an integrated
solution, visit the WAMP Server¹⁸ page.
¹⁸http://www.wampserver.com/en/
Appendix A. Configuring Web Development Environment 444
The command above downloads from repository and installs NetBeans and all its dependent
packages. After the installation is complete, you can run netbeans by typing:
netbeans
To be able to create PHP projects, you need to activate PHP plugin for NetBeans. To do that,
open menu Tools->Plugins, the Plugins dialog appears. In the appeared dialog, click Settings
tab and set check marks to all Configuration and Update Centers (see the figure A.12).
Then click the Available Plugins tab. On that tab, click the Reload Catalog button to build the
list of all available plugins. Then in the list, set check mark to PHP plugin and click the Install
button (see the figure A.13).
When the PHP plugin installation is complete, restart the IDE. Then you should be able to create
new PHP projects from menu New->New Project....
It is also recommended to update NetBeans IDE to the latest version by opening menu
Help->Check for updates.
To install Java 7 (the latest stable version at the moment), run following command:
sudo apt-get install oracle-java7-installer
To ensure that Java 7 has been installed successfully, run the following command from your
command shell:
java -version
Next, download NetBeans installer from the NetBeans site¹⁹ (figure A.14). You may encounter
several NetBeans bundles available for download. Download the distribution that is intended for
PHP development.
¹⁹https://netbeans.org/
Appendix A. Configuring Web Development Environment 446
When download has been finished, go to your Downloads directory and run the installer by
typing the commands below:
cd ∼/Downloads
./netbeans-7.3.1-php-linux.sh
Please note, that the installer’s file name may be different, depending on your version
of NetBeans.
When the installation is completed, you can run NetBeans by typing netbeans in the command
line. You should be able to see NetBeans splash screen, and then the IDE main window should
appear.
Appendix A. Configuring Web Development Environment 448
If your machine does not contain Java Runtime Environment (JRE), you should
download and install it before NetBeans installation. Java JRE can be downloaded from
Oracle site²¹.
Summary
In this appendix, we’ve provided instructions on how to install and configure Apache HTTP
Server, PHP engine and MySQL database in both Linux and Windows platforms.
We’ve also provided instructions for installing NetBeans integrated development environment
(IDE), which is a convenient integrated development environment for PHP development. It
allows you to navigate, edit and debug your ZF2-based application in an effective manner.
²⁰https://netbeans.org/
²¹http://www.oracle.com/technetwork/java/javase/downloads/index.html
Appendix A. Configuring Web Development Environment 449
²²https://groups.google.com/forum/#!forum/using-zend-framework-2-book
Appendix B. Introduction to PHP
Development in NetBeans IDE
In this book, we use NetBeans IDE for developing Zend Framework 2 based applications. In
Appendix A we have installed NetBeans. Here we will provide some useful tips on using
NetBeans for PHP programming. We will learn how to launch and interactively debug a ZF2-
based web site.
What if I want to use another IDE (not NetBeans) for developing my applications?
Well, you can use any IDE you want. The problem is that it is impossible to cover
all IDEs for PHP development in this book. The author only provides instructions
for NetBeans IDE. It would be easier for a beginner to use NetBeans IDE. Advanced
developers may use an IDE of their choices.
Run Configuration
To be able to run and debug a web site, you first need to edit the site’s properties. To do that,
in NetBeans’ Projects pane, right-click on the project’s name, and from context menu select
Properties item. The project’s Properties dialog will appear (shown in figure B.1).
Appendix B. Introduction to PHP Development in NetBeans IDE 451
In the left pane of the dialog that appears, click the Sources node. In the right pane, edit the Web
Root field to point to your web site’s APP_DIR/public directory. You can do this by clicking the
Browse button to the right of the field. Then, in the dialog, click on the public directory and then
click Select Folder button (shown in figure B.2).
Next, click the Run Configuration node in the left pane. The right pane should display the run
settings for your site (figure B.3).
Appendix B. Introduction to PHP Development in NetBeans IDE 452
In the right pane, you can see that the current configuration is “default”. As an option, you can
create several run configurations.
Edit the fields as follows:
• In the Run As field, select “Local Website (running on local web server)”.
• In the Project URL field, enter “http://localhost”. If you configured your virtual host to listen
on different port (say, on port 8080), enter the port number like this “http://localhost:8080”.
• Keep the Index File field empty, because Apache’s mod_rewrite module will mask our
actual index.php file.
• In the Arguments field, you can specify which GET parameters to pass to your site through
the URL string. Typically, you keep this field empty.
If everything is OK with your run configuration, the default web browser will be launched, and
in the browser window, you will be able to see the Home page of the web site.
The same effect would have typing “http://localhost/” in your browser, but NetBeans’ run toolbar
allows you to do that in a single click.
exit;
The lines above would print the value of the $var variable to the browser’s output and then
stop program execution. While this can be used for debugging even a complex site, it makes the
debugging a pain, because you have to type variable dump commands in your PHP source file,
then refresh your web page in the browser to see the output, then edit the source file again, until
you determine the reason of the problem.
In contrast, when you debug your web site in NetBeans IDE, the PHP interpreter pauses program
execution flow at every line where you set a breakpoint. This makes it possible to conveniently
retrieve information about the current state of the program, like the values of the local variables
and the call stack. You see the debugging information in NetBeans window in graphical form.
To be able to debug the site, you need to have the XDebug extension installed. If you
haven’t installed it yet, please refer to Appendix A for additional information on how
to install it.
To start the debugging session, in NetBeans window, click the Debug button on the Run Toolbar
(figure B.4). Alternatively, you can press the CTRL+F5 key combination on your keyboard.
If everything is OK, you should be able to see the current program counter on the first code line
of the index.php file (shown in figure B.5):
Appendix B. Introduction to PHP Development in NetBeans IDE 454
While the program is in paused state, your web browser’s window is frozen, because the browser
is waiting for data from the web server. Once you continue program execution, the browser
receives the data, and displays the web page.
Debug Toolbar
You can resume/suspend program execution with the Debug Toolbar (see figure B.6):
The Finish Debugger Session of the toolbar allows to stop the debugger. Press this button when
you’re done with debugging the program. The same effect would have pressing the SHIFT+F5
key combination.
Clicking the Continue button (or pressing F5 key) continues the program execution until the next
breakpoint, or until the end of the program, if there are no more breakpoints.
Appendix B. Introduction to PHP Development in NetBeans IDE 455
The Step Over toolbar button (or pressing F8 key) moves the current program counter to the next
line of the program.
The Step Into toolbar button (or pressing F7 key) moves the current program counter to the next
line of the program, and if it is the function entry point, enters the function body. Use this when
you want to investigate your code in-depth.
The Step Out toolbar button (CTRL+F7) allows to continue program execution until returning
from the current function.
And Run to Cursor (F4) allows to continue program execution until the line of code where you
place the cursor. This may be convenient if you want to skip some code block and pause at a
certain line of your program.
Breakpoints
Typically, you set one or several breakpoints to the lines which you want to debug in step-by-
step mode. To set a breakpoint, put your mouse to the left of the line of code where you want the
breakpoint to appear and click on the line number. Alternatively, you can put the cursor caret to
the line where you want to set a breakpoint and press CTRL+F8 key combination.
When you set the breakpoint, the line is marked with red color and a small red rectangle appears
to the left of it (shown in figure B.7):
You can travel between breakpoints with the F5 key press. This button continues program
execution until it encounters the next breakpoint. Once the program flow comes to the
breakpoint, the PHP interpreted becomes paused, and you can review the state of the program.
You can see the complete list of breakpoints you have set in the Breakpoints window (see figure
B.9). The Breakpoints window is located in the bottom part of NetBeans window. In this window
you can add new breakpoints or unset breakpoints that have already been set.
Watching Variables
When the PHP interpreter is paused, you can conveniently watch the values of PHP variables.
A simple way to browse a variable is just positioning the mouse cursor over the variable name
inside of the code and waiting for a second. If the variable value can be evaluated, it will be
displayed inside of a small pop up window.
Another way to watch variables is through the Variables window (shown in figure B.10), which
is displayed in the bottom part of NetBeans window. The Variables window has three columns:
Name, Type and Value.
Appendix B. Introduction to PHP Development in NetBeans IDE 457
Mainly, you will be faced with three kinds of variables: super globals, locals and $this:
• Super global variables are special PHP variables like $_GET, $_POST, $_SERVER, $_COOKIES
and so on. They typically contain server information and parameters passed by the web
browser as part of HTTP request.
• Local variables are variables living in the scope of the current function (or class method).
For example, in the Hello World application, if you place a breakpoint inside of the
IndexController::aboutAction(), the variable $zendFrameworkVer will be a local vari-
able.
• $this variable points to the current class instance, if the current code is being executed in
context of a PHP class.
Some variables can be “expanded” (to expand a variable, you need to click on a triangle icon
to the left of variable’s name). For example, by clicking and expanding $this variable, you can
watch all fields of the class instance. If you expand an array variable, you will be able to watch
array items.
Using the Variables window it is possible not only to watch variable’s value, but also to change
the value on the fly. To do that, place your mouse cursor over the value column and click over
it. The edit box appears, where you can set the new value of the variable.
Call Stack
The call stack displays the list of nested functions whose code is being executed at the moment
(shown in the figure B.11). Each line of the call stack (also called a stack frame) contains the full
name of the class, the name of the method within the class and line number. Moving down the
stack, you can better understand the current execution state of the program.
Appendix B. Introduction to PHP Development in NetBeans IDE 458
For example, in figure B.11, you can see that currently the IndexController::aboutAction() is
being executed, and this method was in turn called by the AbstractActionController::onDispatch()
method, and so on. We can walk the call stack until we reach the index.php file, which is the top
of the stack. You can also click a stack frame to see the place of the code that is currently being
executed.
Debugging Options
NetBeans allows you to configure some aspects of the debugger’s behavior. To open the Options
dialog, select menu Tools->Options. In the dialog that appears, click the PHP tab, and in that tab,
select Debugging subtab (figure B.12).
Appendix B. Introduction to PHP Development in NetBeans IDE 459
You typically do not change most of these options, you just need to have an idea of what they
do. These are the following debugging options:
• The Debugger Port and Session ID parameters define how NetBeans connects to XDebug.
By default, the port number is 9000. The port number should be the same as the debugger
port you set in php.ini file when installing XDebug. The session name is by default
“netbeans-xdebug”. You typically do not change this value.
• The Stop at First Line parameter makes the debugger to stop at the first line of your
index.php file, instead of stopping at the first breakpoint. This may be annoying, so you
may want to uncheck this option.
• The Watches and Balloon Evaluation option group is disabled by default, because these
may cause XDebug fault. You can enable these options only when you know what you are
doing.
– The Maximum Depth of Structures parameter sets whether nested structures (like
nested arrays, objects in objects, etc.) will be visible or not. By default, the depth is
set to 3.
– The Maximum Number of Children option defines how many array items to display
in Variables window. If you set this to, say 30, you will see only the first 30 items
even when the array has more than 30 items.
• The Show Requested URLs option, when enabled, displays the URL which is currently
being processed. It prints the URL to an Output window.
Appendix B. Introduction to PHP Development in NetBeans IDE 460
• The Debugger Console option allows to see the output of PHP scripts being debugged. The
output is shown in the Output window. If you plan to use this feature, it is recommended to
add output_buffering = Off parameter in [xdebug] section of your php.ini file, otherwise
the output may appear with delays.
Profiling
When your site is ready and working, you are typically interested in making it as fast and
performing as possible. XDebug provides you with an ability to profile your web site. Profiling
means determining which class methods (or functions) spend what time to execute. This allows
you to determine the “bottle neck” places in your code and address the performance issues.
For each HTTP request, the XDebug extension measures the amount of time a function
executes, and writes the profiling information to a file. Typically, the profiling info files are
placed into the system temporary directory (in Linux, to /tmp directory) and have names like
xdebug.out.<timestamp>, where the <timestamp> placeholder is the timestamp of the HTTP
request. All you have to do is to open a profiling file and analyze it.
To enable XDebug profiler, you should set the following XDebug configuration
parameter in your php.ini file:
xdebug.profiler_enable = 1
Unfortunately, NetBeans for PHP does not have an embedded tool for visualizing the profiling
results. That’s why you need to install a third-party visualizer tool. Below, we will provide
instructions on how to install a simple web-based tool named Webgrind²³. Webgrind can work
on any platform, because this tool itself is written in PHP.
Webgrind’s installation is very straightforward.
First, you need to download webgrind from its project page and unpack it to some folder. In
Linux, you can do this with the following commands:
cd ∼
wget https://webgrind.googlecode.com/files/webgrind-release-1.0.zip
unzip webgrind-release-1.0.zip
The commands above will change your working directory to your home directory, then will
download the Webgrind archive from the Internet, and then unpack the archive.
Next, you need to tell the Apache web server where to find Webgrind files. This means you need
to configure a separate virtual host. We have already learned about virtual hosts in Appendix A.
Do not forget to restart Apache web server after you have configured the virtual host.
Finally, open Webgrind in your browser by navigating to the URL of your Webgrind install. For
example, if you configured the virtual host to listen on port 8080, enter “http://localhost:8080”
²³https://code.google.com/p/webgrind/
Appendix B. Introduction to PHP Development in NetBeans IDE 461
in your browser’s navigation bar and press Enter. The Webgrind web page should appear (see
figure B.13):
At the top of the Webgrind page, you can select the percentage of the “heaviest” function calls
to show (figure B.14). By default, it is set to 90%. Setting this to a lower percentage will hide the
functions called less often.
The drop-down list to the right of percent field allows to select the profiling data file to analyze.
By default, it is set to “Auto (newest)”, which forces Webgrind to use the file with the most recent
timestamp. You may need to select another file, for example, if your web pages use asynchronous
AJAX requests.
The right-most drop-down list allows to set the units which should be used for measuring the
data. Possible options are: percent (default), milliseconds and microseconds.
Appendix B. Introduction to PHP Development in NetBeans IDE 462
When you have selected the percentage, file name and units, click the Update button to let
Webgrind to visualize the data for you (the calculation may take a few seconds). As the
calculation finishes, you should be able to see the table of function calls, sorted in descending
order by function “weight”. The heaviest functions will be displayed at the top.
The table has the following columns:
• The first column (Function), displays the class name followed by method name (in case of
a method call) or function name (in case of a regular function).
• The second column contains the “paragraph” icons, which can be clicked to open the
corresponding PHP source file that function is defined in the web browser.
• Invocation Count column displays the number of times the function was called.
• Total Self Cost column shows the total time it took to execute the built-in PHP code in the
function (excluding the time spent on executing other non-standard functions).
• Total Inclusive Cost column contains the total execution time for the function, including
built-in PHP code and any other user functions called.
• Calls is the “parent” functions or class methods invoking this (child) function;
• Total Call Cost is the total time executing this function, when called from the parent
function;
• Count - number of times the parent calls the child function.
The coloured bar at the top of the page displays the contribution of different function types:
Please note that the profiler creates a new data file in your /tmp directory for each
HTTP request to your web site. This may cause disk space exhaustion, which can
be fixed only by rebooting your system. So, when you’ve finished profiling your
application, it is recommended to disable the profiling by editing the php.ini file,
commenting the xdebug.profiler_enable parameter as follows, and then restarting
the Apache web server.
;xdebug.profiler_enable = 0
Appendix B. Introduction to PHP Development in NetBeans IDE 463
Summary
In this appendix, we’ve learned how to use NetBeans IDE to run the web site and debug it
in interactive step-by-step mode. To be able to run a web site, you first need to edit the site’s
properties (run configuration).
To debug the site, you need to have the XDebug PHP extension installed. When you debug your
web site in NetBeans, the PHP engine pauses program execution at every line where you set a
breakpoint. You see the debugging information (like local variables and call stack) in NetBeans
window in graphical form.
Along with debugging, XDebug extension also provides the ability to profile web sites. With
profiling, you see how much time was spent for execution of a certain function or class method.
This allows you to determine the “bottle necks” and performance issues.
Appendix C. Introduction to Twitter
Bootstrap
Twitter Bootstrap (shortly, Bootstrap) is a popular CSS framework allowing to make your web
site professionally looking and visually appealing, even if you don’t have advanced designer
skills. In this appendix, you can find some introductive information about Bootstrap and its
usage examples.
Let’s look in more details at the files stored inside the APP_DIR/public directory and its
subdirectories (figure C.1).
The css directory contains the CSS stylesheets:
• The bootstrap.css and bootstrap.min.css files are the usual and minified versions of
Bootstrap, respectively.
• The bootstrap-theme.css is the optional Bootstrap theme file for a “visually enhanced
experience”. The bootstrap-theme.min.css is its minified version.
• The style.css file is the stylesheet that can be used and extended by you to define your own
CSS rules which will be applied on top of Bootstrap rules. This way you can customize the
appearance of your web application.
Appendix C. Introduction to Twitter Bootstrap 465
• The bootstrap.js is the file containing the JavaScript code of the Bootstrap extensions. The
bootstrap.min.js file is its minified version.
• Because Bootstrap extensions are implemented as jQuery plugins, they require the latest
version of jQuery library to be present. Thus, the js directory includes the jQuery library
file jquery.min.js. You may also notice the jquery-1.10.2.min.map file, which is the MAP ²⁴
file that can be used for debugging.
The html5shiv.js and respond.min.js files are actually not part of either Bootstrap or
jQuery. They are used for compatibility with older versions of Internet Explorer web
browser. The first one²⁵ enables the use of HTML5 elements and provides basic HTML5
styling, and the latter one²⁶ enables the “responsive” web designs.
²⁴After the concatenation and minification, the JavaScript code is difficult to read and debug. A MAP file (source map) allows to restore the
minified file back to its usual state.
²⁵https://github.com/aFarkas/html5shiv
²⁶https://github.com/scottjehl/Respond
Appendix C. Introduction to Twitter Bootstrap 466
Grid System
In most web sites, content is required to be organized in a table-like structure having rows and
columns. In figure C.2, you can see an example layout of a typical web site: it has the header
block with a logo, the sidebar at the left, page content area in the middle, the ads bar at the right,
and the footer at the bottom of the page. These blocks are arranged in a grid, although grid cells
have unequal width (some cells can span several columns).
Bootstrap provides a simple layout grid system to make it easy to arrange content on your pages
in rows and columns.
Each row consists of up to 12 columns ²⁷ (figure C.3). Column width is flexible and depends on
the width of the grid container element. Column height may vary depending on the height of
the content of the cell. The space between columns is 30 pixels (15 pixels padding at both sides
of the column).
Columns can be spanned, so a single cell takes the space of several columns. For example, in
figure C.3, the upper grid row consists of 12 columns, and each cell spans a single column. In the
bottom row, the first cell spans 2 columns, the second and the third ones span 4 columns each,
and the fourth cell spans 2 columns (in total we have 12 columns).
²⁷You are not required to put exactly 12 columns in a row, there may be fewer columns. If you have fewer columns, the space to the right of
the last column will be empty.
Appendix C. Introduction to Twitter Bootstrap 467
<div class="container">
<div class="row">
...
</div>
</div>
To add columns, you use <div> elements with CSS class names varying from .col-md-1 to
.col-md-12. The number in the class name specifies how many columns each grid cell will span:
<div class="container">
<div class="row">
<div class="col-md-1">Cell 1</div>
<div class="col-md-5">Cell 2</div>
<div class="col-md-6">Cell 3</div>
</div>
</div>
In the example above, we have three cells. The first cell has a width of 1 (it uses the .col-md-1
class), the second cell spans 5 grid columns (class .col-md-5) and the third cell spans 6 columns
(class .col-md-6).
As another example, let’s define the layout that we saw in figure C.2. The layout has the header
(logo spans 3 columns), the main content area (spans 7 columns), the sidebar (spans 3 columns),
the advertisements bar (2 columns) and the footer. To produce this layout, we can use the
following HTML code:
<div class="container">
<!-- Header -->
<div class="row">
<div class="col-md-3">Logo</div>
<div class="col-md-9"></div>
</div>
<!-- Body-->
<div class="row">
<div class="col-md-3">Sidebar</div>
<div class="col-md-7">Page Content</div>
<div class="col-md-2">Ads</div>
</div>
<!-- Footer -->
Appendix C. Introduction to Twitter Bootstrap 468
<div class="row">
<div class="col-md-12">Page Footer</div>
</div>
</div>
Offsetting Columns
In real web pages, sometimes the grid needs to contain “empty holes”. You can define such
holes by offsetting cells to the right with the help of CSS classes named from .col-md-offset-1
to .col-md-offset-12. The number in the class name specifies how many columns should be
skipped.
For example, look at figure C.4:
The grid above has three cells, the latter two cells are offsetted to the right, making empty holes.
To define the grid like in figure C.4, you can use the following HTML code:
1 <div class="container">
2 <div class="row">
3 <div class="col-md-2">Cell 1</div>
4 <div class="col-md-4 col-md-offset-2">Cell 2</div>
5 <div class="col-md-2 col-md-offset-2">Cell 3</div>
6 </div>
7 </div>
Nesting Grids
You can create complex page layouts by nesting grids (for example, look at figure C.5). To nest
your content, you add a new <div> element containing .row class, and set of .col-md-* columns
within an existing .col-md-* column.
To produce the grid as shown in figure C.5, you can use the following HTML code:
Appendix C. Introduction to Twitter Bootstrap 469
<div class="container">
<div class="row">
<div class="col-md-2">Cell 1</div>
<div class="col-md-8">
<!-- Nested grid -->
<div class="row">
<div class="col-md-4">Cell 21</div>
<div class="col-md-4">Cell 22</div>
</div>
<div class="row">
<div class="col-md-4">Cell 23</div>
<div class="col-md-4">Cell 24</div>
</div>
</div>
<div class="col-md-2">Cell 3</div>
</div>
</div>
In the example above, we defined the grid consisting of three cells (denoted by gray color): the
first cell spanning 2 columns, the second cell spanning 8 columns and the third cell spanning
2 columns. Then we put the nested grid rows inside of the second cell. Because the parent cell
spans 8 columns, the child grid consists of 8 columns, too.
This is also called the responsiveness, or the “mobile first” concept. Bootstrap v3.0 is
mobile-first, which means your web site will be viewable and usable on any-sized
screen. However, this does not free you of painstaking preparation and planning the
layout.
This adaptation is performed in two ways. The first way is that the column width within the grid
is flexible. For example, if you increase the size of the browser window, the grid will be scaled
accordingly to fill the whole space.
But what will happen if your web page is too wide for the display? To see the hidden part, the site
visitor will need to scroll it to the right. For mobile phones and other low-resolution devices this
is not a good approach. Instead, it would be better for the grid to become “stacked” below some
screen width. When the grid is stacked, its rows are transformed, making cells to be positioned
one below another (see figure C.6 for example).
To better control when the grid becomes “stacked”, Bootstrap provides you with additional
CSS classes: .col-xs-1 to col-xs-12 (the “xs” abbreviation means “extra-small” devices, or
Appendix C. Introduction to Twitter Bootstrap 470
phones), .col-sm-1 to .col-sm-12 (“sm” stands for “small devices”, or tablets), and .col-lg-1
to .col-lg-12 (large devices, or wide displays). These classes can be used together with the
.col-md-1 – .col-md-12 classes, that we already used (the “md” abbreviation means “medium
devices”, or desktops).
For example, .col-md-* classes define the grid which will become “stacked” when the screen
is below 992 pixels wide, and horizontal for wider screens. The .col-sm-* can be used to make
the grid “stacked” below 768 pixel screen width, and horizontal above this point. The .col-xs-*
class makes the grid always horizontal, not depending on the screen width.
Table C.1 provides the summary of available grid classes and their breakdown page width.
Bootstrap’s grid system greatly simplifies the positioning of elements on a web page.
However, using the grid system is not mandatory. For example, sometimes you may
need a much more complex layout, and the simple grid system will be insufficient. In
such a case, you can create and use your custom layout by using <table> or <div>
HTML elements.
Navigation Bar
Navigation bar is usually positioned on top of your web site and contains the links to main pages,
like Home, Download, Support, About, etc. Twitter Bootstrap provides a nice visual style for the
navbar (see figure C.7 for example):
As you can see from the figure above, a navbar typically has the header (brand name of your
site can be placed here) and the links to main pages. To put a navbar on your page, you use the
following HTML code:
In line 1 above, we used the <nav> element, which contains all the navigation bar information.
The associated CSS class .navbar is defined by Bootstrap and provides the base navigation bar’s
appearance. The .navbar-default CSS class specifies the “default” theme for the navigation bar.
The optional role attribute²⁸ is an HTML attribute allowing to annotate the page elements with
machine-extractable semantic information about the purpose of an element. In this example, the
attribute tells that the <nav> element is used for navigation.
In lines 2-4, we define the navbar header area, which contains the Hello World hyperlink.
The brand hyperlink typically points to the main page of your site. The hyperlink has the
.navbar-brand class that visually enhances the text.
In lines 5-10, we specify the navigation links for the Home, Download, Support and About pages.
These links are organized inside an <ul> unordered list element. The element has CSS classes
.nav and .navbar-nav that place list items in line and provide the hover item state.
Dropdown Menu
With Bootstrap navigation bar, it is possible to use the dropdown menu as a navigation item.
For example, if the Support section of your site can be subdivided into Documentation and Help
pages, these can be implemented as a dropdown menu (see figure C.8).
²⁸http://www.w3.org/TR/xhtml-role/
Appendix C. Introduction to Twitter Bootstrap 472
You define the dropdown menu by replacing the Support list item from the previous example in
the following way:
1 <li class="dropdown">
2 <a href="#" class="dropdown-toggle" data-toggle="dropdown">
3 Support <b class="caret"></b>
4 </a>
5 <ul class="dropdown-menu">
6 <li><a href="#">Documentation</a></li>
7 <li><a href="#">Help</a></li>
8 </ul>
9 </li>
In the code above, we use the <li> element with CSS class .dropdown that indicates the dropdown
menu (line 1). In lines 2-4, the <a> element defines the hyperlink to show when the menu is hidden
(the Support text is shown followed by the triangle caret).
When the site user clicks the hyperlink, the dropdown menu (lines 5-8) appears. The <ul>
unordered list element with class .dropdown-menu defines its visual appearance. The dropdown
menu contains two items: the Documentation and Help hyperlinks.
Collapsible Navbar
As with the grid system, the navbar component supports different types of screen resolutions.
On low-resolution devices, the navbar can be collapsed, as shown in figure C.9.
As you can see, in the collapsed mode, only the navbar header is displayed, and the three
horizontal bars at the right denote the Toggle button. Clicking the button would expand the
hidden navbar items.
You define the collapsible navigation bar as shown in the example below:
Appendix C. Introduction to Twitter Bootstrap 473
Above in lines 3-12, we define the navbar header which will be displayed independently on screen
resolution. The header contains the Toggle button with three horizontal bars and description text
“Toggle navigation”.
The collapsible part of the menu can be seen in lines 15-30. In this area, we put our navigation
links and the dropdown menu items.
The navigation bar can be displayed using two standard “themes”: the default theme (we saw it
above), and the inverse theme. The inverse theme makes the navbar elements be displayed in dark
colors (figure C.10). You probably saw such an inverse navbar in the Zend Skeleton Application
demo.
Appendix C. Introduction to Twitter Bootstrap 474
The inverse theme is defined by simply replacing the .navbar-default class of the <nav> element
by the .navbar-inverse class:
Breadcrumbs
Breadcrumbs is a useful interface component which can be used together with the navbar to give
the site visitor an idea of his current location within the site (figure C.11).
In the figure above, we have an example breadcrumbs for the documentation system of our site.
Because the documentation pages can have deep nesting level, the breadcrumbs tell the user
which page he is visiting right now so the user will not get lost and will be able to return to the
page he visited previously, and to the upper-level pages.
To define the breadcrumbs, you use the ordered list <ol> element with the .breadcrumb CSS
class (see an example below):
<ol class="breadcrumb">
<li><a href="#">Home</a></li>
<li><a href="#">Support</a></li>
<li class="active">Documentation</li>
</ol>
Pagination
The pagination component is useful when you have a long list of items for display. Such a long
list, if displayed on a single page, would require the user to scroll the page down several times to
see the bottom of the list. To improve user experience, you would break the output into pages,
and use the pagination component for navigation between the pages (figure C.12):
To define the pagination like in figure above, use the following HTML code:
Appendix C. Introduction to Twitter Bootstrap 475
<ul class="pagination">
<li><a href="#">« Newest</a></li>
<li><a href="#">< Newer</a></li>
<li><a href="#">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li><a href="#">Older ></a></li>
<li><a href="#">Oldest »</a></li>
</ul>
To create the buttons like in the figure above, use the following HTML code:
<p>
<button type="button" class="btn btn-primary">Save</button>
<button type="button" class="btn btn-default">Cancel</button>
</p>
In the code above, we use the .btn CSS class to assign the button its visual style. Additionally,
we use the .btn-primary class for the Save button (which is typically the primary button on a
form), or the btn-default class for a usual non-primary button Cancel.
To better express the meaning of a button, Bootstrap provides you with several additional
button classes: .btn-success (for buttons applying some change on the page), .btn-info (for
informational buttons), .btn-warning (for buttons that may have an undesired effect), and
.btn-danger (for buttons that may lead to irreversible consequences). For an example of using
these button styles, look at the code below:
<p>
<button type="button" class="btn btn-default">Default</button>
<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-success">Success</button>
<button type="button" class="btn btn-info">Info</button>
<button type="button" class="btn btn-warning">Warning</button>
<button type="button" class="btn btn-danger">Danger</button>
</p>
Appendix C. Introduction to Twitter Bootstrap 476
Bootstrap includes 180 icons (called Glyphicons) that you can use together with your buttons,
dropdown menus, navigation links, etc. To add an icon on a button, you can use the code like
the one below:
<p>
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-plus"></span> Create
</button>
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-pencil"></span> Edit
</button>
<button type="button" class="btn btn-default">
<span class="glyphicon glyphicon-remove"></span> Delete
</button>
</p>
In the code above, we defined a simple toolbar containing three buttons: Create, Edit and Delete.
We placed an icon on each button with the help of <span> element. The <span> element should
have two classes: the .glyphicon class is common for all icons; the second class represents the
icon name. In the example above, we used .glyphicon-plus class for the Create button, the
.glyphicon-pencil for Edit button, and .glyphicon-remove for Delete button. The result of
our work is presented in figure C.15.
You can vary button sizes by specifying the .btn-lg class for large buttons, btn-sm for small
buttons, or .btn-xs class for extra-small buttons. For example, in figure C.16, a large Download
button is presented.
To define such a button, you can use the following HTML code:
Appendix C. Introduction to Twitter Bootstrap 477
Customizing Bootstrap
To finish the introduction to Twitter Bootstrap, we will describe about how to modify some
aspects of Bootstrap framework. You can customize the Bootstrap look and feel using the
Customize²⁹ page of the Bootstrap web site (figure C.17).
On the Customize page you can choose which Bootstrap source files to include into the
“concatenated” resulting file bootstrap.css. If you don’t need some functionality, you can exclude
²⁹http://getbootstrap.com/customize/
Appendix C. Introduction to Twitter Bootstrap 478
it from the resulting file, thus reducing the network traffic and page load time. You can also
remove some unused JavaScript code components from the resulting bootstrap.js file.
Additionally, you can choose different CSS parameters like background color, base text color and
font family, and so on. There are more than a hundred customizable parameters available.
CSS customization is possible, because Bootstrap source files are stored in LESS ³⁰ for-
mat, which allows to define variable parameters (like @bodyBackground or @textColor).
Once the parameters are defined, the LESS files are compiled into usual CSS files,
minified and made available for downloading.
When you have finished with tuning parameters, you can scroll the Customize page down
and press the Compile and Download button. As a result, the bootstrap.zip archive will be
downloaded, which contains all the customized Bootstrap files (both usual and minified CSS
and JS files and glyphicons fonts).
Summary
Twitter Bootstrap is a CSS framework developed to make designing your web pages easier. It
provides the default nice-looking style for typography, tables, forms, buttons, images and so on,
so you can create a professionally looking page in a minute.
The grid system provided by the Bootstrap allows to arrange elements on your web page in a
grid with rows and columns. The grid adapts to different screen resolutions, making your page
equally well-readable on mobile phones, tablets, desktops and wide screens.
Twitter Bootstrap also provides useful web interface components like dropdown menus, naviga-
tion bars, breadcrumbs, etc. These components are made interactive by the JavaScript extensions
bundled with the framework.
Bootstrap is shipped with Zend Skeleton Application, so you can start using it out of the box
or, alternatively, you can download the newest version of Bootstrap from the project’s page and
customize it as you wish.
³⁰LESS is a dynamic stylesheet language extending standard CSS with features like variables, mixins (embedding all the properties of a CSS
class into another CSS class), code block nesting, arithmetic operations, and functions.
Appendix D. Introduction to
Doctrine
In this appendix, we provide overview of the Doctrine library, such as its architecture and
components. Since in this book, we concentrate mainly on Doctrine’s Object Relational Mapper
(ORM) component, reading this appendix may give you the bigger picture of other Doctrine
capabilities.
Relational Databases
In a relational database, you have a collection of tables consisting of records. A record may have
one or several columns. A record (or several records) of a table may be linked to a record (or
several records) of another table, thus forming a relation between data.
For example, assume you have a blog web site whose database contains two tables: the post
table and the comment table. The post table would have columns named id, title, content,
author, date_created; and the comment table would have columns named id, author, content,
and date_created. The post table is related to comment table as one-to-many, because one post
has zero or more (many) comments, while a certain comment may belong to a single post only.
Graphically, the above mentioned tables, their columns and relationship are shown in figure D.1
below.
Figure D.1. Relation between tables. Single post has many comments
Appendix D. Introduction to Doctrine 480
On the market, there is a number of major relational databases. Among them: SQLite³¹, MySQL³²,
PostgreSQL³³, Oracle³⁴, Microsoft SQL Server³⁵ etc.
Each database system has its own features specific to that DBMS and which are not part of other
systems. For example:
• SQLite is designed as an embed extension of PHP engine and doesn’t require installation,
however it works well for simple sites only;
• MySQL is a free system which is very simple in installation and administration and good
for using in systems varying from small to middle scale;
• Commercial Oracle DBMS is mainly targeted on large-scale systems and has sophisticated
administrative tools;
• PostgreSQL supports indefinitely large databases and can be considered as an open-source
replacement of Oracle.
Doctrine library is designed to work with all major databases using a unified programming
interface. This programming interface is implemented in two levels:
1. At the lower level, Doctrine provides the unified mechanism for building SQL queries to
any supported relational database and manipulating database schema. This mechanism is
implemented in the Database Access Layer (DBAL) component.
2. At the higher level, the Object Relational Mapper (ORM) component of Doctrine provides
an ability to query and manage database data in object-oriented way, by mapping the
tables to PHP classes. This component also provides its custom database query language
called DQL allowing to build queries in object-oriented style.
Typically, you use the API provided by high-level ORM component. At the same time, you can
easily work with lower-level DBAL component, if you find that more suitable for your particular
needs.
Doctrine is database-agnostic. In theory, when you use Doctrine you are able to abstract
of database type and switch between databases more easily than when you use your
database-dependent solution.
When using a relational database system, you typically use SQL language as a standard way for
accessing database data and managing database schema. However, each DBMS usually has it
own specific SQL language extensions (dialects).
³¹http://www.sqlite.org
³²http://www.mysql.com/
³³http://www.postgresql.org/
³⁴http://www.oracle.com/
³⁵https://www.microsoft.com/en-us/sqlserver/default.aspx
Appendix D. Introduction to Doctrine 481
Doctrine library is designed to work with all major relational database systems that use
SQL language, but it is obvious that it supports only some subset of their functionality
and SQL language capabilities.
Doctrine is built on top of PHP PDO extension (and other database-specific PHP extensions, like
sqlite, mysqli, oci8, etc.). Those extensions provide drivers for all major relational database
systems. You specify which driver to use when configuring a database connection.
If you are not familiar with SQL, a good point for learning its syntax is W3Schools
Tutorials³⁶.
Since the Object Relational Mapper component of Doctrine is designed to work with objects
instead of tables, it provides its own “object-oriented” query language called DQL. It is similar
to SQL in sense that it allows to write and execute queries to database, but result of a query is
an array of objects rather than an array of table rows.
NoSQL Databases
In contrast to a relational database system, a NoSQL database system - as its name assumes -
uses a not-only-SQL method of accessing the data. This means that each NoSQL system may
provide its own custom methods and API for accessing and manipulating the data. Technically,
NoSQL databases can be divided in the following groups:
• Document Store. A document database operates the concept of documents and their
fields. This is useful, for example, if you have an hierarchical document tree in a content
management (CMS) system. Documents are addressed in the database via a unique key
that represents that document. One of the other defining characteristics of a document-
oriented database is that, beyond the simple key-document lookup that you can use to
retrieve a document, the database will offer an API or query language that will allow
retrieval of documents based on their contents.
• Column Store. Frequently used in web indexing. A column-oriented DBMS is a database
management system that stores data tables as sections of columns of data rather than as
rows of data. In comparison, most relational DBMSs store data in rows. This column-
oriented DBMS has advantages for data warehouses, customer relationship management
(CRM) systems, and library card catalogues, and other ad hoc inquiry systems where
aggregates are computed over large numbers of similar data items.
• Key-Value Store. This is the simplest data storage using unique keys for accessing certain
data. Such database systems provide a simple key-value lookup mechanism.
• and others.
Doctrine provides support only to the Document Store subset of the NoSQL database
systems. Column store and key-value store database systems typically have very
specific field of applications, and not covered by Doctrine.
³⁶http://www.w3schools.com/sql/default.asp
Appendix D. Introduction to Doctrine 482
Document Databases
In this book, we do not address the Doctrine-provided API to the NoSQL document
databases. If you want to learn about these capabilities, please refer to the correspond-
ing sections of Doctrine project documentation.
Doctrine Architecture
The Doctrine Project⁴¹ consists of several libraries (components). Each Doctrine component is
distributed as a Composer-installable package and registered in Packagist.org⁴² catalogue. This
is very similar to the way that Zend Framework 2 uses for installing its components.
At the moment of writing this book, the latest version of Doctrine is v.2.4.
Here we will provide you with a brief description of Doctrine library architecture to let you a
general idea of its capabilities.
Doctrine ORM component uses the so called Data Mapper⁴³ pattern. This pattern tells
that a database table can be represented as a PHP entity class. The database in this
pattern is considered as some kind of repository (storage of entities). When you retrieve
an entity from the repository, an SQL query is performed internally, and an instance
of the PHP entity class is constructed and its properties are filled with data. Vice versa,
when you save the entity to repository, the values of its properties are read from the
entity and saved to database table by an SQL query.
Figure D.2. Doctrine components designed for working with relational databases
By analogy with ZF2 components, Doctrine component names consist of two parts: the vendor
name (“Doctrine”) and the component name (e.g. “Common”). Below, you can find the list
of Doctrine components together with their Composer-installable package names and brief
descriptions:
Since Doctrine uses PHP autoloading and PSR-0 standard, classes belonging to certain compo-
nent live in that component’s namespace. For example, the EntityManager class belonging to
Doctrine\ORM component, lives in Doctrine\ORM namespace.
Figure D.3. Doctrine components designed for working with document databases
Summary
In this appendix, we’ve provided the overview of Doctrine library architecture and components.
Doctrine is a large project consisting of multiple components mainly targeted on data persistence.
Appendix D. Introduction to Doctrine 486
On the market, there are two big groups of database management systems: traditional relational
databases and so called NoSQL databases. Although most relational databases use SQL language
for querying and manipulating data, each particular database system has its own specific
features. The same thing can be seen with NoSQL databases, where each system provides its own
custom method for accessing data. Doctrine is designed to work with data in database-agnostic
way by providing sophisticated abstraction layers.
The most useful component of Doctrine, Object Relational Mapper (ORM) is designed to let the
developer an ability to work with data in object oriented way. This is when instead of writing an
SQL query, you load an entity object (or an array of entity objects) from a repository. With this
approach, a database table is mapped to a PHP class (also called an entity), and a record from
that table is mapped to an instance of that entity class.
About the Author
Oleg Krivtsov is a software developer currently living in Tomsk,
Russia. He received a PhD degree in Computer Science from Tomsk
Polytechnic University in 2010. Oleg has been professionally devel-
oping C/C++ and PHP software since 2005. He also taught Digital
Signal Processing in the university. Oleg likes contributing to open-
source and writing programming articles for popular web resources,
like CodeProject. This writing passion has also inspired him to create
this book about Zend Framework 2. He also writes posts to his
personal blog⁴⁴ on a regular basis. You can contact Oleg through
his E-mail address olegkrivtsov@gmail.com.
⁴⁴http://olegkrivtcov.wordpress.com/