Zend Framework PDF

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 506

Using Zend Framework 2

The first book about Zend Framework 2 easy to read


and understand for beginners

Oleg Krivtsov
This book is for sale at http://leanpub.com/using-zend-framework-2

This version was published on 2014-09-05

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.

©2013 - 2014 Oleg Krivtsov


To my father who assembled my first computer and shown me how to write a simple program.
To my mother who shown me how to overcome life’s obstacles and become a winner.
Contents

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

1. Introduction to Zend Framework 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . 1


1.1 What is Zend Framework 2? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 License . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 What Companies Prefer Zend Framework 2? . . . . . . . . . . . . . . . . . . . . 3
1.4 Release Schedule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Distributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.6 User Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.7 Supported Operating Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.8 Server Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.9 Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.10 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.11 Design Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.12 Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.13 ZF2 Service Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
1.14 Differences with Zend Framework 1 . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.14.1 Backwards Compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.14.2 ZFTool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.14.3 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.14.4 Aspect Oriented Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.14.5 Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.14.6 Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
1.14.7 Service Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
CONTENTS

1.15 Competing Frameworks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17


1.16 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

2. Zend Skeleton Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22


2.1 Getting Zend Skeleton Application . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.2 Typical Directory Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
2.3 Installing Dependencies with Composer . . . . . . . . . . . . . . . . . . . . . . . 25
2.4 Apache Virtual Host . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.5 Opening the Web Site in Your Browser . . . . . . . . . . . . . . . . . . . . . . . 29
2.6 Creating NetBeans Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
2.7 Hypertext Access File (.htaccess) . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
2.8 Blocking Access to the Web Site by IP Address . . . . . . . . . . . . . . . . . . . 34
2.9 HTTP Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
2.10 Having Multiple Virtual Hosts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.11 Hosts File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.12 Advanced Composer Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.12.1 Package Names and Versions . . . . . . . . . . . . . . . . . . . . . . . . . 37
2.12.2 Installing and Updating Packages . . . . . . . . . . . . . . . . . . . . . . . 38
2.12.3 Virtual Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
2.12.4 Composer and Version Control Systems . . . . . . . . . . . . . . . . . . . 40
2.13 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

3. Web Site Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42


3.1 PHP Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.2 PHP Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
3.3 PHP Class Autoloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
3.4 PSR-0 Standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.5 HTTP Request and Response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
3.6 Site Entry Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
3.7 Events & Application’s Life Cycle . . . . . . . . . . . . . . . . . . . . . . . . . . 51
3.8 Application Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
3.8.1 Application-Level Config Files . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.8.2 Application-Level Extra Config Files . . . . . . . . . . . . . . . . . . . . . 55
3.8.3 Module-Level Config Files . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
3.8.4 Combining the Configuration Files . . . . . . . . . . . . . . . . . . . . . . 56
3.9 Module Entry Point . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.10 Class Autoloading in Zend Framework 2 . . . . . . . . . . . . . . . . . . . . . . 58
3.10.1 Composer-provided Autoloader . . . . . . . . . . . . . . . . . . . . . . . . 58
3.10.2 Zend\Loader Component . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
3.10.2.1 Standard Autoloader . . . . . . . . . . . . . . . . . . . . . . . . . 60
3.10.2.2 PSR-0 and Src Directory Structure . . . . . . . . . . . . . . . . . . 61
3.10.2.3 Class Map Autoloader . . . . . . . . . . . . . . . . . . . . . . . . 62
3.11 Service Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.11.1 Service Locator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
3.11.2 Canonical Service Names . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
3.11.3 Registering a Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
CONTENTS

3.11.4 Registering Invokable Classes . . . . . . . . . . . . . . . . . . . . . . . . . 67


3.11.5 Registering Factories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
3.11.6 Registering Service Aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
3.11.7 Service Manager Configuration . . . . . . . . . . . . . . . . . . . . . . . . 69
3.12 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

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

5. URL Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107


5.1 URL Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
5.2 Route Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
5.3 Combining Route Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
5.3.1 Simple Route Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.3.2 Tree Route Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.4 Routing Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
5.4.1 Configuration for Simple Routes . . . . . . . . . . . . . . . . . . . . . . . . 112
CONTENTS

5.4.2 Configuration for Nested Routes . . . . . . . . . . . . . . . . . . . . . . . 112


5.4.3 Default Routing Configuration in Zend Skeleton Application . . . . . . . . 113
5.5 Literal Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
5.6 Segment Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
5.7 Regex Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
5.8 Wildcard Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
5.9 Other Route Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
5.10 Extracting Parameters from Route . . . . . . . . . . . . . . . . . . . . . . . . . . 126
5.10.1 Retrieving the RouteMatch and the Router Object . . . . . . . . . . . . . . 127
5.11 Generating URLs from Route . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
5.11.1 Generating URLs in View Templates . . . . . . . . . . . . . . . . . . . . . 128
5.11.1.1 Passing Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . 129
5.11.1.2 Generating Absolute URLs . . . . . . . . . . . . . . . . . . . . . . 130
5.11.1.3 Specifying Query Part . . . . . . . . . . . . . . . . . . . . . . . . 131
5.11.2 Generating URLs in Controllers . . . . . . . . . . . . . . . . . . . . . . . . 131
5.11.3 URL Encoding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
5.12 Writing Own Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
5.12.1 RouteInterface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
5.12.2 Custom Route Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
5.13 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

6. Page Appearance and Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144


6.1 About CSS Stylesheets and Twitter Bootstrap . . . . . . . . . . . . . . . . . . . . 144
6.2 Page Layout in Zend Framework 2 . . . . . . . . . . . . . . . . . . . . . . . . . . 146
6.3 Modifying the Default Page Layout . . . . . . . . . . . . . . . . . . . . . . . . . 150
6.4 Switching between Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
6.4.1 Setting Layout for All Actions of a Controller . . . . . . . . . . . . . . . . 155
6.5 Partial Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
6.6 Placeholder View Helper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
6.7 Adding Scripts to a Web Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
6.7.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
6.8 Adding CSS Stylesheets to a Web Page . . . . . . . . . . . . . . . . . . . . . . . 167
6.8.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
6.9 Writing Own View Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
6.9.1 Menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
6.9.2 Breadcrumbs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
6.10 View Models and Page Composition . . . . . . . . . . . . . . . . . . . . . . . . . 179
6.11 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181

7. Collecting User Input with Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183


7.1 Get the Form Demo Sample from GitHub . . . . . . . . . . . . . . . . . . . . . . 183
7.2 About HTML Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
7.2.1 Fieldsets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
7.2.2 Example: “Contact Us” Form . . . . . . . . . . . . . . . . . . . . . . . . . . 187
7.2.3 GET and POST Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
7.3 Styling HTML Forms with Twitter Bootstrap . . . . . . . . . . . . . . . . . . . . 189
CONTENTS

7.4 Retrieving Form Data in a Controller’s Action . . . . . . . . . . . . . . . . . . . 191


7.5 Forms and Model-View-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . 194
7.5.1 A Typical Form Usage Workflow . . . . . . . . . . . . . . . . . . . . . . . 195
7.6 A Form Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
7.7 Form Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
7.7.1 Adding Elements to a Form Model . . . . . . . . . . . . . . . . . . . . . . 201
7.7.2 Method 1: Passing an Instance of an Element . . . . . . . . . . . . . . . . . 201
7.7.3 Method 2: Using Array Specification . . . . . . . . . . . . . . . . . . . . . 203
7.8 Example: Creating the Contact Form Model . . . . . . . . . . . . . . . . . . . . . 204
7.9 Adding Form Validation Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
7.9.1 Input Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
7.9.2 Adding Inputs to Input Filter . . . . . . . . . . . . . . . . . . . . . . . . . 209
7.9.2.1 Filter Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . 210
7.9.2.2 Validator Configuration . . . . . . . . . . . . . . . . . . . . . . . 210
7.9.2.3 Attaching Input Filter to Form Model . . . . . . . . . . . . . . . . 211
7.9.3 Creating Input Filter for the Contact Form . . . . . . . . . . . . . . . . . . 211
7.10 Using the Form in a Controller’s Action . . . . . . . . . . . . . . . . . . . . . . . 214
7.10.1 Passing Form Data to a Model . . . . . . . . . . . . . . . . . . . . . . . . . 218
7.11 Form Presentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
7.11.1 Preparing the Form Model for Rendering . . . . . . . . . . . . . . . . . . . 222
7.12 Standard Form View Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
7.12.1 Rendering a Form Element . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
7.12.2 Rendering an Element’s Validation Errors . . . . . . . . . . . . . . . . . . 225
7.12.3 Rendering an Element’s Label . . . . . . . . . . . . . . . . . . . . . . . . . 225
7.12.4 Rendering a Form Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
7.12.5 Rendering the Entire Form . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
7.13 Example: Creating the View Template for the Contact Form . . . . . . . . . . . . 227
7.13.1 Applying the Bootstrap CSS Styles to Form . . . . . . . . . . . . . . . . . . 229
7.13.2 Styling the Validation Errors List . . . . . . . . . . . . . . . . . . . . . . . 231
7.13.3 Adding the “Thank You” & “Error Sending Email” Pages . . . . . . . . . . 232
7.13.4 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
7.14 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235

8. Transforming Input Data with Filters . . . . . . . . . . . . . . . . . . . . . . . . . . 236


8.1 About Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
8.1.1 FilterInterface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
8.2 Standard Filters Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
8.3 Instantiating a Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
8.3.1 Method 1: Instantiating a Filter Manually . . . . . . . . . . . . . . . . . . . 240
8.3.2 Method 2: Constructing a Filter with StaticFilter . . . . . . . . . . . . . . . 242
8.3.3 Method 3: Constructing a Filter From Array . . . . . . . . . . . . . . . . . 242
8.4 About Filter Plugin Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
8.5 Filter’s Behavior in Case of Incorrect Input Data . . . . . . . . . . . . . . . . . . 243
8.6 Filter Usage Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
8.6.1 Filters Casting Input Data to a Specified Type . . . . . . . . . . . . . . . . 244
8.6.1.1 Int Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
CONTENTS

8.6.1.2 Boolean Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244


8.6.1.3 Null Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
8.6.1.4 DateTimeFormatter Filter . . . . . . . . . . . . . . . . . . . . . . . 248
8.6.2 Filters Performing Manipulations on a File Path . . . . . . . . . . . . . . . 249
8.6.2.1 BaseName Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
8.6.2.2 Dir Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
8.6.2.3 RealPath Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
8.6.3 Filters Performing Compression and Encryption of Input Data . . . . . . . 251
8.6.3.1 Compress Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
8.6.3.2 Encrypt Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
8.6.4 Filters Manipulating String Data . . . . . . . . . . . . . . . . . . . . . . . . 254
8.6.4.1 StringToLower Filter . . . . . . . . . . . . . . . . . . . . . . . . . 254
8.6.4.2 PregReplace Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
8.6.4.3 StripTags Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
8.6.4.4 StripNewlines Filter . . . . . . . . . . . . . . . . . . . . . . . . . . 257
8.6.4.5 UriNormalize Filter . . . . . . . . . . . . . . . . . . . . . . . . . . 257
8.6.5 Organizing Filters in a Chain . . . . . . . . . . . . . . . . . . . . . . . . . 259
8.6.6 Custom Filtering with the Callback Filter . . . . . . . . . . . . . . . . . . . 260
8.6.6.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
8.7 Writing Your Own Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
8.7.1 Using the PhoneFilter Class . . . . . . . . . . . . . . . . . . . . . . . . . . 267
8.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268

9. Checking Input Data with Validators . . . . . . . . . . . . . . . . . . . . . . . . . . 270


9.1 About Validators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
9.1.1 ValidatorInterface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
9.2 Standard Validators Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
9.3 Validator Behaviour in Case of Invalid or Unacceptable Data . . . . . . . . . . . 273
9.4 Instantiating a Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
9.4.1 Method 1. Manual Instantiation of a Validator . . . . . . . . . . . . . . . . 275
9.4.2 Method 2. Using StaticValidator Wrapper . . . . . . . . . . . . . . . . . . . 278
9.4.3 Method 3. Using an Array Configuration . . . . . . . . . . . . . . . . . . . 279
9.5 About Validator Plugin Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
9.6 Validator Usage Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
9.6.1 Validators for Checking Value Conformance to Certain Format . . . . . . . 280
9.6.1.1 Ip Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
9.6.1.2 Hostname Validator . . . . . . . . . . . . . . . . . . . . . . . . . . 281
9.6.1.3 Uri Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
9.6.1.4 Date Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
9.6.1.5 Regex Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
9.6.2 Validators for Checking a Numerical Value Lies in a Given Range . . . . . 286
9.6.2.1 NotEmpty Validator . . . . . . . . . . . . . . . . . . . . . . . . . . 286
9.6.2.2 Between Validator . . . . . . . . . . . . . . . . . . . . . . . . . . 288
9.6.2.3 InArray Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
9.6.2.4 StringLength Validator . . . . . . . . . . . . . . . . . . . . . . . . 290
9.6.3 Organizing Validators in a Chain . . . . . . . . . . . . . . . . . . . . . . . 291
CONTENTS

9.6.4 Custom Validation with the Callback Validator . . . . . . . . . . . . . . . . 292


9.6.4.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
9.7 Writing Own Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
9.7.1 Using the PhoneValidator Class . . . . . . . . . . . . . . . . . . . . . . . . 299
9.8 Using Filters & Validators Outside a Form . . . . . . . . . . . . . . . . . . . . . . 300
9.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302

10.Uploading Files with Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303


10.1 About HTTP File Uploads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
10.1.1 HTTP Binary Transfer Encoding . . . . . . . . . . . . . . . . . . . . . . . 303
10.1.2 $_FILES Super-Global Array in PHP . . . . . . . . . . . . . . . . . . . . . 305
10.2 Accessing Uploaded Files in ZF2 . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
10.3 File Uploads & ZF2 Form Model . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
10.4 Validating Uploaded Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
10.5 Filtering Uploaded Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
10.6 InputFilter Container & File Uploads . . . . . . . . . . . . . . . . . . . . . . . . . 311
10.6.1 FileInput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
10.6.2 Executing Validators before Filters . . . . . . . . . . . . . . . . . . . . . . 312
10.7 Controller Action & File Uploads . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
10.8 Example: Image Gallery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
10.8.1 Adding ImageForm Model . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
10.8.2 Adding Validation Rules to ImageForm Model . . . . . . . . . . . . . . . . 317
10.8.3 Writing ImageManager Service . . . . . . . . . . . . . . . . . . . . . . . . 319
10.8.4 Adding ImageController . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
10.8.4.1 Adding Upload Action & Corresponding View Template . . . . . . 325
10.8.4.2 Adding Index Action & Corresponding View Template . . . . . . . 328
10.8.4.3 Adding File Action . . . . . . . . . . . . . . . . . . . . . . . . . . 330
10.8.4.4 Registering the ImageController . . . . . . . . . . . . . . . . . . . 332
10.8.5 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333
10.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335

11.Advanced Usage of Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336


11.1 Form Security Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
11.1.1 CAPTCHA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
11.1.1.1 CAPTCHA Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
11.1.1.2 CAPTCHA Form Element & View Helper . . . . . . . . . . . . . . 338
11.1.2 Example 1: Adding Image CAPTCHA to the ContactForm . . . . . . . . . . 339
11.1.3 Example 2: Adding a FIGlet CAPTCHA to the ContactForm . . . . . . . . 342
11.1.4 Example 3: Adding reCaptcha CAPTCHA to the ContactForm . . . . . . . 344
11.1.5 CSRF Prevention . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
11.1.5.1 Example: Adding a CSRF Element to Form . . . . . . . . . . . . . 348
11.2 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349

12.Database Management with Doctrine ORM . . . . . . . . . . . . . . . . . . . . . . 350


12.1 Get Blog Example from GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
12.2 Creating a Simple MySQL Database . . . . . . . . . . . . . . . . . . . . . . . . . 352
12.2.1 Creating New Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
CONTENTS

12.2.2 Creating Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354


12.2.3 Importing Ready Database Schema . . . . . . . . . . . . . . . . . . . . . . 356
12.3 Integrating Doctrine ORM with Zend Framework 2 . . . . . . . . . . . . . . . . 357
12.3.1 Installing Doctrine Components with Composer . . . . . . . . . . . . . . . 358
12.3.2 Loading Doctrine Integration Modules on Application Start Up . . . . . . . 360
12.3.3 Doctrine Configuration Overview . . . . . . . . . . . . . . . . . . . . . . . 361
12.3.4 Overriding the Default Doctrine Configuration . . . . . . . . . . . . . . . 363
12.4 Specifying Database Connection Parameters . . . . . . . . . . . . . . . . . . . . 363
12.5 About Doctrine Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
12.5.1 Annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
12.6 Creating Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
12.6.1 Adding Post Entity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
12.6.2 Adding the Comment and Tag Entities . . . . . . . . . . . . . . . . . . . . 371
12.6.3 Specifying Relations between Entities . . . . . . . . . . . . . . . . . . . . . 375
12.6.3.1 OneToMany/ManyToOne . . . . . . . . . . . . . . . . . . . . . . . 375
12.6.3.2 ManyToMany . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
12.6.4 Specifying Entity Locations . . . . . . . . . . . . . . . . . . . . . . . . . . 380
12.7 About Entity Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
12.7.1 Entity Repositories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
12.8 Adding Blog Home Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
12.9 Adding New Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
12.9.1 Adding PostForm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
12.9.2 Adding PostManager Service . . . . . . . . . . . . . . . . . . . . . . . . . 390
12.9.3 Creating Controller Action and View Template . . . . . . . . . . . . . . . . 393
12.10 Editing Existing Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
12.10.1Modifying PostManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . 397
12.10.2Adding Controller Action and View Template . . . . . . . . . . . . . . . . 398
12.11 Deleting Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
12.11.1Modifying PostManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
12.11.2Adding Controller Action . . . . . . . . . . . . . . . . . . . . . . . . . . . 404
12.12 Implementing Post Preview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
12.12.1Adding CommentForm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
12.12.2Modifying PostManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
12.12.3Adding Controller Action and View Template . . . . . . . . . . . . . . . . 410
12.13 Implementing Admin Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414
12.14 Implementing Tag Cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
12.14.1Adding Custom Post Repository . . . . . . . . . . . . . . . . . . . . . . . . 418
12.14.2Calculating Tag Cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
12.14.3Modifying Controller Action . . . . . . . . . . . . . . . . . . . . . . . . . . 421
12.14.4Rendering Tag Cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422
12.15 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424

Appendix A. Configuring Web Development Environment . . . . . . . . . . . . . . . 425


Installing Apache, PHP and MySQL in Linux . . . . . . . . . . . . . . . . . . . . . . . 425
Installing Apache and PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
Checking Web Server Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
CONTENTS

Editing PHP Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428


Restarting Apache Web Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430
Enabling Apache’s mod_rewrite module . . . . . . . . . . . . . . . . . . . . . . . 430
Creating Apache Virtual Host . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
Installing XDebug PHP extension . . . . . . . . . . . . . . . . . . . . . . . . . . . 432
Installing APC PHP Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434
Installing MySQL Database Server . . . . . . . . . . . . . . . . . . . . . . . . . . 434
Configuring the MySQL Database Server . . . . . . . . . . . . . . . . . . . . . . 435
Installing Apache, PHP and MySQL in Windows . . . . . . . . . . . . . . . . . . . . . 435
Checking Web Server Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . 438
Enabling Apache’s mod_rewrite module . . . . . . . . . . . . . . . . . . . . . . . 439
Creating Apache Virtual Host . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
Installing XDebug PHP extension . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
Installing MySQL Database Server . . . . . . . . . . . . . . . . . . . . . . . . . . 442
Configuring the MySQL Database Server . . . . . . . . . . . . . . . . . . . . . . 442
Installing NetBeans IDE in Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
Method 1. Installing from Repository . . . . . . . . . . . . . . . . . . . . . . . . . 444
Method 2. Downloading from Web Site . . . . . . . . . . . . . . . . . . . . . . . 445
Installing NetBeans IDE in Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . 448
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448

Appendix B. Introduction to PHP Development in NetBeans IDE . . . . . . . . . . . . 450


Run Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450
Running the Web Site . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452
Site Debugging in NetBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453
Debug Toolbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454
Breakpoints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
Watching Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
Call Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457
Debugging Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458
Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463

Appendix C. Introduction to Twitter Bootstrap . . . . . . . . . . . . . . . . . . . . . . 464


Overview of Bootstrap Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464
Grid System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
Defining the Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Offsetting Columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
Nesting Grids . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468
“Mobile First” Concept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469
Bootstrap’s Interface Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470
Navigation Bar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471
Dropdown Menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471
Collapsible Navbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472
Inverse Navbar Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
Breadcrumbs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474
CONTENTS

Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474
Buttons & Glyphicons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
Customizing Bootstrap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478

Appendix D. Introduction to Doctrine . . . . . . . . . . . . . . . . . . . . . . . . . . . 479


Doctrine and Database Management Systems . . . . . . . . . . . . . . . . . . . . . . . 479
Relational Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479
SQL vs. DQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
NoSQL Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
Document Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
Doctrine Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
Components Supporting Relational Databases . . . . . . . . . . . . . . . . . . . . 482
Components Supporting NoSQL Document Databases . . . . . . . . . . . . . . . 484
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485

About the Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487


Preface
About this Leanpub Book
Thanks to Leanpub¹, you have a chance to obtain this book at a lower cost and give your feedback
to the author. This makes it possible for the author to immediately correct mistakes and enhance
the places in text that you point to, thus creating a high-quality product.

You will receive all newer versions of this book for free as they appear.

Why to Read this Book?


The “Using Zend Framework 2” book is about programming web-sites with Zend Framework 2.
With this e-Book, you can save your time and efforts learning ZF2.
The author strives to give material starting with simple things that a beginner should understand.
Advanced things go last in a chapter. This makes this book the first book about Zend Framework
that is easy to read and understand for a newbie.

You can have a brief introduction to the book by watching the intro video² on YouTube!

Zend Framework Explained


The “Using Zend Framework 2” book is dedicated to web site development with PHP and
Zend Framework 2 (ZF2). ZF2 is a modern PHP web development framework intended for
building professionally looking, scalable and secure web-sites. Such web sites are easy to test
and maintain. The framework utilizes the best practices and common design patterns, inspired
by the evolution of web development industry. This includes Model-View-Controller pattern,
allowing to organize the code in a consistent and standard way, making it easier to implement
automatic code testing.
¹https://leanpub.com
²http://youtu.be/A9BSV0RXn2k
Preface ii

See ZF2 Wider


This e-book is not only about Zend Framework, but also about closely related libraries. Although
Zend Framework 2 has dedicated component for accessing the database, in this book we use
third-party library called Doctrine ORM — a de-facto standard object-oriented way to perform
database management. In the sample applications we will create in chapters of this book, Twitter
Bootstrap CSS Framework is used, allowing to produce nice looking visual appearance and layout
of HTML elements on the web pages.

ZF2 Book for Beginners


This book is intended for web developers involved in the development of sites in PHP. The author
strives to give material starting with simple things that a beginner should understand. Advanced
things go last in a chapter. You do not need to be a guru in design patterns to understand most
of the stuff.
To read and understand this book, you need to have a basic knowledge of PHP language. A good
point for learning PHP is its official web site³ and the online documentation⁴. It would be good
if you have some idea of what is HTTP request, GET and POST variables, namespaces, classes
and interfaces.
Because PHP is closely related to other web technologies, it is also recommended that you have
some basic experience in the following:

• 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⁵.

Structure of the Book


This book is divided by chapters. A chapter is dedicated to a single topic. For example, Chapter 1
“Introduction to Zend Framework 2” is intended to make you familiar with fundamental concepts
and main components of the framework; Chapter 2 “Zend Skeleton Application” is dedicated to
giving you instructions to install the skeleton application, which can be used for creation of your
own web sites, and so on.
³http://php.net/
⁴http://php.net/docs.php
⁵http://www.w3schools.com/
Preface iii

Learn ZF2 by Example


This ZF2 book’s text is illustrated with code samples (the source code is published on GitHub).
Each sample is a complete web-site you can install and run yourself to see Zend Framework 2 in
action. You can even use the samples as a base for your own web sites.
All the source code is stored on GitHub code hosting. The code is publicly available, and you
can download the entire code archive by visiting this page⁶. To download the archive, click the
Download ZIP button that can be found on the page (see the figure below).

Samples can be downloaded from GitHub

The structure of the code archive is presented below.

⁶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?

1. Create an account at Leanpub site¹².


2. Go to the Affiliate page in your account.
3. Look for the “Using Zend Framework 2″ book in the list of books participating in the
affiliate program.
4. Click the “Copy affiliate URL” button.
5. Paste the affiliate URL on your web site.
6. When someone buys the book after going to the page using your affiliate code, you get
50% of the minimum price of the book.

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.

1.1 What is Zend Framework 2?


PHP is a popular web-site development language. However, it has been proven that writing web-
sites in pure PHP is difficult. If you write a web application in PHP, you have to organize your
code in some way, collect and validate user input, implement support of user access control,
manage database, perform scheduled mail delivery, test your code and so on. As your site grows
in size, it becomes more and more difficult to develop the code in such manner. Moreover, when
you switch to the development of a new site, you will notice that a large portion of the code you
have already written for the old site can be used again with small modifications. This code can
be separated in a library. This is how frameworks appeared.
A framework is some kind of a library, a piece of software (also written in PHP) providing
web developers with code base and consistent standardized ways of creating web applications.
Imagine that your web-site is a house, then PHP language is its foundation, and the framework
is the basement. The basement contains a lot of building blocks (components) and tools prepared
for you to make it easier to build the upper floors of your house (see figure 1.1).
Zend Framework 2 is a free and open-source PHP framework. Its development is guided (and
sponsored) by Zend, which is also known as the vendor of the PHP language itself. The first
version (Zend Framework 1) was released in 2007 and since then it has become obsolete. Zend
Framework 2 (or shortly ZF2) is the second version of this software, and it was released in
September 2012. At the moment of writing this book, Zend Framework 2.3 is out.
Zend Framework 2 provides you with the following capabilities:

• 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

Copyright (c) 2005-2013, Zend Technologies USA, Inc.


All rights reserved.

Redistribution and use in source and binary forms, with or without


modification, are permitted provided that the following conditions
are met:

* Redistributions of source code must retain the above copyright


notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright


notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.

* Neither the name of 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.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS


"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1.3 What Companies Prefer Zend Framework 2?


Today, there are many companies who prefer Zend Framework 2 for building their powerful web
sites. Some of them are listed on the main page of the framework.zend.com¹. Among them:

• 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

Figure 1.2. BBC web site is based on Zend Framework 2

• 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

Figure 1.3. BNP Paribas web site is based on Zend Framework 2

1.4 Release Schedule


ZF developers are known to release new versions rather frequently. This allows for security bugs
to be fixed quickly, thus allowing your sites to remain stable and secure. Release history⁴ for
recent versions of ZF2 is shown in the table 1.1:

Table 1.1. ZF2 Release History

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

Table 1.1. ZF2 Release History

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.

1.6 User Support


Support is an important thing to consider when deciding whether to use the framework as
the base for your web site or not. Support includes well written documentation, webinars,
community forums and (optionally) commercial support services, like trainings and certification
programs.
⁵http://framework.zend.com/changelog/
⁶http://framework.zend.com/
Introduction to Zend Framework 2 7

Figure 1.4. Zend Framework official project web site

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

found by this link⁹. Among webinar topics, there are:

• 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.

1.7 Supported Operating Systems


As any PHP web-site, ZF2-based web application can work on a Windows server, on a Linux
server and on any other operating systems PHP can run in. For instance, for creating samples
for this book, the author used Ubuntu Linux operating system.
If you do not know yet what OS to use for your web development, it is recommended for you to
use Linux, because most server software operates on Linux servers. You can refer to Appendix
A for some instructions on configuring your development environment.

1.8 Server Requirements


Zend Framework 2 requires that your server has PHP version 5.3.3 (or later) installed. Note that
this is a rather strict requirement. Not all cheap shared hostings and not all private servers have
such a modern PHP version.
Moreover, the recommended way of installing ZF2 (and other components your app depends on)
is using Composer¹¹. This forces the need of shell access (SSH) to be able to execute Composer
command-line tool. Some shared hostings provide FTP access only, so you won’t be able to install
a ZF2-based web app on such servers the usual way.

What do I do if I don’t have shell access to server?


If your hosting allows you to upload files through FTP protocol, you can prepare all
files on your local machine and then upload the files to the server as an archive.
⁹http://www.zend.com/en/resources/webinars/framework
¹⁰http://www.zend.com/en/services/training/course-catalog/zend-framework-2
¹¹http://getcomposer.org/
Introduction to Zend Framework 2 9

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.

Are there any benchmark tests of ZF2 performance?


As per the author’s knowledge, currently, there are no reliable benchmark tests that
would allow to compare ZF2 performance with performance of other frameworks.

1.11 Design Patterns


Zend Framework 2 creators are big fans of various design patterns. Although you don’t have to
understand patterns to read this book, this section is intended to give you an idea of what design
patterns ZF2 is based on.

• Model-View-Controller (MVC) pattern. Model-View-Controller pattern is used in all mod-


ern PHP frameworks. In an MVC-application you separate your code into three categories:
models (your business logics go here), views (your presentation goes here) and controllers
(code responsible for interaction with user goes here). This is also called the separation
of concerns. With MVC, you can reuse your components in a different project. It is also
easy to substitute any part of this triad. For example, you can easily replace a view with
another one, without changing your business logics.
• Domain Driven Design (DDD) pattern In Zend Framework 2, by convention, you will have
model layer further splitted into entities (classes mapped on database tables), repositories
(classes used to retrieve entities), value objects (model classes not having identity), services
(classes responsible for business logics).
Additionally, you will have forms (classes responsible for collecting user input), view
helpers (reusable plugin classes intended for rendering stuff) and others.
Introduction to Zend Framework 2 11

• 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.

Table 1.2. Zend Framework 2 Components

Component Name Description


Core Components
Zend\Cache Provides a generic way to cache any data. Caching is used to save
frequently used data to memory (or another storage media) to
speed-up data retrieval.
Zend\Code Provides facilities to generate arbitrary code using an object
oriented interface. Also includes annotation parsing.
Zend\Console Provides an ability to create applications runnable from shell
command line. Console can be used, for example, for executing
scheduled actions, like mail delivery.
Zend\Di Dependency injection. Can be used to easily substitute and replace
dependent classes.
Zend\EventManager Allows to send events and register listeners to react to them. This
component is covered in Chapter 3.
Zend\Http Provides an easy interface for performing Hyper-Text Transfer
Protocol (HTTP) requests. This component is covered in Chapter 4.
Zend\Loader PHP class discovery and autoloading support. Autoloading is a more
efficient replacement for require_once. This component is covered in
Chapter 3.
Zend\ModuleManager Zend Framework 2 module manager. In ZF2, every application consists
of modules. This component is covered in Chapter 3.
Zend\Mvc Support of Model-View-Controller pattern. Separation of business
logic from presentation. This component is covered in Chapter 4.
Zend\ServiceManager Service manager. This is the registry of all services available
in the application, making it possible to access services from
Introduction to Zend Framework 2 13

Table 1.2. Zend Framework 2 Components

Component Name Description


any point of the web site. This component is covered in Chapter 3.
Zend\Stdlib Miscellaneous utility classes: string utils, array utils,
serializable queues, etc.
Zend\View Provides a system of helpers, output filters, and variable
escaping. Used in presentation layer. This component is covered in
Chapter 4.
Zend\Uri A component that aids in manipulating and validating Uniform
Resource Identifiers (URIs).
Persistence
Zend\Dom Provides tools for working with DOM documents and structures.
This includes querying DOM trees with CSS selectors and XPath.
Zend\Db Provides database access in cross-database style.
Zend\Json Provides convenience methods for serializing native PHP to JSON and
decoding JSON to native PHP. Used for object serialization.
Zend\Serializer Provides an adapter based interface to simply generate storable
representation of PHP types by different facilities, and recover them.
User Management
Zend\Authentication Provides an API for user authentication. Users are typically
authenticated by providing a username and password which are
compared against a database table or Apache password file.
Zend\Permissions Access control lists (ACLs) and role-based access control (RBAC).
Zend\Session Manage and preserve session data, a logical complement of cookie
data, across multiple page requests by the same client.
Presentation Utilities
Zend\Barcode Provides a generic way to generate barcodes. A barcode is a
small bar containing stripes of various width and is optically
readable by a machine. You may have seen barcodes when purchasing
goods in a supermarket. This component is covered in Chapter 5.
Zend\Captcha Human input check. Generates a random image ensuring that your
site’s user is not a robot. This component is covered in Chapter 10.
Zend\Navigation Sitemaps, breadcrumbs and site navigation trees.
Zend\Paginator Breaking large tabular data results into pages.
Zend\ProgressBar Component to create and update progress bars in different environments.
Zend\Escaper Smart class for escaping text output. Used to secure web site views.
Zend\Tag A component suite which provides a facility to work with taggable items.
Testing and Debugging
Zend\Debug A small component containing a debugging utility class.
Introduction to Zend Framework 2 14

Table 1.2. Zend Framework 2 Components

Component Name Description

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

Table 1.2. Zend Framework 2 Components

Component Name Description


works in limited memory mode.
Zend\Text Various text utilities like character tables and FIGlets.
Zend\Version Allows to retrieve the version of Zend Framework. This component is
covered in Chapter 4.

1.13 ZF2 Service Components


In addition to standard Zend Framework 2 components described in the previous section,
there are so called Services for Zend Framework 2 components. Those components provide
implementations of API for accessing various popular web resources (e.g. Flickr, Twitter,
SlideShare, reCaptcha and so on) programmatically. Table 1.3 contains the list of (currently
available) service components together with their brief descriptions:

Component Name Description


ZendService\Akismet Provides API for accessing Akismet¹⁵ (a spam
filtering service for your blog).
ZendService\Amazon Provides API for using Amazon¹⁶ web services.
Amazon provides a number of web services, among them EC2 (web
hosting in the cloud), S3 (storage in the cloud) and others.
ZendService\AppleApns Provides a client for the Apple Push Notification Service (APNs for
short), which is a service for propagating information to iOS and
Mac OS X devices.
ZendService\Audioscrobbler API for using the Audioscrobbler¹⁷ service, which is a database that
tracks listening habits.
ZendService\Delicious API for using del.icio.us¹⁸ services. Provides access to posts at
del.icio.us and read-only access to public data of all users.
ZendService\DeveloperGarden Provides API for accessing services of Deutsche Telekom, such as
voice connections or sending SMS messages.
ZendService\Flickr API for using the Flickr¹⁹ photo sharing service.
ZendService\Google\Gcm Provides a client for the Google Cloud Messaging API.
ZendService\LiveDocx Provides API to LiveDocx service that allows to generate PDF,
DOCX, DOC, HTML or RTF files.
ZendService\Nirvanix API for using Nirvanix service which provides an Internet Media
File System (IMFS), a storage service that allows applications to
upload, store and access files.
¹⁵http://akismet.com/
¹⁶http://aws.amazon.com/
¹⁷http://www.audioscrobbler.net/
¹⁸https://delicious.com/
¹⁹http://www.flickr.com/
Introduction to Zend Framework 2 16

Component Name Description

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 Differences with Zend Framework 1


For readers who have an experience in Zend Framework 1, in this section we’ll give some
information on what has changed in Zend Framework 2.
Below, the main technical differences between ZF1 and ZF2 are presented:

1.14.1 Backwards Compatibility


ZF1’s architecture passed an evolutionary path, preserving backwards compatibility and accu-
mulating many solutions which were not as efficient as they could be. ZF2 has been rewritten
from scratch to implement the best features of ZF1 in a better, more efficient and scalable way.
Because of these breaking changes, ZF2 is not backwards-compatible with ZF1.

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.4 Aspect Oriented Design


In ZF2, events are used to make it possible to decouple modules. You can install a module, and it
will just work by listening to events occurring in the application without knowing about other
modules. Events include bootstrapping, routing, dispatching and rendering.

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.

1.14.7 Service Manager


In ZF1, you had an application registry of classes, which allowed you to access application
services and even put your own class to the registry and use it later. In ZF2, we have the
service manager, which is a more complex version of the registry, implementing lazy loading
and dependency injection. With service manager, you can register a service class and use it
across your modules. For example, Doctrine ORM library registers the Entity Manager service
which you can use to access the database across the module controllers.

1.15 Competing Frameworks


Zend Framework is not the only web development framework. There are others, like Symfony²⁶,
Cake PHP²⁷, CodeIgniter²⁸ and Yii Framework²⁹. To estimate the average popularity of these
²⁶http://symfony.com/
²⁷http://cakephp.org/
²⁸http://ellislab.com/codeigniter
²⁹http://www.yiiframework.com/
Introduction to Zend Framework 2 18

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.

Figure 1.5. Popularity of PHP frameworks. Powered by Google Trends

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.

Table 1.4. Comparison of Features provided by PHP frameworks

Feature ZF2 Symfony 2 Cake PHP CodeIgniter Yii


Current 2.2.1 2.3.1 2.3.6 2.1.3 1.1.13
version
Distribution 3 Mb 4.4 Mb 2.0 Mb 2.2 Mb 3.9 Mb
archive size
Installation Composer Composer Archive Archive Archive
Compatibility Bad (requires Bad (requires Good Good Good
with SSH and SSH and
shared vhosts) vhosts)
hostings
Monolithic or Components Components Components Components Monolithic
component-
based?
Prefer Configuration Configuration Conventions Conventions Conventions
conventions
or configs?
Database Data Mapper Data Mapper Active Record Traditional Active Record,
access
pattern (Doctrine/ORM),(Doctrine/ORM) or PDO
Table Active Record
Gateway,
Row
Gateway
Introduction to Zend Framework 2 20

Table 1.4. Comparison of Features provided by PHP frameworks

Feature ZF2 Symfony 2 Cake PHP CodeIgniter Yii

Database Yes Yes (Doctrine- Yes Yes Yes


(Doctrine-
migrations provided) provided)
CSS Twitter Twitter Any Any Blueprint CSS
Framework
Bootstrap Bootstrap
View Any you Twig Smarty/Twig Any you None or Prado
Template want, or want,
Language none at all (recommended) or none
Unit testing Yes Yes (PHPUnit) Yes (PHPUnit) Yes (PHPUnit) Yes (PHPUnit-
(PHPUnit)
support based)
Functional Yes Yes (PHPUnit) Yes No Yes (Selenium)
(PHPUnit)
testing
Database Yes Yes (Doctrine Yes No Yes
(Doctrine-
fixtures provided) bundle)

Summarizing the table above, we can say that:

• 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.

I’ve found a mistake in this chapter/have a suggestion. What do I do?


Please contact the author using the dedicated Forum³¹. Alternatively, you can contact
the author through his E-mail address (olegkrivtsov@gmail.com). The author appre-
ciates your feedback and will be happy to answer you and improve this book.

³¹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.

2.1 Getting Zend Skeleton Application


The Skeleton Application is a simple ZF2-based application that contains most necessary things
for creating your own simple web site. The skeleton application’s code is stored on GitHub code
hosting and can be publicly accessed by this link¹. To download the source code of the skeleton
application as a ZIP archive, click the Download ZIP button (see the figure 2.1 below).

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

Figure 2.1. Zend Skeleton Application’s code is stored on GitHub

2.2 Typical Directory Structure


Every ZF2-based web-site (including the skeleton app) is organized in the same recommended
way. Of course, you can configure your application to use a different directory layout, but this
may make it difficult to support your web-site by other people who are not familiar with such a
directory structure.
Let’s have a brief look at the typical directory structure (see figure 2.2):
As you can see, in the top-level directory (we will denote it APP_DIR from now on), there are
several files:

• README.md is a text file containing a brief description of the skeleton application;


• LICENSE.txt is a text file containing ZF2 license (you had a chance to read it in Chapter 1
of this book);
• composer.phar is an executable PHP archive containing the code of Composer dependency
management tool; we will use it later;
• composer.json is a JSON configuration file for Composer.
Zend Skeleton Application 24

Figure 2.2. Typical Directory Structure

And we also have several subdirectories:


The config directory contains application-level configuration files.
The data directory contains the data your application might create; it may also contain cache
used to speed-up Zend Framework.
The module directory contains all application modules. Currently there is a single module called
Application. The Application is the main module of your web-site. You can also put other
modules here, if you wish. We will talk about the modules a little bit later.
The vendor directory’s purpose is to contain third-party library files, including Zend Framework
2 library files. Currently this directory is almost empty, but we will install all required libraries
later.
The public directory contains data publicly accessible by the web-user. As you can see, web-
users will mainly communicate with the index.php, which is also called the entry point of your
web site.

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:

• css subdirectory contains all publicly accessible CSS files;


• img subdirectory contains publicly accessible images (*.JPG, *.PNG, *.GIF, *.ICO, etc.);
Zend Skeleton Application 25

• 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.

What is jQuery library?


jQuery is a JavaScript library which was created to simplify the client-side scripting
of HTML pages. jQuery’s selector mechanism allows to easily attach event handlers to
certain HTML elements, making it really simple to make your HTML pages interactive.

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

2.3 Installing Dependencies with Composer


When writing a ZF2-based web-site, you are recommended to use Composer⁴ for installation
of your application’s dependencies. A dependence is some third-party code your app uses. For
example Zend Framework 2 is the dependence for your web-site.
In Composer, any library is called a package. All packages installable by Composer are registered
on Packagist.org⁵ site. With Composer, you can identify the packages that your app requires and
have Composer to download and install them automatically.
The dependencies of the skeleton application are declared in APP_DIR/composer.json file (see
below):

²http://jquery.com/
³http://git-scm.com/
⁴http://getcomposer.org/
⁵https://packagist.org/
Zend Skeleton Application 26

Contents of composer.json file

{
"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.)

Figure 2.3. Vendor directory

In some other frameworks, another (conventional) way of dependency installation is


used. You just download the dependency library as an archive, unpack it and put it
somewhere inside of your directory structure (typically, to vendor directory). This
approach was used in Zend Framework 1.

2.4 Apache Virtual Host


Now we are almost ready to get our skeleton web-site live! The last thing we are going to do is
configure an Apache virtual host. A virtual host term means that you can run several web-sites on
the same machine. The virtual sites are differentiated by domain name (like site.mydomain.com
and site2.mydomain.com) or by port number (like localhost and localhost:8080). Virtual hosts
work transparently for site users, that means users have no idea whether the sites are working
on the same machine or on different ones.
Zend Skeleton Application 28

Can I install the web-site to /var/www directory without virtual hosts?


With ZF2-based web sites, it would be more convenient to use Apache virtual hosts
instead of putting the files inside of /var/www. This is because the public subdirectory
needs to be the document root of your site. If you put an entire application in /var/www,
which is the document root by default, you would have to additionally configure the
.htaccess file to forbid access to everything except the public subdirectory. With
virtual host configuration this is a bit easier to do.

Currently, we have the skeleton application inside of home folder. To let Apache know about it,
we need to edit the virtual host file.

Virtual host file may be located at a different path, depending on your


operating system type. For example, in Linux Ubuntu it is located in
/etc/apache2/sites-available/000-default file. Moreover, virtual host file
name and content may look differently depending on Apache HTTP Server’s version.
For OS- and server-specific information about virtual hosts, please refer to Appendix
A.

Let’s now edit the default virtual host file to make it look like below (this example is applicable
to Apache v.2.2):

Virtual host file

<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

# Possible values include: debug, info, notice, warn, error, crit,


# alert, emerg.
LogLevel warn

</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.

2.5 Opening the Web Site in Your Browser


To open the web site, type “http://localhost” in your browser’s navigation bar and press Enter.
Figure 2.3 shows the site in action.
On the page that appears, you can see the navigation menu at the top. The navigation bar
currently contains the single link named Home. Under the navigation bar, you can see the
“Welcome to Zend Framework 2” caption. Below the caption, you can find some advices for
beginners on how to develop new ZF2-based applications.
Zend Skeleton Application 30

Figure 2.3. Zend Skeleton Application

2.6 Creating NetBeans Project


Now that we have the skeleton application set up and working, we will want to change something
with it in the future. To easily navigate the directory structure, edit files and debug the web site,
the common practice is to use an IDE (Integrated Development Environment). In this book, we
use NetBeans IDE (see Appendix A for more information on how to install NetBeans).
To create NetBeans project for our skeleton application, run NetBeans and open menu File->New
Project…. The New Project dialog appears (see figure 2.4).
In the Choose Project page that appears, you should choose PHP project type and in the right
list select Application with Existing Source (because we already have the skeleton application’s
code). Then click the Next button to go to the next page (shown in figure 2.5).
In the Name and Location dialog page, you should enter the path to the code (like /home/user-
name/helloworld), the name for the project (for example, helloworld) and specify the version of
PHP your code uses (PHP 5.3 or later). The PHP version is needed for the NetBeans PHP syntax
checker which will scan your PHP code for errors and highlight them. Press the Next button to
go to the next dialog page (shown in figure 2.6).
Zend Skeleton Application 31

Figure 2.4. Creating NetBeans Project - Choose Project Page

Figure 2.5. Creating NetBeans Project - Name and Location Page


Zend Skeleton Application 32

Figure 2.6. Creating NetBeans Project - Choosing Configuration Page

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.

It’s time for some advanced stuff…


Congratulations! We’ve done the hard work of installing and running the Zend Skeleton
Application, and now it’s time to have a rest and read about some advanced things in
the last part of this chapter.

2.7 Hypertext Access File (.htaccess)


We’ve mentioned the APP_DIR/public/.htaccess file when talking about typical directory
structure. Now let’s try to understand the role of this file and how we can use it.
Zend Skeleton Application 33

Figure 2.7. NetBeans Project Window

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.

Table 2.2. URL rewrite examples

Requested URL Rewritten URL


http://localhost/index.php File exists; return the local file
APP_DIR/public/index.php

http://localhost/css/bootstrap.css File exists; return the local file


APP_DIR/public/css/bootstrap.css

http://localhost/htpasswd File does not exist; return


APP_DIR/public/index.php instead.

2.8 Blocking Access to the Web Site by IP Address


Sometimes it may be required to block access to your web site from all other IP addresses except
yours. For example, when you develop a web site, you don’t want someone to see your incomplete
work. Also, you may not want to let Google or other search engines to index your web site.
To forbid access to your site, you can modify the .htaccess file and add the following lines to
the end:

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

How do I determine my IP address?


You can use the http://www.whatismyip.com⁶ web site to determine your external IP
address. The external IP address is the address by which other computers on the Internet
may access your site.

2.9 HTTP Authentication


You may want to allow access to your site to certain users. For example, when you are
demonstrating your web site to your boss, you will give her username and password for logging
into your site.
To allow access to your web site by username and password, you can modify the .htaccess file
and add the following lines to the end:

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

# htpasswd -c /usr/local/apache/passwd/passwords <username>


New password:
Re-type new password:
Adding password for user <username>

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.

For additional information on HTTP authentication, you can refer to Authentication


and Authorization⁷ topic of Apache documentation.

2.10 Having Multiple Virtual Hosts


When developing several web sites on the same machine, you will want to create several virtual
hosts. For each virtual host you need to specify a domain name (like site1.mydomain.com). But
if you currently don’t have a domain name, you can specify a different port instead (see the
example below).

# Listen directive tells Apache to listen requests on port 8080


Listen 8080

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.

2.11 Hosts File


When you have multiple local web sites mapped to different ports, it becomes difficult to
remember which port each site presents. To simplify this, you can define an alias for your web
site.
To do this, you should edit the hosts file. The hosts file is a system file which contains mappings
between IP addresses and host names. The hosts file contains lines of text consisting of an IP
address in the first text field followed by one or more host names.
To add an alias for your local web sites, add lines for each of your web site as shown in the
example below.
⁷http://httpd.apache.org/docs/current/howto/auth.html
Zend Skeleton Application 37

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.

2.12 Advanced Composer Usage


Earlier in this chapter, we have used Composer to install Zend Framework 2 library code. Now
let’s briefly describe some advanced Composer usage examples.
As we already know, the only required key in the composer.json file is require. This key tells
what packages are required by your application:

{
"require": {
"php": ">=5.3.3",
"zendframework/zendframework": ">2.2.0rc1"
}
}

2.12.1 Package Names and Versions


A package name consists of two parts: vendor name and project name. For example “zend-
framework/zendframework” package name consists of “zendframework” vendor name and
“zendframework” project name. You can search for other packages from “zendframework”
vendor through Packagist.org⁸ web site (see the figure 2.8 for an example).
A package also has an associated version number. A version number consists of major number,
minor number, optional build number, and optional stability suffix (.e.g b1, rc1). Within the
require key we specify which versions of the package are acceptable. For example, “>2.2.0rc1”
means that we can install versions greater than “2.2.0rc1” (rc1 stands for Release Candidate
1), e.g.: “2.2.0rc2”, “2.2.0”, “2.2.1” and so on. In table 2.1, possible ways of specifying acceptable
versions are presented:
⁸https://packagist.org/search/?q=zendframework
Zend Skeleton Application 38

Figure 2.8. You can search packages on Packagist.org

Table 2.1. Package Version Definitions

Definition Example Description


2.2.0 Exact version. In this example, only the version 2.2.0 can be installed.
>=2.2.0 Greater or equal version can be installed (2.2.0, 2.2.1, etc.)
>2.2.0 Greater version can be installed (2.2.1 etc.)
<=2.2.0 Lower or equal version can be installed (1.0, 1.5, 2.0.0, 2.2.0 etc.)
<2.2.0 Lower version can be installed (1.0, 1.1, 1.9, etc.)
!=2.2.0 All versions except this version can be installed.
>=2.0,<2.2.0 Any version belonging to this range of versions can be installed.
2.* Any version having major number equal to 2 can be installed (minor number
can be any).
∼2.2 Any version starting from 2.2, but lower than the next major version
(equivalent to >=2.2,<3.00).

2.12.2 Installing and Updating Packages


We’ve already used the install command to install our dependencies. As soon as you call
this command, Composer will find, download and install the dependencies to your vendor
subdirectory.
Zend Skeleton Application 39

Is it safe to install dependencies with Composer?


Well, some people may be afraid of Composer-style dependency management, because
they think someone can update the dependencies system-wide by mistake or intention-
ally, causing the web application to break. Note, that Composer never installs these
system-wide, instead it installs them into your APP_DIR/vendor/ directory.

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.

What do I do if I want to roll back to a previous version of the package?


If the update procedure resulted in unwanted problems with your system, you can roll
back by reverting the changes to your composer.lock file and issuing the install
command again. Reverting changes to composer.lock is easy if you use a version
control system, like GIT or SVN. If you don’t use a version control system, make a
backup copy of composer.lock before updating.

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.

2.12.3 Virtual Packages


Composer can be used to require some functionality to present on your system. You’ve already
seen how we require “php>=5.3.3”. PHP package is a virtual package representing PHP itself. You
can also require other stuff, like PHP extensions:
Zend Skeleton Application 40

Table 2.3. Virtual Composer Packages

Definition Example Description


php>=5.3.3 Require PHP version greater or equal than 5.3.3
ext-dom, ext-pdo-mysql Require PHP DOM and PDO MySQL extensions
lib-openssl Require OpenSSL library

You can use composer show --platform command to display a list of available virtual packages
for your machine.

2.12.4 Composer and Version Control Systems


If you are using a version control system (like SVN), you will be curious about what should
be stored in SVN: your application code only, or your application code plus all the Composer-
installed dependencies in APP_DIR/vendor directory?
In general, it is not recommended to store your Composer-dependencies under version control,
because this can make your repository really big and slow to check out and branch. Instead, you
should store your composer.lock file under version control. The composer.lock file guarantees
that everyone will install the same versions of dependencies as you have. This is useful in
development teams having more than one developer, because all developers should have the
same code to avoid unwanted issues with environment misconfiguration.

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.

3.1 PHP Namespaces


When you use classes from different libraries (or even classes from different components of
a single library) in your program, the class names may conflict. This means you can encounter
two classes having the same name, resulting in PHP interpreter error. If you’ve ever programmed
web sites with Zend Framework 1, you might remember those extra long class names like Zend_-
Controller_Abstract. The idea with long names was utilized to avoid name collisions between
different components. Each component defined its own name prefix, like Zend_ or My_.
To achieve the same goal, Zend Framework 2 uses the PHP 5.3 language feature called
namespaces. The namespaces allow to solve the name collisions between code components, and
provide you with the ability to make the long names shorter.
A namespace is a container for a group of names. You can nest namespaces into each other.
If a class or function does not define a namespace, it lives inside of the global namespace (for
example, PHP classes Exception and DateTime belong to global namespace).
A real-world example of a namespace definition (taken from ZendMvc component) is presented
below:
Web Site Operation 43

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;

Please note the leading back-slash in \Zend\Mvc\Application name. It represents the


global namespace.

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;

// Later in your code, use the short class name.


$application = new Application;
Web Site Operation 44

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 ...
}

3.2 PHP Interfaces


In PHP, interfaces allow you to define which behavior a class should have, but without providing
the implementation of such a behavior. This is also called a contract: by implementing an
interface, a class agrees to the contract terms.
In Zend Framework 2, interfaces are widely used. For example, the Application class implements
the ApplicationInterface, which defines the methods every application class must provide:

<?php
namespace Zend\Mvc;

//...

interface ApplicationInterface
{
// Retrieves the service manager.
public function getServiceManager();

// Retrieves the HTTP request object.


public function getRequest();

// Retrieves the HTTP response object.


public function getResponse();

// Runs the application.


public function run();
}
Web Site Operation 45

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.

In Zend Framework 2, by convention, interface classes should be named with


Interface suffix, like ApplicationInterface.

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;
//...

class Application implements ApplicationInterface


{
// Implement the interface's methods here

public function getServiceManager() {


//...
}

public function getRequest() {


//...
}

public function getResponse() {


//...
}

public function run() {


//...
}
}

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.

Figure 3.1. Application class diagram

3.3 PHP Class Autoloading


A web application consists of many PHP classes, and each class typically resides in a separate
file. This introduces the need of including the files.
For example, let’s assume we have the file named Application.php which contains the definition
for the \Zend\Mvc\Application class from the previous section. Before you can create an
instance of the Application class somewhere in your code, you have to include the contents
of Application.php file (you can do this with the help of require_once statement, passing it the
full path to the file):

<?php
require_once "/path/to/zend/lib/Application.php";

use Zend\Mvc\Application;

$application = new 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) {

// Class map static array.


static $classMap = array(
'Zend\\Mvc\\Application' => '/path/to/zend/dir/Zend/Mvc/Application.php',
'Application\\Module' => '/path/to/app/dir/Application/Module.php',
//...
);

// Check if such a class name presents in the class map.


if(isset(static::$classMap[$className])) {
$fileName = static::$classMap[$className];

// Check if file exists and is readable.


if (is_readable($filename)) {
// Include the file.
require $filename;
}
}
}

// Register our autoloader function.


spl_autoload_register("autoloadFunc");

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.

3.4 PSR-0 Standard


Because each library’s vendor uses its own code naming and file organization conventions, you
will have to register a different custom autoloader function per each dependent library, which
is rather annoying (and actually this is an unneeded work). To resolve this problem, the PSR-0
standard was introduced.
The PSR-0 standard¹ (PSR stands for PHP Standards Recommendation) defines the recommended
code structure that an application or library must follow to guarantee autoloader interoperability.
In two words, the standard says that:
¹https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
Web Site Operation 48

• The class namespaces should be organized in the following way:


\<Vendor Name>\(<Namespace>)*\<Class Name>
• Namespaces can have as many nesting levels as desired, but the Vendor Name should be
the top-level namespace.
• Namespaces should map to directory structure. Each namespace separator (‘\’) is converted
to a OS-specific DIRECTORY_SEPARATOR constant when loading from the file system.
• The class name is suffixed with .php extension when loading the file from the file system.

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

// "Standard" autoloader function.


function standardAutoloadFunc($className) {

// Replace special characters in class name.


$className = str_replace('\\', '/', $className);
// Format the file path.
$fileName = "path/to/zend/dir/" . $className . ".php";
// Check if file exists and is readable.
if (is_readable($fileName)) {
// Include the file.
require $fileName;
}
}

// Register the autoloader function.


spl_autoload_register("standardAutoloadFunc");

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.

Zend Framework 2 conforms to PSR-0 standard, making it possible to use standard


autoloading mechanism across all its components. It is also compatible with other PSR-
0 conforming libraries like Doctrine or Symfony 2.

3.5 HTTP Request and Response


When a site user opens a web page in a web browser’s window, the browser generates a request
message and sends it using HTTP protocol to the web server. The web server directs this HTTP
request to your web 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:

An HTTP request example

1 GET http://www.w3schools.com/ HTTP/1.1


2 Host: www.w3schools.com
3 Connection: keep-alive
4 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
5 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64)
6 Accept-Encoding: gzip,deflate,sdch
7 Accept-Language: en-US;q=0.8,en;q=0.6
8 Cookie: __gads=ID=42213729da4df8df:T=1368250765:S=ALNI_MaOAFe3U1T9Syh;
9 (empty line)
10 (message body goes here)

The HTTP request message above consists of three parts:


²http://www.w3.org/Protocols/rfc2616/rfc2616.html
Web Site Operation 50

• 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:

An HTTP response example


1 HTTP/1.1 200 OK
2 Cache-Control: private
3 Content-Type: text/html
4 Content-Encoding: gzip
5 Vary: Accept-Encoding
6 Server: Microsoft-IIS/7.5
7 Set-Cookie: ASPSESSIONIDQQRBACTR=FOCCINICEFAMEKODNKIBFOJP; path=/
8 X-Powered-By: ASP.NET
9 Date: Sun, 04 Aug 2013 13:33:59 GMT
10 Content-Length: 8434
11 (empty line)
12 (page content follows)

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.

3.6 Site Entry Script


When the Apache web server receives an HTTP request from a client browser, it executes the
APP_DIR/public/index.php file, also called the entry script.

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();

Mainly, there are three things done in it.


First, in line 7, current working directory is changed to APP_DIR. This makes it simple to define
relative file paths in your application:
Next, in line 10, PHP class autoloading is initialized. This allows to easily load any class either
located in Zend Framework library or in your application without the need for require_once
statement.
And finally, in line 13, an instance of Zend\Mvc\Application class is created. The application is
initialized with application.config.php configuration file, and, the application is run.

3.7 Events & Application’s Life Cycle


As you’ve learned from the previous section, on every HTTP request, the Zend\Mvc\Application
object is created. The application’s “life” consists of several stages.

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:

Figure 3.2. Event flow during the application’s life cycle

3.8 Application Configuration


Most of Zend Framework 2 components which are used in your web site, require configuration
(fine-tuning). For example, in the configuration file you define database connection credentials,
specify which modules are present in your application, and, optionally, provide some custom
parameters specific to your application.
You can define the configuration parameters in two levels: either at the application level, or at
the module level. At the application level you typically define parameters which control the
whole app and are common to all modules of your application. At the module level, you define
parameters which affect only this module.
Web Site Operation 53

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.

3.8.1 Application-Level Config Files


The APP_DIR/config subdirectory contains application-wide configuration files. Let’s look at this
subdirectory in more details (figure 3.3).

Figure 3.3. Configuration files

The APP_DIR/config/application.config.php file is the main configuration file. It is used by the


application on start up for determining which services should be created in the service manager
and which application modules should be loaded.
Below, a starting fragment of application.config.file is presented. You can see that the configu-
ration file is just a usual PHP nested associative array, and each component may have a specific
key in that array. You can provide inline comments for the array keys to make it easier for others
to understand what each key means.

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

Content of application.config.php file


1 <?php
2 return array(
3 // This should be an array of module namespaces
4 // used in the application.
5 'modules' => array(
6 'Application',
7 ),
8
9 // These are various options for the listeners
10 // attached to the ModuleManager
11 'module_listener_options' => array(
12 // This should be an array of paths in which
13 // modules reside. If a string key is provided,
14 // the listener will consider that a module
15 // namespace, the value of that key the specific
16 // path to that module's Module class.
17 'module_paths' => array(
18 './module',
19 './vendor',
20 ),
21
22 // An array of paths from which to glob configuration
23 // files after modules are loaded. These effectively
24 // override configuration provided by modules themselves.
25 // Paths may use GLOB_BRACE notation.
26 'config_glob_paths' => array(
27 'config/autoload/{,*.}{global,local}.php',
28 ),
29
30 // ...
31 );

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.

3.8.2 Application-Level Extra Config Files


“Extra” config files, APP_DIR/config/autoload/global.php and APP_DIR/config/autoload/local.php
files define application-wide environment-agnostic and environment-dependent parameters,
respectively. These config files are automatically loaded and recursively merged with the
module-provided config files, that’s why their directory is named autoload.
Having different config files in APP_DIR/config/autoload directory, you might have been
confused about which parameters should be put into each one. Here are some hints:

• 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.

Because the autoload/local.php file contains environment-specific parameters, in ver-


sion control system you store its “distribution template” local.php.dist. Each developer
in your team then renames the local.php.dist file into local.php and enters his own
parameters. This local.php file should not be stored under version control, because
it may contain confidential information like database credentials (username and
password), and you might want that other people do not see these.

3.8.3 Module-Level Config Files


In figure 3.3, you can see that the Application module shipped with your application has the
module.config.php file, in which you put your module-specific parameters.
Web Site Operation 56

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).

3.8.4 Combining the Configuration Files


When an application is created, module-provided configuration files and extra configuration
files from APP_DIR/config/autoload directory are merged into one big nested array, so every
configuration parameter becomes available to any piece of the web site. So, potentially, you are
able to override some parameters specified by the modules.

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 configuration files are loaded in the following order:

• 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.

3.9 Module Entry Point


Each module of the web application has the Module.php file which is some kind of entry point
for the module. This file provides the Module class with several “callback” methods. Below, the
contents of skeleton application’s Module class is presented:

Contents of Module.php file

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.

3.10 Class Autoloading in Zend Framework 2


Now that we know about PHP namespaces, class autoloading basics and application configura-
tion, we can learn in more details about how class autoloading works in a Zend Framework 2
based application.

3.10.1 Composer-provided Autoloader


Everything begins with the Composer dependency manager. When you install a package with
Composer, it automatically creates the file APP_DIR/vendor/autoloader.php, which uses the
spl_autoload_register() PHP function to register an autoloader.

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

By default, Composer uses the PSR-0 standard autoloader. To improve autoloading


performance, especially for production environment, you may want to enable class
map autoloader instead. You can do that by specifying the --optimize-autoloader
option for Composer’s install and update commands, like below:
php composer.phar install --optimize-autoloader

php composer.phar update --optimize-autoloader

3.10.2 Zend\Loader Component


Each module of the web application registers an autoloader, which makes it possible to autoload
any PHP class in your modules. This is made with the getAutoloaderConfig() method of the
Module class.

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);

// Configure the autoloader


public function setOptions($options);

// Autoload a class
public function autoload($class);

// Register the autoloader with spl_autoload registry


public function register();
}

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

Figure 3.4. ZF2 autoloader class diagram

3.10.2.1 Standard Autoloader

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 {

public function getAutoloaderConfig()


{
return array(
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
}

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

3.10.2.2 PSR-0 and Src Directory Structure

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”).

We will refer to the src directory as module’s source directory.

For example, lets look at the IndexController.php file of Application module (figure 3.5).

Figure 3.5. Skeleton application’s directory structure conforms to PSR-0 standard

As you can see, it contains the IndexController class ³ belonging to Application\Controller


namespace. To be able to follow the PSR-0 standard and use the standard autoloader with this
PHP class, we have to put it under the Application/Controller directory under the module’s
source directory.

At a first sight, the duplication of Application directory name in directory structure


would look strange and unneeded if you do not keep in mind the PSR-0 standard
requirements.

³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

3.10.2.3 Class Map Autoloader

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

3.11 Service Manager


You can imagine the web application as a set of services. For example, you can have an
authentication service responsible for logging in the site users, entity manager service responsible
Web Site Operation 63

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:

Figure 3.6. ServiceManager class inheritance

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.

Service Name Description


Application Allows to retrieve the singleton of Zend\Mvc\Application class.
ApplicationConfig Configuration array extracted from application.config.php file.
Config Merged configuration array extracted from module.config.php files
merged with autoload/global.php and autoload/local.php.
EventManager Allows to retrieve the singleton of Zend\Mvc\EventManager class. The
event manager allows to send (trigger) events and attach event listeners.
ModuleManager Allows to retrieve the singleton of Zend\Mvc\ModuleManager class. The
module manager is responsible for loading application modules.
Request The singleton of Zend\Http\Request class. Represents HTTP request
received from client.
Response The singleton of Zend\Http\Response class. Represents HTTP response that
will be sent to client.
Router The singleton of Zend\Mvc\Router\Http\TreeRouteStack. Performs URL
routing.
ServiceManager Service manager itself.
ViewManager The singleton of Zend\Mvc\View\Http\ViewManager class. Responsible for
preparing the view layer for page rendering.

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).

3.11.1 Service Locator


As you can see from figure 3.5, the service manager implements the ServiceLocatorInterface
interface. As its name suggests, this interface defines the minimum needed methods for locating
and retrieving a service from the service manager:

<?php
namespace Zend\ServiceManager;

interface ServiceLocatorInterface {

// Retrieves a registered service's instance.


public function get($name);

// Checks if such a service is registered.


public function has($name);
}

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

// Retrieve the application config array.


$appConfig = $serviceLocator->get('ApplicationConfig');
// Use it (for example, retrieve the module list).
$modules = $appConfig['modules'];

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.

3.11.2 Canonical Service Names


Different services can use different naming styles: the same currency converter service may be
registered under the different names: CurrencyConverter, currency_converter and so on. To
introduce some uniform naming convention, the service manager internally uses the canonical
service names.
A canonical service name consists of lower-case letters. The space, forward- and back-slashes,
underscore and minus sign characters are not allowed. To produce the canonical name, the
service manager uses its canonicalizeName() method, which takes the usual service name string
and produces the canonical name string.
To get the list of all canonical service names registered within the service manager, you can use
its getCanonicalNames() method, which returns an array of string pairs. The first element of the
pair is the canonical service name. The second one is the usual name of the service (the name
under which it was registered).

3.11.3 Registering a Service


When writing your web site, sometimes you will need to register your custom service in the
service manager. To register a service, you use the setService() method. Let’s create and register
the currency converter service class, which will be used, for example, on a shopping cart page to
convert EUR currency to USD:
Web Site Operation 66

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);

3.11.4 Registering Invokable Classes


What is bad with the setService() method is that you have to create the service instance
before you really need it. If you never use the service, the service instantiation will only
waste the time and memory. To resolve this issue, the service manager provides you with the
setInvokableClass() method.

<?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.

3.11.5 Registering Factories


Sometimes, service instantiation is more complex than just creating the service instance with
new operator. You may need to pass some parameters to the service’s constructor or invoke some
service methods just after construction. This complex instantiation logics can be encapsulated
inside of a factory. A factory is a PHP class responsible for creating new objects. The factory
class typically implements the FactoryInterface:

<?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{

public function createService(ServiceLocatorInterface $serviceLocator) {

// Create an instance of the class.


$service = new CurrencyConverter();

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);

// Create service with such a name.


public function createServiceWithName(
ServiceLocatorInterface $serviceLocator, $name, $requestedName);
}

The AbstractFactoryInterface interface has two methods: canCreateServiceWithName() and


createServiceWithName(). The first one is needed to test if the factory can create the service
with the certain name, and the latter one allows to actually create the service. The methods
take three parameters: service locator, canonical service name and usual service name (as it was
requested from service locator by the client).
Comparing to usual factory class, the difference is that the usual factory class can create only a
single service, but an abstract factory can dynamically create as many services as it wants.

3.11.6 Registering Service Aliases


Sometimes, you may want to define an alias for a service. The alias is like a symbolic link:
it references the already registered service. To create an alias, you use the service manager’s
setAlias() method:
Web Site Operation 69

<?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.

3.11.7 Service Manager Configuration


To automatically register a service within the service manager, typically the service_manager
key of a configuration file is used. You can put this key either inside of an application-level
configuration file or in a module-level configuration file.

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.

This service_manager key should look like below:

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:

• the services subkey (line 7) allows to register class instances;


• the invokables subkey (line 11) allows to register full class name of a service; the service
will be instantiated using lazy loading;
• the factories subkey (line 15) allows for registering a factory, which is able to create
instances of a single service;
• the abstract_factories (line 19) can be used for registering abstract factories, which are
able to register several services by name;
• the aliases subkey (line 23) provides an ability to register an alias for a service.

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.

4.1 Get the Hello World Example from GitHub


In this and in the next chapters, we will provide some code examples that you may want to
reproduce yourself. It may be difficult for a novice to write code without mistakes. If you are
stuck or can not understand why your code does not work, you can download the complete Hello
World web application from GitHub code hosting. The examples from this chapter are mostly
the part of this sample application.
To download the Hello World application, visit this page¹ and click the Download ZIP button to
download the code as a ZIP archive (see figure 4.1). When download is complete, unpack the
archive to some directory.
Then navigate to the helloworld directory containing the complete source code of the Hello
World example:

/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

4.2 Separating Business Logic from Presentation


A typical web site has three kinds of functionality: code implementing business logic, code
implementing user interaction and code rendering HTML pages (presentation). Before PHP
frameworks, programmers usually merged these three types of code in a single big PHP script
file, which made it a pain to test and maintain such a code, especially when you write a large
web site.

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

Figure 4.2. HTTP request processing in an MVC web application

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

Figure 4.3. Controller directory

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.

Table 4.1. Index controller’s typical actions

Action Method Description


IndexController::indexAction() The “index” action displays the Home page of your
site.
IndexController::aboutAction() The “about” action displays the About page of
the site. The About page contains contact and
copyright information.
IndexController::contactUsAction() The “contactUs” action displays the Contact Us
page of the site. The Contact Us page displays
the form for contacting site authors.

4.3.1 Base Controller Class


Every controller in your web site is inherited from the AbstractActionController base class.
In figure 4.4, the class inheritance diagram is presented.

Figure 4.4. Controller inheritance diagram

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:

Table 4.2. AbstractActionController’s useful methods

Method Name Description


getRequest() Retrieves the Zend\Http\Request object, which is the
representation of HTTP request data.
getResponse() Retrieves the Zend\Http\PhpEnvironment\Response object
allowing to set data of HTTP response.
getServiceLocator() This method returns the Zend\ServiceManager\ServiceLocator
interface, allowing to access all services registered in the
web application.
Model-View-Controller 78

Table 4.2. AbstractActionController’s useful methods

Method Name Description

getEventManager() Returns the Zend\EventManager\EventManager object,


allowing to trigger events and listen to events.
getEvent() Returns the Zend\Mvc\MvcEvent object, which represents
the event the controller responds to.
getPluginManager() Returns the Zend\Mvc\Controller\PluginManager object,
which can be used for registering controller plugins.
plugin($name, $options) This method allows to access certain controller plugin
with the given name.
__call($method, $params) Allows to call a plugin indirectly using the PHP __call
magic method.

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).

4.3.2 Retrieving Data from HTTP Request


In a controller’s action method, you may need to retrieve the data from the HTTP request (the
data like GET and POST variables, cookies, HTTP headers and so on). For this purpose, Zend
Framework 2 provides you with Zend\Http\Request and Zend\Http\PhpEnvironment\Response
classes, which are part of Zend\Http component.
To get the HTTP request object, inside of your action method, you can use the following code:

// Get HTTP request object


$request = $this->getRequest();

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.

Method Name Description


isGet() Checks if this is a GET request.
isPost() Checks if this is a POST request.
isXmlHttpRequest() Checks if this request is an AJAX request.
isFlashRequest() Check if this request is a Flash request.
getMethod() Returns the method for this request.
getUriString() Returns the URI for this request object as a string.
Model-View-Controller 79

Method Name 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.

4.3.3 Retrieving GET or POST Variables


To simply get a GET or POST variable from an HTTP request, you use the following code:

1 // Get a variable from GET


2 $getVar = $this->params()->fromQuery('var_name', 'default_val');
3
4 // Get a variable from POST
5 $postVar = $this->params()->fromPost('var_name', 'default_val');

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

4.3.4 Putting Data to HTTP Response


Although you typically do not interact with HTTP response data directly, you can do that with
the help of getResponse() method provided by AbstractActionController base class. Table 4.4
contains the most important methods of the Response class:

Table 4.4. Methods of Zend\Http\PhpEnvironment\Response class.

Method Name Description


fromString($string) Populate response object from string.
getCookie() Retrieves Cookie header.
setStatusCode($code) Sets HTTP status code and (optionally) message.
getStatusCode() Retrieves HTTP status code.
setReasonPhrase($reasonPhrase) Sets the HTTP status message.
getReasonPhrase() Gets HTTP status message.
getBody() Gets the body of the response.
isForbidden() Checks if the response code is 404 Forbidden.
isNotFound() Checks if the status code indicates the resource is not found.
isOk() Checks whether the response is successful.
isServerError() Checks if the response is 5xx status code.
isRedirect() Checks whether the response is 303 Redirect.
isSuccess() Checks whether the response is 200 Successful.
renderStatusLine() Renders the status line header.
toString() Renders entire response as HTTP response string.

4.4 Variable Containers


After you retrieve the data from the HTTP request (or from other data sources, for example, from
database), you would do something with that data (typically you will process the data with your
model layer) and return the data from the action method.
You can see that the indexAction() method of the Index controller returns an instance of the
ViewModel class. The ViewModel class is some kind of a variable container. All variables passed
to its constructor, will be then automatically accessible by the view script.
Let’s have some real-life example. We will create another action method in our IndexController
class, which we will call the aboutAction(). The “about” action will display the About page of
our site. In the action method, we will get the current version of the Zend Framework with the
Zend\Version component, and return the resulting variables for rendering in a view with the
help of ViewModel object:
Model-View-Controller 81

1 // Create a class name alias in the


2 // beginning of file.
3 use Zend\Version\Version;
4
5 // The "about" action
6 public function aboutAction() {
7
8 // Get current ZF version
9 $zendFrameworkVer = Version::VERSION;
10 // Fetch the latest available version of ZF
11 $latestVer = Version::getLatest();
12 // Test if newer version is available
13 $isNewerVerAvailable = Version::compareVersion($latestVer);
14
15 // Return variables to view script with the help of
16 // ViewObject variable container
17 return new ViewModel(array(
18 'zendFrameworkVer' => $zendFrameworkVer,
19 'isNewerVerAvailable' => $isNewerVerAvailable,
20 'latestVer' => $latestVer
21 ));
22 }

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

Table 4.5. Methods of the ViewModel class

Method name Description


getVariable($name, $default) Returns a variable by name (or default value if the
variable
does not exist).
setVariable($name, $value) Sets a variable.
setVariables($variables, $overwrite) Sets a group of variables, optionally overwriting the
existing ones.
getVariables() Returns all variables as an array.
clearVariables() Removes all variables.

4.5 Controller Registration


All controller classes belonging to a module should be registered in the module.config.php
configuration file as follows:

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

4.6 When to Create a New Controller?


When your site grows in size, you should create new controller classes instead of putting all
actions to IndexController. The Index controller is used for defining the actions which work
for your entire site. It is recommended to create new controller class for each model (or for most
important ones) of your business logic domain.
For example, you can create UserController to manage users of your site. This controller would
have the default “index” action for displaying the page with all users, “add” action for adding a
new user, “edit” action for editing user’s profile and “delete” action for deleting the user.
By analogy, you would create PurchaseController and its actions to manage the purchases
of your products and implementing the shopping cart, DownloadController and its actions to
manage file downloads for the site, etc.

4.7 Controller Plugins


A controller plugin is a class which extends the functionality of all controllers in some way.

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.

Table 4.6. Standard Controller Plugins

Standard Plugin Class Description


Params Allows to retrieve variables from HTTP request,
including GET and POST variables.
Url Allows to generate absolute or relative URL addresses
from inside controllers.
Layout Gives access to layout view model for passing data to
layout template.
Identity Returns the identity of the user who has logged into the
web site.

²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

Table 4.6. Standard Controller Plugins

Standard Plugin Class Description

FlashMessenger Allows to define “flash” messages which are stored in


session and can be displayed on a different web page.
Redirect Allows to redirect the request to another controller’s
action method.
PostRedirectGet Redirects the POST request, converting all POST variables
to GET ones.
FilePostRedirectGet Redirects the POST request, preserving uploaded files.

Inside of the controller’s action method, you access a plugin in the following way:

1 // Access Url plguin


2 $urlPlguin = $this->url();
3
4 // Access Layout plugin
5 $layoutPlugin = $this->layout();
6
7 // Access Redirect plugin
8 $redirectPlugin = $this->redirect();

4.7.1 Writing Own Controller Plugin


In your web sites, you will definitely need to create custom controller plugins. For example,
assume you need that all your controller classes to be able to check whether a site user is allowed
to access certain controller action. This can be implemented with the AccessPlugin class.
The controller plugin should be derived from the AbstractPlugin class. Plugins typically live in
their own namespace Plugin, which is nested in Controller namespace:

<?php
namespace Application\Controller\Plugin;

use \Zend\Mvc\Controller\Plugin\AbstractPlugin;

// Plugin class
class AccessPlugin extends AbstractPlugin {

// This method checks whether user is allowed


// to visit the page
public function checkAccess($actionName){
// ...
}
}
Model-View-Controller 85

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(
// ...

'controller_plugins' => array(


'invokables' => array(
'Access' => 'Application\Controller\Plugin\AccessPlugin',
)
),

// ...
);

After that, you’ll be able to access your custom plugin from all of your controller’s actions in
this way:

// Check if site user is allowed to visit the "index" page


$isAllowed = $this->access()->checkAccess('index');

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

Figure 4.5. View directory

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; ?>

Figure 4.6. Context Menu

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):

Figure 4.7. Context Menu


Model-View-Controller 89

Figure 4.8. About Page

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.

4.9 View Helpers


A view helper is typically a (relatively) simple PHP class whose goal is to render some part of a
view. You can invoke view helpers from any view template. With view helpers, you can create
reusable widgets (like menus, navigation bars, etc.) for your web pages.

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

Table 4.7. Standard View Helpers

Standard Plugin Class Description


BasePath Allows to retrive the base path to the web application,
which is the absolute path to APP_DIR.
Url Allows to generate absolute or relative URL addresses
from inside view templates.
ServerUrl Retrieves the current request’s URL.
Doctype Helper for setting and retrieving the doctype HTML element
of the web page.
PageTitle Helper for setting the title HTML element
of the web page.
HtmlList Helper for generating ordered and unordered HTML lists.
ViewModel Helper for storing and retrieving the view model
Layout Retrieves the layout template view.
Partial Allows to render a “partial” view template.
InlineScript Helper for setting and retrieving script elements for
inclusion in HTML body section.
Identity View helper to retrive the authenticated user’s identity.
FlashMessenger Allows to retrieve the “flash” messages stored in
session.

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

Figure 4.9. Setting page title for the About page

We will discuss the view helpers in more details and provide more usage examples in
Chapter 6.

4.10 View Template Names & View Resolver


When you return data with the ViewModel variable container from your controller’s action
method, Zend Framework somehow knows the name of the corresponding view template file and
its location. For example, for your IndexController’s aboutAction() method, ZF2 automatically
uses the about.phtml view template.
It may be a surprise, but the ViewModel partially contributes into view template resolving.
Actually the ViewModel class is more than just a variable container. Additionally, it allows
to specify which view template should be used for page rendering. The summary of methods
provided for this purpose is shown in table 4.8.

Table 4.8. Methods of the ViewModel class for setting and retrieving the view template name

Method name Description


setTemplate() Sets the view template name.
getTemplate() Returns 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:

1 // Index action renders the Home page of your site.


2 public function indexAction() {
3
4 // Use a different view template for rendering the page.
5 $viewModel = new ViewModel();
6 $viewModel->setTemplate('application/index/about');
7 return $viewModel;
8 }

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.

4.11 Disabling the View Rendering


Sometimes, you would need to disable the default view rendering. To do that, just return the
Response object from the the controller’s action.

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

47 // Write file content


48 $fileContent = file_get_contents($path);
49 if($fileContent!=false) {
50 $response->setContent($fileContent);
51 } else {
52 // Set 500 Server Error status code
53 $this->getResponse()->setStatusCode(500);
54 return;
55 }
56
57 // Return Response to avoid default view rendering
58 return $this->getResponse();
59 }
60 }

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.

4.12 Error Pages


When a page could not be found or some other error happens inside of your web application,
a standard error page is displayed. The appearance of the error page is controlled by the error
templates. There are two error templates: error/404 which is used for “404 Page Not Found”
Model-View-Controller 96

error (shown in figure 4.10), and error/index which is displayed when an unhandled exception
is thrown somewhere inside of the application.

Figure 4.10. 404 Error Page

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 );

• The display_not_found_reason parameter controls whether to display the detailed infor-


mation about the “Page not Found” error.
• The display_exceptions parameter defines whether to display information about an
unhandled exception and its stack trace.
• The not_found_template defines the template name for the 404 error.
• The exception_template specifies the template name for the unhandled exception error.

You typically set the display_not_found_reason and display_exceptions parameters to


false in production systems, because you don’t want site visitors see the details about
errors in your site. However, you will still be able to retrieve the detailed information
from Apache’s error.log file.

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.

4.14 Model Types


In Zend Framework 2, there is no single Model directory for storing the model classes, as you
could assume. Instead, by convention, models are further subdivided into the following types,
and each type is stored in its own subdirectory (see table 4.9):
Model-View-Controller 98

Table 4.9. Model Types and their Location

Model Type Directory


Entities APP_DIR/module/Application/src/Application/Entity

Value Objects APP_DIR/module/Application/src/Application/ValueObject

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:

1 // The User entity represents a site visitor


2 class User {
3
4 // Properties
5 private $login; // e.g. "admin"
6 private $title; // e.g. "Mr."
7 private $firstName; // e.g. "John"
8 private $lastName; // e.g. "Doe"
9 private $country; // e.g. "USA"
10 private $city; // e.g. "Paris"
11 private $zipCode; // e.g. "10543"
12 private $address; // e.g. "Jackson rd."
13
14 // Behaviors
15 public getLogin() {
16 return $this->login;
17 }
18
19 public setLogin($login) {
Model-View-Controller 99

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”.

4.14.2 Value Objects


Value objects are a kind of model for which the identity is not as important as for entities. A
value object is usually a small class identified by all of its attributes. It does not have an identifier
attribute. Value objects typically have getter methods, but do not have setters (value objects are
immutable).
For example, a model wrapping a money amount can be treated as a value object:

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

// The Email message value object


class EmailMessage {
private $recipient;
private $subject;
private $text;

// Constructor
public function __construct($recipient, $subject, $text) {
$this->recipient = $recipient;
$this->subject = $subject;
$this->text = $text;
}

// Getters
public function getRecipient() {
return $this->recipient;
}

public function getSubject() {


return $this->subject;
}

public function getText() {


return $this->text;
}
}

// The Mailer service, which can send messages by E-mail


class Mailer {

public function sendMail($message) {


// Use PHP mail() function to send an E-mail
if(!mail($message->getRecipient(), $message->getSubject(),
$message()->getText()))
{
// Error sending message
return false
}

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.

// The CaptchaFactory class is used to create CAPTCHA


// image for a web form.
class CaptchaFactory {

// Creates a CAPTCHA object based on type parameter


public static function createCaptcha($type) {
if($type=='Image')
return new ImageCaptcha();
else if($type=='ReCaptcha')
return new ReCaptcha();
else
throw new Exception('Unknown captcha type');
}
}

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

4.15 Determining the Correct Model Type

Isn’t it confusing to have so many model types?


Well, yes and no. At first, it may be a little difficult to determine the correct model
type, but as soon you improve your skills, you will be able to do that intuitively. Just
remember that model types improve the structure of your domain models.

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:

• Your model class is definitely a Service


– if you call it from your controller class
– if it has no state (private attributes)
– if you think the best name for it ends with “er”: suffix, like FileUploader or
VersionChecker
• Your model class is an Entity:
– if your model is stored in a database
– if it has an ID attribute
– if it has both getters and setters methods
• Your model class is a ValueObject:
– if changing any attribute would make the model completely different
– if your model has getters, but not setters (immutable)
• Your model is a Repository:
– if it works with a database to store and retrieve entities
• Your model is a Factory:
– if it can create other objects and can do nothing else

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.

Hmm… what if I just store all my models in a single Model directory?


Of course, you can, if you strongly wish. But, when you use Doctrine ORM library, you
will notice that it utilizes DDD principles as well, so using DDD makes your application
well-organized.
Model-View-Controller 104

4.16 Skinny Controllers, Fat Models, Simple Views


When developing a web site using Model-View-Controller pattern, there is a risk of misunder-
standing the role of controllers, views and models. This results in making the controllers huge
and models small, which in turn makes it difficult to test and support your application. This
section’s goal is to give you a general understanding of what code may be placed in a controller
class, what code may be placed in a view template, and what code may be placed in a model
class.

4.16.1 Skinny Controllers


The idea behind the term “skinny controller” is that typically, in your controller classes, you put
only the code that:

• 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.

A controller class should avoid:

• containing complex business logic, which is better kept in model classes;


• containing any HTML or any other presentational markup code. This is better be put in
view templates.

For an example of a “skinny” controller, look at the CurrencyConverterController class below.


This controller provides the “convert” action method whose goal is to convert an amount of
money from EUR to USD currency. The user passes the amount of money through the “amount”
GET variable.

1 class CurrencyConverterController extends AbstractActionController {


2
3 public function convertAction() {
4
5 // Get the money amount from GET
6 $amount = (float)$this->params()->fromQuery('amount', -1);
7 if($amount==-1) {
8 // Money amount is missing
9 $this->getResponse()->setStatusCode(404);
10 return;
11 }
12
13 // Create CurrencyConverter model
Model-View-Controller 105

14 $currencyConverter = new CurrencyConverter();


15 $convertedAmount = $currencyConverter->convertEURtoUSD($amount);
16
17 return new ViewModel(array(
18 'amount'=>$amount,
19 'convertedAmount'=>$convertedAmount
20 ));
21 }
22 }

The controller’s action method above does the following:

• 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.

4.16.2 Fat Models


Because you need to keep your controllers as thin as possible, most of the business logic of your
application should be put into model classes. In a properly designed Model-View-Controller
application, models look “huge”. A model class may contain the code which:

• 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.

In a model class you are not recommended to:

• 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.

4.16.3 Simple View Templates


Because most of the logic is stored in models, your view templates should be as simple as possible
to produce the presentation of the data passed through the variable container. In a view template,
you may:

• Keep static HTML markup code.


• Retrieve the data from a variable container and echo them to PHP output stream.
• If a controller passed a certain model through a variable container, poll the model for data
(e.g. you can retrieve table rows from a database table and render them).
• Contain simple PHP flow control operations, like if, foreach, switch and so on. This
allows to vary the presentation depending on variables passed by the controller.

The view template is not recommended to:

• Access data from the HTTP request and PHP variables.


• Create models, manipulate them and modify the state of the application.

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.

5.1 URL Structure


To better understand routing, we first need to look at the URL structure. A typical URL from an
HTTP request consists of segments. The segments are URL parts delimited by slash characters
(‘/’): there are scheme, host name, path and query segments.
For example, let’s look at the URL “http://site1.yourserver.com/path/to/page?query=Search”
(figure 5.1).

Figure 5.1. Typical URL structure

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

5.2 Route Types


Routing is a mechanism which allows to map HTTP request to the controller. With routing,
ZF2 knows which of the controller’s action method to execute as the result of the request. For
example, you can map “http://localhost/” URL to IndexController::indexAction() method or
“http://localhost/about” URL to IndexController::aboutAction() method.
A typical routing rule has the name, type and options. The name is used to uniquely identify
the rule. The type defines the name of the PHP class which implements the algorithm used for
comparing the URL string. The options is an array that includes the route string which should
be compared against the URL string, and several parameters called the defaults.
In general, the routing algorithm may use any data from HTTP request for matching the route.
However, typically, it takes only the URL string (or its substring) as input. The algorithm then
compares the URL with the route, and if the URL string matches the route, returns several
parameters, including the controller’s name and action method’s name, and possibly others.
These parameters may be either hard-coded in a configuration file or grabbed from the URL
string. If a certain parameter cannot be retrieved from the URL, its default value is returned.
There are several standard route types provided by Zend Framework 2 (shown in table 5.1). These
route types are implemented as classes living in the Zend\Mvc\Router\Http namespace.

Table 5.1. Route Types

Route Type Description


Literal Exact matching against a path part of a URL.
Segment Matching against a path segment (or several segments) of a URL.
Regex Matching the path part of a URL against a regular expression template.
Wildcard Matching the path part of a URL against a key/value pattern.
Hostname Matching the host name against some criteria.
Scheme Matching URL scheme against some criteria.
Method Matching an HTTP method (e.g. GET, POST, etc.) against some criteria.

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

5.3 Combining Route Types


Routes may be combined with the help of “aggregate” route types (shown in table 5.2). The
compound route types allow to define arbitrarily complex URL mapping rules.

Table 5.2. Aggregate Route Types

Route Type Description


SimpleRouteStack Aggregates different route types in a list with priorities.
TreeRouteStack Aggregates different route types in a tree-like structure.
Part Aggregates different route types in a subtree.
Chain Aggregates different route types in a chain (degenerated subtree).

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.

Figure 5.2. 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.

5.3.1 Simple Route Stack


The SimpleRouteStack allows to combine different route types in a priority list. For an example
of such a list, look at the route stack in the left part of figure 5.3. The example list contains several
Literal routes and several Segment routes.
When matching against the HTTP request, the SimpleRouteStack walks through the list of
routes and tries to match each route in turn. Each route in the list has a priority; the routes
with the higher priority are visited first. The lookup is finished once some route matches the
HTTP request. If none of the routes match, the “not found” error is raised.

Figure 5.3. An example of Simple Route Stack (left) and Tree Route Stack (right)

5.3.2 Tree Route Stack


The TreeRouteStack class extends the SimpleRouteStack class, which means it can organize
the routes in a priority list, plus it provides an ability to nest routes in subtrees and chains. An
example tree route stack is presented in the right part of figure 5.3. The list contains of one
Literal route, a chain of Literal and Segment routes, and a subtree consisting of two branches:
URL Routing 111

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.

Figure 5.4. An example of nested route matching

5.4 Routing Configuration


You typically do not create the route stack (or tree) yourself, instead you provide the instructions
for ZF2 on how to do that. The routing configuration for a module is stored in module.config.php
configuration file:

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.

5.4.1 Configuration for Simple Routes


Configuration for each routing rule under the routes subkey may have the following format:

'<route_name>' => array(


'type' => '<route_class>',
'priority' => <priority>,
'options' => array(
'route' => '<route>',
'defaults' => array(
//...
),
),
),

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.

5.4.2 Configuration for Nested Routes


To organize routes in a subtree, you add the child_routes key to the route definition, and add
your child routes under that key, like below:
²LIFO (stands for Last In, First Out) is used to organize items in a stack, where the topmost item, which is added last, is taken out first.
URL Routing 113

'<route_name>' => array(


'type' => '<route_class>',
'priority' => <priority>,
'options' => array(
//...
),
'child_routes' => array(
// Add child routes here.
// ...
)
),

If you need to organize the routes in a chain (degenerated subtree), you add the chain_routes
key to your route configuration:

'<route_name>' => array(


'type' => '<route_class>',
'priority' => <priority>,
'options' => array(
//...
),
'chain_routes' => array(
// Add chained routes here.
// ...
)
),

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.

5.4.3 Default Routing Configuration in Zend Skeleton


Application
Now that you know how to configure routes and organize them in a compound structures, let’s
look at the real life example. In a fresh Zend Skeleton Application, the routing configuration
looks like below:
URL Routing 114

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:

Figure 5.5. Default route stack in the Skeleton Application

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.

5.5 Literal Route Type


With Literal route type, the route match is achieved only when you have the exact literal match
of the route string against the URL path. You typically use the Literal type for URLs which should
be short and memorable, like ‘/about’ or ‘/news’.
Below, the definition of the route named “home” is presented. The “home” route is usually
mapped to the “index” action of the IndexController and points to the Home page of your
site:
URL Routing 116

1 'home' => array(


2 'type' => 'Zend\Mvc\Router\Http\Literal',
3 'options' => array(
4 'route' => '/',
5 'defaults' => array(
6 'controller' => 'Application\Controller\Index',
7 'action' => 'index',
8 ),
9 ),
10 ),

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:

'about' => array(


'type' => 'Literal',
'options' => array(
'route' => '/about',
'defaults' => array(
'controller' => 'Application\Controller\Index',
'action' => 'about',
),
),
),

If you now open the “http://localhost/about” URL in your web browser, you should see the About
page.

5.6 Segment Route Type


The Segment route type allows for matching the route string against one or several URL path
segments.
URL Routing 117

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:

1 'barcode' => array(


2 'type' => 'Segment',
3 'options' => array(
4 'route' => '/barcode[/:type/:label]',
5 'constraints' => array(
6 'type' => '[a-zA-Z][a-zA-Z0-9_-]*',
7 'label' => '[a-zA-Z0-9_-]*'
8 ),
9 'defaults' => array(
10 'controller' => 'Application\Controller\Index',
11 'action' => 'barcode',
12 ),
13 ),
14 ),

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

1 // Add name alias in the beginning of the file


2 use Zend\Barcode\Barcode;
3
4 // ...
5
6 // The "barcode" action
7 public function barcodeAction() {
8
9 // Get parameters from route.
10 $type = $this->params()->fromRoute('type', 'code39');
11 $label = $this->params()->fromRoute('label', 'HELLO-WORLD');
12
13 // Set barcode options.
14 $barcodeOptions = array('text' => $label);
15 $rendererOptions = array();
16
17 // Create barcode object
18 $barcode = Barcode::factory($type, 'image',
19 $barcodeOptions, $rendererOptions);
20
21 // The line below will output barcode image to standard
22 // output stream.
23 $barcode->render();
24
25 // Return Response object to disable default view rendering.
26 return $this->getResponse();
27 }

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.

Zend\Barcode is an auxiliary component used for generation of various barcode


images. For additional information about this component, please refer to the corre-
sponding section of Zend Framework reference manual.

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

Figure 5.6. An example barcode image

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

Figure 5.7. Barcode types

³PHP GD extension allows to create image files in different formats (like JPEG, PNG, GIF, etc.)
URL Routing 120

5.7 Regex Route Type


The regular expression route type (Regex) is useful if you have URLs which can be matched
against a regular expression.
For example, assume you want to create a simple documentation system for your web site. The
documentation would consist of “static” pages mapped to URLs like /doc/<page_name>.html.

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:

1 'doc' => array(


2 'type' => 'Zend\Mvc\Router\Http\Regex',
3 'options' => array(
4 'regex' => '/doc(?<page>\/[a-zA-Z0-9_\-]+)\.html',
5 'defaults' => array(
6 'controller' => 'Application\Controller\Index',
7 'action' => 'doc',
8 ),
9 'spec'=>'/doc/%page%.html'
10 ),
11 ),

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

1 public function docAction() {


2
3 $pageTemplate = 'application/index/doc'.
4 $this->params()->fromRoute('page', 'documentation.phtml');
5
6 $filePath = __DIR__.'/../../../view/'.$pageTemplate.'.phtml';
7 if(!file_exists($filePath) || !is_readable($filePath)) {
8 $this->getResponse()->setStatusCode(404);
9 return;
10 }
11
12 $viewModel = new ViewModel();
13 $viewModel->setTemplate($pageTemplate);
14
15 return $viewModel;
16 }

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):

Figure 5.8. “Static” Page

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.

5.8 Wildcard Route Type


The Wildcard route type is designed specially for URLs consisting of several key/value pairs.
These URLs usually look like “http://localhost/key1/value1/key2/value2[/key3/value3 …]”. The
URL may contain variable count of name/value pairs. Because the Wildcard route type may
consume all segments up to the end of the URL, it should not have child routes.
URL Routing 123

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:

1 public function blogAction() {


2 // Get parameters from the route.
3 $year = $this->params()->fromRoute('year', null);
4 $month = $this->params()->fromRoute('month', null);
5 $name = $this->params()->fromRoute('name', null);
6
7 if($name!=null)
8 echo 'You requested to see the blog post named "'.$name.'"';
9 else
10 echo 'You requested to see all blog posts';
11
12 if($year!=null && $month!=null)
13 echo ' published in '.$month.' '.$year;
14 else if($year!=null)
15 echo ' published in '.$year;
16 else
17 echo ' published in the past';
18
19 // Suppress default view rendering.
20 return $this->getResponse();
21 }

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

1 'blog' => array(


2 'type' => 'Literal',
3 'options' => array(
4 'route' => '/blog',
5 'defaults' => array(
6 ),
7 ),
8 'may_terminate' => false,
9 'child_routes' => array(
10 'wildcard' => array(
11 'type' => 'Zend\Mvc\Router\Http\Wildcard',
12 'options' => array(
13 'key_value_delimiter' => '/',
14 'param_delimiter' => '/',
15 'defaults' => array(
16 'controller' => 'Application\Controller\Index',
17 'action' => 'blog',
18 ),
19 ),
20 ),
21 ),
22 ),

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”.

5.9 Other Route Types


The Hostname, Scheme, and Method route types are used less commonly compared to the route
types mentioned previously.
The Hostname route type can be used, for example, if you develop a content management system
(CMS) ⁵ engine, which should serve several web sites at once, each site using a different sub-
domain. In that case you will define the Hostname route as the parent, and nest child routes of
other types inside of it:

⁵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

1 'routename' => array(


2 'type' => 'Zend\Mvc\Router\Http\Hostname',
3 'options' => array(
4 'route' => ':subdomain.yourserver.com',
5 'constraints' => array(
6 'subdomain' => '[a-zA-Z][a-zA-Z0-9_-]*'
7 ),
8 'defaults' => array(
9 ),
10 ),
11 'child_routes'=>array(
12 //...
13 ),
14 ),

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:

// An example of an action that uses parameters returned by


// Hostname route.
public function someAction() {
// Get the 'subdomain' parameter from the route.
$subdomain = $this->params()->fromRoute('subdomain', null);

// Use different logic based on sub-domain.


//...

// Render the view template.


return new ViewModel();
}

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

1 'routename' => array(


2 'type' => 'Zend\Mvc\Router\Http\Scheme',
3 'options' => array(
4 'scheme' => 'https',
5 'defaults' => array(
6 'https' => true,
7 ),
8 ),
9 'child_routes'=>array(
10 //...
11 ),
12 ),

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:

1 'routename' => array(


2 'type' => 'Zend\Mvc\Router\Http\Method',
3 'options' => array(
4 'verb' => 'post',
5 'defaults' => array(
6 ),
7 ),
8 'child_routes'=>array(
9 //...
10 ),
11 ),

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.)

5.10 Extracting Parameters from Route


On route match, the router (top-level route class) returns some parameters: the “defaults” (pa-
rameters listed in the defaults section of routing configuration) plus any wildcard parameters
extracted from URL string.
In your controller, you will often need to retrieve these parameters. We already did this in the
examples above. In this section, we will give some summary.
URL Routing 127

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() {

// Get the single 'id' parameter from route.


$id = $this->params()->fromRoute('id', -1);

// Get all route parameters at once as an array.


$params = $this->params()->fromRoute();

//...
}

5.10.1 Retrieving the RouteMatch and the Router Object


On route match, the router class internally creates an instance of Zend\Mvc\Router\RouteMatch
class, providing the methods for extracting the matched route name and parameters extracted
from route. The useful methods of the RouteMatch class are listed in table 5.3:

Table 5.3. ZendMvcRouterRouteMatch class methods

Method Name Description


getMatchedRouteName() Gets the name of matched route.
getParams() Get all parameters.
getParam($name, $default) Get a specific parameter.

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:

// Call this inside of your action method


// to retrieve the RouteStackInterface for the router class.
$router = $this->getEvent()->getRouter();

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.

5.11 Generating URLs from Route


The main task of any route class is to compare the route string with the request URL and on
match return 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 can be
used in your controller action methods for generating URLs, for example, for redirecting a user
to another page. It can also be used inside view templates for generating hyperlinks.

5.11.1 Generating URLs in View Templates


Your web pages usually contain hyperlinks to other pages. These links may point either to a
page internal to your site or to a page on another site. A hyperlink is represented by <a> HTML
URL Routing 129

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:

1 <!-- A hyperlink to Home page -->


2 <a href="<?php echo $this->url('home'); ?>">Home page</a>
3
4 <!-- A hyperlink to About page -->
5 <a href="<?php echo $this->url('about'); ?>">About page</a>

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:

<!-- A hyperlink to Home page -->


<a href="/">Home page</a>

<!-- A hyperlink to About page -->


<a href="/about">About page</a>

5.11.1.1 Passing Parameters

If a route uses some variable parameters, you should pass them to the Url view helper as the
second argument:
URL Routing 130

1 <!-- A hyperlink to About page -->


2 <a href="<?php echo $this->url('application/default',
3 array('controller' => 'index', 'action' => 'about')); ?>" >
4 About page </a>
5
6 <!-- A hyperlink to Barcode image -->
7 <a href="<?php echo $this->url('application/default', array(
8 'controller' => 'index', 'action' => 'barcode',
9 'type' => 'code39', 'text' => 'HELLO-WORLD')); ?>" >
10 Barcode image </a>

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:

<!-- A hyperlink to About page -->


<a href="/application/index/about" > About page </a>

<!-- A hyperlink to Barcode image -->


<a href="/application/index/barcode/code39/HELLO-WORLD" > Barcode image </a>

As another example, let’s try to generate a URL for our Regex route (the one which serves our
“static” pages):

<!-- A hyperlink to Introduction page -->


<a href="<?php echo $this->url('doc', array('page'=>'introduction')); ?>">
Introduction </a>

This will generate the following HTML markup:

<!-- A hyperlink to Introduction page -->


<a href="/doc/introduction.html"> Introduction </a>

5.11.1.2 Generating Absolute URLs

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

1 <!-- A hyperlink to Home page -->


2 <a href="<?php echo $this->url('home', array(),
3 array('force_canonical' => true)); ?>" > Home page </a>
4
5 <!-- A hyperlink to About page -->
6 <a href="<?php echo $this->url('application/default', array(
7 'controller' => 'index', 'action' => 'about'),
8 array('force_canonical' => true)); ?>" > About page </a>

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:

<!-- A hyperlink to Home page -->


<a href="http://localhost/" > Home page </a>

<!-- A hyperlink to About page -->


<a href="http://localhost/application/index/about" > About page </a>

5.11.1.3 Specifying Query Part

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:

<a href="<?php echo $this->url('search', array(),


array('force_canonical' => true,
'query'=>array('q'=>'topic', 'count'=>10))); ?>" >
Search </a>

In the code above, we specified the query option, which is the array containing name⇒value
pairs of the query parameters.

5.11.2 Generating URLs in Controllers


You can generate URLs inside your controller’s action methods using the Url controller plugin.
To generate a URL, you call the Url controller plugin’s fromRoute() method, as in the example
below:
URL Routing 132

1 // An example action method


2 public function someAction() {
3
4 // Generate a URL pointing to the Home page ('/')
5 $url1 = $this->url()->fromRoute('home');
6
7 // Generate an absolute URL pointing to the About page
8 // ('http://localhost/application/index/about')
9 $url2 = $this->url()->fromRoute('application/default',
10 array('controller'=>'index', 'action'=>'about'),
11 array('force_canonical'=>true));
12 }

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.

5.11.3 URL Encoding


When generating URLs either with the Url view helper or with the Url controller plugin, you
should remember that URLs may only contain “safe” characters from ASCII character set. Thus,
if you pass the parameter containing unsafe characters, these characters will be replaced with
the sequence of the percentage character and two digits.
For example, let’s try to generate a URL for our Regex route and pass it the “page” parameter
with the value “/chapter1/introduction”.

<!-- A hyperlink to Introduction page -->


<a href="<?php echo $this->url('doc',
array('page'=>'chapter1/introduction')); ?>">
Introduction </a>

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:

<!-- A hyperlink to Introduction page -->


<a href="/doc/chapter1%2Fintroduction.html"> Introduction </a>

Unfortunately, this hyperlink is unusable, because it won’t match our Regex route.
URL Routing 133

5.12 Writing Own Route Type


Although ZF2 provides you with many route types, in some situations, you will need to write
your own route type.
One example of the need for such a custom route type is when you have to define the URL
mapping rules dynamically. Usually, you store the routing rules configuration in module’s config
file, but in some CMS systems you will have documents stored in the database. For such a system,
you would need to develop a custom route type which would connect to the database and perform
route matching against the data stored in the database. You cannot store this information in
config file, because new documents are created by system administrators, not programmers.

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:

Table 5.4. RouteInterface methods

Method Name Description


factory($options) Static method for creation of the route class.
match($request) Method which performs match against the HTTP request data.
assemble($params, $options) Method for generating URL by route parameters.
getAssembledParams() Method for retrieving parameters that were utilized for URL
generation.

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.

5.12.2 Custom Route Class


To demonstrate the creation of a custom route type, let’s improve our previous approach to
building the simple documentation system with Regex route type. The disadvantage of the Regex
route type is that you cannot organize the static pages in a hierarchy by creating subdirectories
under the doc directory (when generating an URL for such a page, the slash directory separator
URL Routing 134

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:

Figure 5.9. StaticRoute.php file


URL Routing 135

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:

1 'static' => array(


2 'type' => '\Application\Service\StaticRoute',
3 'options' => array(
4 'dir_name' => __DIR__ . '/../view',
5 'template_prefix' => 'application/index/static',
6 'filename_pattern' => '/[a-z0-9_\-]+/',
7 'defaults' => array(
8 'controller' => 'Application\Controller\Index',
9 'action' => 'static',
10 ),
11 ),
12 ),
URL Routing 141

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:

1 public function staticAction() {


2
3 // Get path to view template from route params
4 $pageTemplate = $this->params()->fromRoute('page', null);
5 if($pageTemplate==null) {
6 $this->getResponse()->setStatusCode(404);
7 return;
8 }
9
10 // Render the page
11 $viewModel = new ViewModel();
12 $viewModel->setTemplate($pageTemplate);
13 return $viewModel;
14 }

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):

Figure 5.10. Static pages

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).

Figure 5.11. Help page


URL Routing 143

Figure 5.12. Introduction page

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.

6.1 About CSS Stylesheets and Twitter Bootstrap


In a ZF2-based web site, for defining the visual appearance and style of the web pages, CSS
stylesheets are utilized. These CSS ¹ files are typically stored in APP_DIR/public/css directory.
Because the CSS rules may be rather complex and require laborious adjustment and the skills
of a designer, they can be separated in a “library” (framework). Analogous to PHP frameworks,
CSS frameworks allow for code reusability.
Today, several CSS frameworks exist on the market, and one of them is Twitter Bootstrap² (or
shortly, the Bootstrap). Originally designed at Twitter to unify the appearance of their own
web tools, Bootstrap has became a popular CSS framework, allowing to make your web site
professionally looking and visually appealing, even if you don’t have advanced designer skills
and without the need of creating basic CSS rules (but, of course you can define your own custom
CSS rules on top of Bootstrap to customise your site’s appearance). Bootstrap is freely distributed
under the Apache License v.2.0³.

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.

Generally, the Bootstrap does the following things:


¹If you are new to CSS, please refer to the excellent W3Schools CSS tutorial by visiting this link.
²http://twitter.github.io/bootstrap/
³http://www.apache.org/licenses/LICENSE-2.0.html
⁴http://getbootstrap.com/
Page Appearance and Layout 145

• 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

6.2 Page Layout in Zend Framework 2


Pages of your web site typically have some common structure that can be shared among them.
For example, a typical page has the <!DOCTYPE> declaration to identify the HTML document, and
the <head> and <body> elements:

Typical page structure

<!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).

Figure 6.2. Content placeholder in layout template

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):

Figure 6.3. Layout directory

1 <?php echo $this->doctype(); ?>


2
3 <html lang="en">
4 <head>
5 <meta charset="utf-8">
6 <?php echo $this->headTitle('ZF2 '.
7 $this->translate('Skeleton Application'))
8 ->setSeparator(' - ')
9 ->setAutoEscape(false)
10 ?>
11
12 <?php echo $this->headMeta()
13 ->appendName('viewport', 'width=device-width, initial-scale=1.0')
14 ->appendHttpEquiv('X-UA-Compatible', 'IE=edge')
15 ?>
16
17 <!-- Le styles -->
18 <?php
19 echo $this->headLink(array('rel' => 'shortcut icon',
20 'type' => 'image/vnd.microsoft.icon',
21 'href' => $this->basePath().'/img/favicon.ico'))
22 ->prependStylesheet($this->basePath().'/css/style.css')
23 ->prependStylesheet($this->basePath().'/css/bootstrap-theme.min.css')
24 ->prependStylesheet($this->basePath().'/css/bootstrap.min.css') ?>
25
26 <!-- Scripts -->
Page Appearance and Layout 148

27 <?php echo $this->headScript()


28 ->prependFile($this->basePath().'/js/bootstrap.min.js')
29 ->prependFile($this->basePath().'/js/jquery.min.js')
30 ->prependFile($this->basePath().'/js/respond.min.js', 'text/javascript'\
31 ,
32 array('conditional' => 'lt IE 9',))
33 ->prependFile($this->basePath().'/js/html5shiv.js', 'text/javascript',
34 array('conditional' => 'lt IE 9',));
35 ?>
36
37 </head>
38 <body>
39 <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
40 <div class="container">
41 <div class="navbar-header">
42 <button type="button" class="navbar-toggle" data-toggle="collapse"
43 data-target=".navbar-collapse">
44 <span class="icon-bar"></span>
45 <span class="icon-bar"></span>
46 <span class="icon-bar"></span>
47 </button>
48 <a class="navbar-brand" href="<?php echo $this->url('home') ?>">
49 <img src="<?php echo $this->basePath('img/zf2-logo.png') ?>"
50 alt="Zend Framework 2"/>&nbsp;
51 <?php echo $this->translate('Skeleton Application') ?>
52 </a>
53 </div>
54 <div class="collapse navbar-collapse">
55 <ul class="nav navbar-nav">
56 <li class="active"><a href="<?php echo $this->url('home') ?>">
57 <?php echo $this->translate('Home') ?></a>
58 </li>
59 </ul>
60 </div><!--/.nav-collapse -->
61 </div>
62 </nav>
63 <div class="container">
64 <?php echo $this->content; ?>
65 <hr>
66 <footer>
67 <p>
68 &copy; 2005 - <?php echo date('Y') ?> by Zend Technologies Ltd.
69 <?php echo $this->translate('All rights reserved.') ?>
70 </p>
71 </footer>
72 </div> <!-- /container -->
Page Appearance and Layout 149

73 <?php echo $this->inlineScript() ?>


74 </body>
75 </html>

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.

6.3 Modifying the Default Page Layout


To demonstrate how you can define your own page layout, we will modify the original layout of
the Zend Skeleton Application page. We want to make it display the “Hello world” page title, the
“Hello world!” header text at the top, the navigation bar and breadcrumbs below the header, page
content placeholder in the middle of the page, and the footer with the copyright information at
the bottom (see figure 6.4 for an example of what we are trying to achieve).
Let’s start with the “Hello World” page title. We replace the lines 6-10 in the layout.phtml file as
follows:

<?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

Figure 6.4. Resulting Page Layout

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

21 <!-- Page content placeholder -->


22 <?php echo $this->content; ?>
23 </div>
24 </div>
25 <div class="row">
26 <div class="col-md-12">
27 <hr>
28 <p>
29 &copy; <?php echo date('Y') ?> by Your Company.
30 <?php echo $this->translate('All rights reserved.') ?>
31 </p>
32 </div>
33 </div> <!-- /container -->
34 <?php echo $this->inlineScript() ?>
35 </body>

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.

Next, we put the navigation bar in the corresponding grid row:

<!-- Navigation bar -->


<nav class="navbar navbar-default" role="navigation">
<div class="collapse navbar-collapse navbar-ex1-collapse">
<ul class="nav navbar-nav">
<li class="active">
<a href="<?php echo $this->url('home') ?>">Home</a>
</li>
<li>
<a href="<?php echo $this->url('application/default',
array('controller'=>'index', 'action'=>'downloads')) ?>">
Downloads
</a>
Page Appearance and Layout 153

</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.

Next, put the breadcrumbs component to the corresponding grid row:

<!-- Breadcrumbs -->


<ol class="breadcrumb">
<li class="active">Home</li>
</ol>

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.

6.4 Switching between Layouts


By default, ZF2 provides you with a single layout template layout.phtml. In real-life applications,
you will probably need to have several layouts and switch the layout for certain controller/action.
For example, you may have a front-end and a back-end part of your site. The front-end part
would consist of web pages publicly visible to all users and would utilize the default layout for
all of these pages. The back-end part would consist of pages visible to the administrator user only
and utilize another layout template containing an administrative menu.
First, prepare another layout template file. For example, call it layout2.phtml. To simplify the file
preparation, copy the content of the default layout.phtml file and make the necessary changes.
When the second layout template is ready, you can switch between layouts for a particular
controller’s action by using the following code:
Page Appearance and Layout 155

1 // A controller's action method that uses an alternative


2 // layout template.
3 public function indexAction() {
4 //...
5
6 // Use the Layout plugin to access the ViewModel
7 // object associated with layout template.
8 $this->layout()->setTemplate('layout/layout2');
9
10 //...
11 }

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.

6.4.1 Setting Layout for All Actions of a Controller


If all action methods of a controller class need to use the same alternative layout, you can override
the onDispatch() method of the AbstractActionController class and call the setTemplate()
method there, as shown in the example below:

// Add this alias in the beginning of the controller file


use Zend\Mvc\MvcEvent;

// ...

class IndexController extends AbstractActionController {

/**
* 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);

// Set alternative layout


Page Appearance and Layout 156

$this->layout()->setTemplate('layout/layout2');

// Return the response


return $response;
}
}

6.5 Partial Views


A partial view is a .phtml view template file which can be rendered by another view template.
Partial views allow to compose your page of pieces and reuse pieces of view rendering logic
across different view templates.
For a simple example of partial view usage, let’s imagine that we need to render a table of some
products. Each product has the ID, the name and the price. We can use partial view template to
render a single row of the table several times.
First, let’s add the partialDemoAction() method to the Index controller:

// An action that demonstrates the usage of partial views.


public function partialDemoAction() {
$products = array(
array(
'id' => 1,
'name' => 'Digital Camera',
'price' => 99.95,
),
array(
'id' => 2,
'name' => 'Tripod',
'price' => 29.95,
),
array(
'id' => 3,
'name' => 'Camera Case',
'price' => 2.99,
),
array(
'id' => 4,
'name' => 'Batteries',
'price' => 39.99,
),
array(
'id' => 5,
'name' => 'Charger',
'price' => 29.99,
Page Appearance and Layout 157

),
);

return new ViewModel(array(


'products' => $products
));
}

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.

6.6 Placeholder View Helper


The Placeholder is another useful view helper allowing for capturing HTML content and storing
⁸ it for later use. Thus, analogous to the Partial view helper, it allows to compose your page of
several pieces.
For example, you can use the Placeholder view helper in pair with the Partial view helper
to “decorate” the content of a view template with another view template. A useful practical
application for this is layout “inheritance”.
Imagine the situation, when you need to create an alternative layout which has exactly the same
head section, header and the footer, but has differences in the middle page section. The “rough”
way for making such a layout would be to copy and paste the content of the original layout
template, and make necessary changes. Another (better) way is “inheriting” the original one,
when the resulting layout will reuse the common parts.
To demonstrate how to inherit a layout, we will create the layout2.phtml view template, which
will inherit the default layout.phtml template, and add the Ads bar at the right of the page.
Keeping ads in layout would be useful, if you plan to profit from displaying commercial ads on
all (or on most) of pages of your site.
⁸The Placeholder view helper stores the data in PHP session storage. So, in theory, you can even capture content on one page and then
render/use it on another one.
Page Appearance and Layout 159

Figure 6.5. Table rows are rendered by partial views

Put the following code in the layout2.phtml template file:

1 <?php $this->placeholder('content')->captureStart(); ?>


2
3 <div class="row">
4 <div class="col-md-8">
5 <?php echo $this->content; ?>
6 </div>
7 <div class="col-md-4">
8 <div class="panel panel-default">
9 <div class="panel-heading">
10 <h3 class="panel-title">Ads</h3>
11 </div>
12 <div class="panel-body">
13 <strong>Zend Framework 2 Book</strong>
Page Appearance and Layout 160

14 <p>Learn how to create modern web applications with PHP


15 and Zend Framework 2</p>
16 <a target="_blank"
17 href="https://leanpub.com/using-zend-framework-2">
18 Learn More
19 </a>
20 </div>
21 </div>
22 </div>
23 </div>
24
25 <?php
26 $this->placeholder('content')->captureEnd();
27 echo $this->partial('layout/layout',
28 array('content'=>$this->placeholder('content')));
29 ?>

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.

6.7 Adding Scripts to a Web Page


JavaScript code can be inserted into HTML pages and make them interactive. Scripts should be
inserted to an HTML file between <script> and </script> tags. Below, an example JavaScript
code is presented:
Page Appearance and Layout 161

<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.

Figure 6.6. Inherited layout

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:

<script type="text/javascript" src="/js/jquery.min.js"></script>

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:

Table 6.1. Methods provided by the HeadScript view helper

Method name Description


appendFile() Puts a link to external JS file after all others.
offsetSetFile() Inserts a link to external JS file in a given list position.
prependFile() Puts a link to external JS file before all others.
setFile() Clears the list of scripts and puts the single external JS file in it.
appendScript() Puts an inline script after all others.
offsetSetScript() Inserts an inline script to a given list position.
prependScript() Puts an inline script before all others.
setScript() Clears the list of inline scripts and puts the single inline
script in it.

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

The methods prependScript(), appendScript(), offsetSetScript() and setScript() are


designed to insert an inline JavaScript code. They are rarely used, because you typically insert
external JS scripts in the head section of the document
To insert a script to the end of the <body> section of the document, you can use the InlineScript
view helper ¹⁰. It provides exactly the same methods as the HeadScript view helper. Below,
an example is presented which can be used to append an inline JavaScript code to the end of
document body:

<?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:

<!-- Page content goes first -->

<!-- Inline script goes last -->


<script type="text/javascript">
$( document ).ready(function() {
// Show a simple alert window with the "Hello World!" text.
alert("Hello World!");
});
</script>

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).

Figure 6.7. Auto-complete feature


Page Appearance and Layout 167

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.

6.8 Adding CSS Stylesheets to a Web Page


CSS stylesheets are typically placed to the <head> section of an HTML document, either as a
link to an external file (external CSS stylesheet files are usually stored in APP_DIR/public/css
directory.)

<link rel="stylesheet" type="text/css" href="/css/style.css">

or as an inline <style> element

<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

Table 6.2. Methods provided by HeadLink view helper

Method name Description


appendStylesheet() Puts a link to CSS stylesheet file after all others.
offsetSetStylesheet() Inserts a link to CSS stylesheet file in a given list position.
prependStylesheet() Puts a link to external CSS stylesheet file before all others.
setStylesheet() Clears the list and puts the single CSS file instead.

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:

Table 6.3. Methods of the HeadStyle view helper

Method name Description


appendStyles() Adds an inline CSS code after all others.
offsetSetStyle() Inserts an inline CSS stylesheet file in a given list position.
prependStyle() Puts a link to external CSS stylesheet file before all others.
setStyle() Clears the list and puts the single CSS file instead.

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.min.js – the minified version of jQuery UI JavaScript code;


• jquery-ui.min.css – the minified version of jQuery UI theming styles.

Put the jquery-ui.min.js file to APP_DIR/public/js, and jquery-ui.min.css file to APP_DIR/pub-


lic/css. Finally, add the datepicker.phtml view template:

¹³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 Writing Own View Helpers


Earlier in this chapter, we’ve created the layout common to all pages of the web site. But we still
have a couple of things to do to make the layout fully functional. If you remember, the layout
template contains the navigation bar and breadcrumbs. But both navigation bar and breadcrumbs
interface components provided by Twitter Bootstrap are currently “static”, while they need to be
more interactive.
For example, the active item of the navigation bar should depend on the controller’s action that
is being executed at the moment. And the breadcrumbs should display the path to the currently
viewed page. In this section we will make these widgets completely ready for the web site with
the help of our own view helpers.
Page Appearance and Layout 170

Figure 6.8. Datepicker

A typical view helper is a PHP class deriving from Zend\View\Helper\AbstractHelper base


class, which in turn implements the Zend\View\Helper\HelperInterface interface (class inher-
itance diagram is presented in figure 6.9).

Figure 6.9. View helper class diagram


Page Appearance and Layout 171

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).

Table 6.4. Methods of the Menu view helper

Method name Description


__construct($items) Class constructor.
setItems($items) Method for setting the menu items.
setActiveItemId($activeItemId) Method for setting the currently active menu item.
render() Renders the menu.
renderItem($item) Renders a single menu item.

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).

Figure 6.10. View helper directory

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.

Next, create the stub code for the Menu class:

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:

1 // Renders the menu.


2 public function render() {
3
4 if(count($this->items)==0)
5 return ''; // Do nothing if there are no items.
6
7 $result = '<nav class="navbar navbar-default" role="navigation">';
8 $result .= '<div class="navbar-header">';
9 $result .= '<button type="button" class="navbar-toggle" ';
10 $result .= 'data-toggle="collapse" data-target=".navbar-ex1-collapse">';
11 $result .= '<span class="sr-only">Toggle navigation</span>';
12 $result .= '<span class="icon-bar"></span>';
13 $result .= '<span class="icon-bar"></span>';
14 $result .= '<span class="icon-bar"></span>';
15 $result .= '</button>';
16 $result .= '</div>';
17
18 $result .= '<div class="collapse navbar-collapse navbar-ex1-collapse">';
19 $result .= '<ul class="nav navbar-nav">';
20
21 // Render items
Page Appearance and Layout 174

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(

// ...

// The following registers our custom view


// helper classes in view plugin manager.
'view_helpers' => array(
'invokables' => array(
'mainMenu' => 'Application\View\Helper\Menu',
),
),
);

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

1 <!-- Navigation bar -->


2 <?php
3 $this->mainMenu()->setItems(array(
4 array(
5 'id' => 'home',
6 'label' => 'Home',
7 'link' => $this->url('home')
8 ),
9 array(
10 'id' => 'downloads',
11 'label' => 'Downloads',
12 'link' => $this->url("application/default",
13 array('controller'=>'index', 'action'=>'downloads'))
14 ),
15 array(
16 'id' => 'support',
17 'label' => 'Support',
18 'dropdown' => array(
19 array(
20 'id' => 'documentation',
21 'label' => 'Documentation',
22 'link' => $this->url('doc', array('page'=>'contents'))
23 ),
24 array(
25 'id' => 'help',
26 'label' => 'Help',
27 'link' => $this->url('static', array('page'=>'help'))
28 )
29 )
30 ),
31 array(
32 'id' => 'about',
33 'label' => 'About',
34 'link' => $this->url('about')
35 ),
36 ));
37
38 echo $this->mainMenu()->render();
39 ?>

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

31 // Get item count


32 $itemCount = count($this->items);
33
34 $itemNum = 1; // item counter
35
36 // Walk through items
37 foreach($this->items as $label=>$link) {
38
39 // Make the last item inactive
40 $isActive = ($itemNum==$itemCount?true:false);
41
42 // Render current item
43 $result .= $this->renderItem($label, $link, $isActive);
44
45 // Increment item counter
46 $itemNum++;
47 }
48
49 $result .= '</ol>';
50
51 return $result;
52 }
53
54 // Renders an item.
55 protected function renderItem($label, $link, $isActive) {
56
57 $result = $isActive?'<li class="active">':'<li>';
58
59 if(!$isActive)
60 $result .= '<a href="'.$link.'">'.$label.'</a>';
61 else
62 $result .= $label;
63
64 $result .= '</li>';
65
66 return $result;
67 }
68 }

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:

<!-- Breadcrumbs -->


<?php echo $this->pageBreadcrumbs()->render(); ?>

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.

Figure 6.11. Breadcrumbs for the About page

6.10 View Models and Page Composition


Earlier, when we wrote action methods for the controller classes, we used the ViewModel class
as a variable container for passing the variables from controller to view template, and we also
used the ViewModel for overriding the default view template name.
Page Appearance and Layout 180

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).

Figure 6.12. View models nested in a tree-like structure

The resulting process of page rendering is the following:

• 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

Table 6.5. Methods of the ViewModel class for page composition

Method name Description


addChild() Adds a child view model.
getChildren() Gets the list of child view models.
hasChildren() Tests if the view model has children or not.
clearChildren() Removes all child view models.
count() Returns count of child view models.
getIterator() Returns the iterator for child view models.
setTerminal() Sets the terminal flag.
terminate() Tests whether the view model is terminal.
setCaptureTo() Sets the name of the variable for capturing the output.
setAppend() Sets the append flag.
isAppend() Tests whether to append this view model to another one.

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.

7.1 Get the Form Demo Sample from GitHub


We will demonstrate form usage on the Form Demo sample web application bundled with the
book. This sample is a complete web site you can install and see the working forms in action.
To download the Form Demo application, visit this page¹ and click the Download ZIP button to
download the code as a ZIP archive. When the download is complete, unpack the archive to a
directory of your choosing.
Then navigate to the formdemo directory which contains the complete source code of the Form
Demo web application:

/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

7.2 About HTML Forms


Form functionality provided by Zend Framework 2 internally uses HTML forms. Because of that,
we start with a brief introduction to HTML forms topic.
In HTML, forms are enclosed with <form> and </form> tags. A form typically consists of fields:
text input fields, check boxes, radio buttons, submit buttons, hidden fields and so on. HTML
provides several tags intended for defining form fields:

• <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).

Figure 7.1. Standard HTML form fields

²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

Table 7.1. Standard HTML form fields

Field Definition
Text input field <input type="text" />

Text area <textarea rows=4></textarea>

Password <input type="password" />

Button <input type="button" value="Apply"/> or


<button type="button">Apply</button>

Submit button <input type="submit" value="Submit" />

Image (graphical submit button) <input type="image" src="button.jpg" />

Reset button <input type="reset" value="Reset"/>

Checkbox <input type="checkbox">Remember me</input>

Radio <input type="radio" value="Radio">Allow</input>

Select <select><option>Enable</option><option>Disable</option></select>

File <input type="file" />

Hidden field <input type="hidden" />

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.

Table 7.2. HTML5 form fields

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.

Figure 7.2. 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:

Figure 7.3. Fieldset


Collecting User Input with Forms 187

7.2.2 Example: “Contact Us” Form


An example of a typical HTML form is presented below:

1 <form name="contact-form" action="/contactus" method="post">


2 <label for="email">E-mail</label>
3 <input name="email" type="text">
4 <br>
5 <label for="subject">Subject</label>
6 <input name="subject" type="text">
7 <br>
8 <label for="body">Message</label>
9 <textarea name="body" class="form-control" rows="6"></textarea>
10 <br>
11 <input name="submit" type="submit" value="Submit">
12 </form>

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:

• the name attribute specifies the name of the form (“contact-form”).


• the action attribute defines the URL of the server-side script which is responsible for
processing the submitted form (“/contactus”).
• the method attribute defines the method (either GET or POST) to use for delivering form
data. In this example, we use the POST method (recommended).

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

Figure 7.4. Visualization of the feedback form

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.

7.2.3 GET and POST Methods


HTML forms support GET and POST methods for submitting the data to server. These methods
have important technical differences.
When using POST method (the default) for submitting the form, the data is sent in HTTP request
body. For example, when you press the Submit button on the feedback form, an HTTP request
will look like the example below:

1 POST http://localhost/contactus HTTP/1.1


2 Host: localhost
3 Connection: keep-alive
4 Content-Length: 76
5 Accept: text/html,application/xhtml+xml,application/xml
6 Origin: null
7 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64)
8 Content-Type: application/x-www-form-urlencoded
9
10 email=name%40example.com&subject=Example+Subject&body=Hello%21&submit=Submit

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.

7.3 Styling HTML Forms with Twitter Bootstrap


In ZF2-based web sites, we use the Twitter Bootstrap CSS Framework that provides default CSS
rules for styling forms and form fields. To apply the CSS rules to a form field (like <input>,
<textarea>, etc.), you should assign it the .form-control CSS class. Additionally, when using
labels together with input fields, put the label-input pairs inside of <div> elements with the
.form-group CSS class. For submit buttons, you can use the .btn CSS class plus a theme class
like .btn-default, .btn-primary, etc.
Below, we provide the modified example of the feedback form which uses the Bootstrap styling:

³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>

<form name="contact-form" action="/contactus" method="post">

<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>

<input name="submit" type="submit"


class="btn btn-primary" value="Submit">
</form>

The visualization of the form is presented in figure 7.5.


Because Twitter Bootstrap is designed to support mobile phones, tablets, and desktops, it makes
the form fields as wide as the size of the screen. This may make your form too wide and hard to
understand. To limit form width, you can use the Bootstrap-provided grid, like in the example
below:

<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.

Figure 7.5. Styled feedback form

7.4 Retrieving Form Data in a Controller’s Action


The site user typically works with the form in the following order:

• 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

Figure 7.6. Creating the contact-us.phtml file

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)

Figure 7.7. Feedback Form

7.5 Forms and Model-View-Controller


In the previous section, we’ve considered a very simple form usage case: we prepared the view
template with form HTML markup and a controller action responsible for displaying the form
and dumping raw user input to the screen. However, using raw user input in real-life applications
Collecting User Input with Forms 195

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.

Figure 7.8. Form Functionality in ZF2

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.

7.5.1 A Typical Form Usage Workflow


Generally speaking, you instantiate a form model inside of your controller’s action method,
then you retrieve the user-submitted data from PHP variables, and pass it to the form model for
validation. Form view helpers are used in a view template for generating HTML markup of the
form. This typical workflow is illustrated by figure 7.8.
Arrows in figure 7.8 denote the direction of the actions:

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.

In the following sections, we will discuss these in more detail.

Figure 7.8. Working with form in an MVC application

7.6 A Form Model


A form model is usually a PHP class which creates a number of fields. The base class for all form
models is the Form class defined in the Zend\Form component.
Fields in a form model can optionally be grouped into fieldsets. Moreover, the form model itself
can be considered as a fieldset. This fact is reflected in form class inheritance (figure 7.9). As you
Collecting User Input with Forms 197

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:

Figure 7.9. Form class inheritance

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

18 // (Optionally) set action for this form


19 $this->setAttribute('action', '/contactus');
20
21 // Create the form fields here ...
22 }
23 }

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.

7.7 Form Elements


In a form model, an input field is typically paired with the text label (<label> and <input> tags
are used together). Such a pair is also called a form model’s element.
Analogous to an HTML form field, a form model’s element may contain the name and other
(optional) attributes (e.g. “id”, “class”, etc.) Additionally, you may set options to an element; the
options mostly allow you to specify the text and attributes for the element’s label.
All form model’s elements are inherited from the base class Element which also belongs to the
Zend\Form component. The Element base class implements the ElementInterface interface. The
class inheritance diagram is shown in figure 7.10.
Collecting User Input with Forms 199

Figure 7.10. Form element class inheritance

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.

Table 7.3. Form elements

Class name Description


Elements compatible with HTML4
Button Button.
Checkbox Check box.
File File field.
Hidden Hidden field.
Image Image field.
Password Password field.
Radio Radio button.
Select Dropdown list.
Submit Submit button.
Text General-purpose text input field.
Textarea Multi-line text area.
HTML5 Elements
Color Color picker.
Date Date picker.
DateTime Date & time (with time zone).
DateTimeLocal Date & time (without time zone).
Email E-mail field.
Month Month input field.
Number A text input field accepting numbers.
Time Text input field for entering time.
Url Text input field for entering an URL.
Week Text input field for entering days of week.
Range Range field (slider).
Compound Fields
MultiCheckbox A group of related check boxes.
DateTimeSelect Date & time select.
DateSelect Date select.
MonthSelect Month select.
Security Form Elements
Collecting User Input with Forms 200

Table 7.3. Form elements

Class name Description

Captcha Human check image.


Csrf Cross-site request forgery prevention.
Other
Collection Element collection.

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.

Figure 7.11. Compound form fields

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

7.7.1 Adding Elements to a Form Model


The methods inherited by the Form base class from the Fieldset class are used to add elements
(and fieldsets) to the form model. These methods are summarized in the table 7.4.

Table 7.4. Methods provided by the Fieldset class

Method name Description


add($elementOrFieldset, $flags) Attaches an element (or fieldset).
has($elementOrFieldset) Checks whether certain element is attached.
get($elementOrFieldset) Retrieves the given element (or fieldset) by name.
getElements() Retrieves all attached elements.
getFieldsets() Retrieves all attached fieldsets.
count() Return the count of attached elements/fieldsets.
remove($elementOrFieldset) Removes the element (or fieldset).

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.

7.7.2 Method 1: Passing an Instance of an Element


The following code fragment creates an instance of the Zend\Form\Element\Text class and adds
the element to the form model:

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.

Table 7.5. Methods provided by the Element class

Method name Description


setName($name) Sets element’s name.
getName() Retrieves element’s name.
setOptions($options) Sets options.
getOptions($options) Retrieves options.
getOption($option) Retrieves the given option.
setAttribute($key, $value) Sets a single element attribute.
getAttribute($key) Retrieves a single element attribute.
removeAttribute($key) Removes an attribute.
hasAttribute($key) Checks whether such an attribute presents.
setAttributes($arrayOrTraversable) Removes an attribute.
getAttributes() Retrieves all attributes at once.
clearAttributes() Removes all attributes at once.
setValue() Sets the element value.
getValue() Retrieves the element value.
setLabel() Sets the label used for this element.
getLabel() Retrieves the label string used for this element.
setLabelAttributes() Sets the attributes to use with the label.
getLabelAttributes() Gets the attributes to use with the label.
setLabelOptions() Sets label specific options.
getLabelOptions() Retrieves label specific options.
Collecting User Input with Forms 203

7.7.3 Method 2: Using Array Specification


The second example below (equivalent to the first one) shows how to use an array specification
to add an element to form. This method is preferable, because it requires less code to write.

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

Figure 7.12. The logic of the add() method

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.

7.8 Example: Creating the Contact Form Model


Now that we know how to set the form name, action, and method attributes and how to add
fields (elements) to the form, let’s create the complete model class for the feedback form that we
used in our previous examples.
As we know, form model classes for the Application module live inside the Application\Form
namespace. So, we have to create the ContactForm.php file inside of the Form directory under
the Application module’s source directory (figure 7.13).
⁶If you are confused where we take element aliases from, than you should know that they are defined inside of the
Zend\Form\FormElementManager class.
Collecting User Input with Forms 205

Figure 7.13. Form directory

We will have two methods in our form class:

• __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.

The code of the ContactForm class is presented below:

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.

Figure 7.14. The feedback form model and its elements


Collecting User Input with Forms 208

7.9 Adding Form Validation Rules


Form validation is the procedure of filtering and checking the data passed to the server during
the form submission. For example, for our feedback form, we want to perform the following
checks:

• 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.

7.9.1 Input Filter


In ZF2, you store the validation rules with the help of the InputFilter class. The InputFilter
class is defined in the Zend\InputFilter component. The input filter is a container for so called
inputs. Typically, you add an input per each form model’s field you have.
⁷There may be malicious users inserting HTML code in the message. If you open such code in your browser, you may see some undesired
content. To avoid this, we need to replace HTML tags in message subject and text.
Collecting User Input with Forms 209

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).

7.9.2 Adding Inputs to Input Filter


To add an input to the input filter, you use its add() method, which takes the single argument -
an array specification of the input in the following form:

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 )

In above array, we have the following keys:

• 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

Figure 7.15. Input class inheritance

7.9.2.1 Filter Configuration

A typical filter configuration is presented below:

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.

7.9.2.2 Validator Configuration

A typical validator configuration is presented below:


Collecting User Input with Forms 211

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.

7.9.2.3 Attaching Input Filter to Form Model

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).

Table 7.6. Methods provided by the Form base class

Method name Description


setInputFilter($inputFilter) Attaches the input filter container to the form.
getInputFilter() Retrieves the input filter attached to the form.

7.9.3 Creating Input Filter for the Contact Form


Now that you have a general idea on know how to define the input filter container and populate it
with filters and validators for each form field, let’s complete our ContactForm form model class.
Below, we add the addInputFilter() private method, which defines the filtering/validation
rules, stores them in input filter container, and attaches the input filter to the form model:

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.

Figure 7.16. The input filter for ContactForm

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.

7.10 Using the Form in a Controller’s Action


When the form model class is ready, you finally can use the form in a controller’s action method.
As you might already know, the way the site user works with form is typically an iterative
process (schematically illustrated by figure 7.17):
Collecting User Input with Forms 215

Figure 7.17. Typical form usage workflow

• 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

Table 7.7. Methods provided by the Form base class

Method name Description


setData($data) Sets form data for validation.
getData($flag) Retrieves the validated data.
isValid() Validates the form.
hasValidated() Check if the form has been validated.
getMessages($elementName = null) Returns a list of validation failure messages, if any,
for a single element or for all form elements.

So, a generic form usage workflow is the following:

• 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).

7.10.1 Passing Form Data to a Model


To give you a real-life example of how you can use the validated data of the feedback form, in
this section we will create a simple MailSender model ⁹ class which can be used for sending an
E-mail message to an E-mail address. When the user submits the form, we will validate the form
data and pass the validated data to the MailSender model and ask it to send the E-mail message
to the recipient.

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).

Figure 7.18. Creating the MailSender.php File

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.

Finally, you can instantiate the MailSender model in your IndexController::contactUsAction()


method and pass it the validated form data. Below, the complete code for the contactUsAction()
method is presented:

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 }

As you can see from the code above, we do the following:

• In line 3, we declare an alias for Application\Service\MailSender class. This will allow


you to refer to the model class by its short name.
• In lines 26-28, after we’ve validated the form, we extract the validated field values into the
$email, $subject and $body PHP variables.
Collecting User Input with Forms 222

• 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.

7.11 Form Presentation


When your controller’s action is ready, all you have to do is prepare the .phtml view template
file to display your form on a web page. In the view template, you need to define the markup
using <form>, <label>, <input>, and possibly other HTML tags.
Additionally, you will have to display error messages if the form validation failed. Because this
work is rather boring, Zend Framework 2 provides you with special view helpers intended for
rendering the form.

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.

7.11.1 Preparing the Form Model for Rendering


Before rendering, it is required that you call the prepare() method on the form model’s instance
(see table 7.8). If you forget to call this method, there may be undesired effects.

Table 7.8. Methods provided by the Form base class

Method name Description


prepare() Ensures the form state is ready for use.

The prepare() method does the following form model preparations:

• 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

7.12 Standard Form View Helpers


Standard form view helpers provided by ZF2 are shown in table 7.9. These classes live in the
Zend\Form\View\Helper namespace. As you can see from the table, the view helpers can be
divided into the following categories:

• 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.

Table 7.9. View helpers designed for using with forms

Method name Description


Generic helpers
Form Renders the entire form and all its elements.
FormElement Renders a generic form element.
FormElementErrors Renders validation errors for a form element.
FormRow Renders the label, the field and validation errors.
HTML field helpers
FormButton Renders the <button> form field.
FormCheckbox Renders the <input type="checkbox"> field.
FormFile Renders the <input type="file"> form field.
FormHidden Renders the <input type="hidden"> form field.
FormInput Renders an <input> form field.
FormImage Renders the <input type="image"> form field.
FormLabel Renders the <label> tag.
FormPassword Renders the <input type="password"> form field.
FormRadio Renders the <input type="radio"> form field.
FormReset Renders the <input type="reset"> form field.
FormSelect Renders the <select> dropdown field.
FormSubmit Renders the <input type="submit"> form field.
FormText Renders the <input type="text"> form field.
FormTextarea Renders the <textarea> multi-line text field.
HTML5 field helpers
FormColor Renders the <input type="color"> HTML5 form field.
FormDate Renders the <input type="date"> HTML5 form field.
Collecting User Input with Forms 224

Table 7.9. View helpers designed for using with forms

Method name Description


FormDateTime Renders the <input type="date"> HTML5 form field.
FormDateTimeLocal Renders the <input type="datetime-local"> HTML5 form field.
FormEmail Renders the <input type="email"> HTML5 form field.
FormMonth Renders the <input type="month"> HTML5 form field.
FormNumber Renders the <input type="number"> HTML5 form field.
FormRange Renders the <input type="range"> HTML5 form field.
FormTel Renders the <input type="tel"> HTML5 form field.
FormTime Renders the <input type="time"> HTML5 form field.
FormUrl Renders the <input type="url"> HTML5 form field.
FormWeek Renders the <input type="week"> HTML5 form field.

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.

7.12.1 Rendering a Form Element


You can render a form field with the FormElement view helper. It is designed to be as flexible as
possible and recognize as many field types as possible. So, with this view helper you are able to
produce HTML markup for text fields, buttons, dropdown lists and so on.
The methods provided by this view helper are listed in table 7.10.

Table 7.10. Methods provided by the FormElement view helper

Method name Description


render($element) PHP magic method which renders the given form field.
__invoke($element) PHP magic method which renders the given form field
(the effect is the same as render()).

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')); ?>

// The same, but with __invoke


echo $this->formElement($form->get('email'));

When executed, the code above will generate the HTML code as follows:

<input type="text" name="email" id="email" value="">

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.

7.12.2 Rendering an Element’s Validation Errors


The FormElementErrors view helper class allows you to produce HTML markup for field
validation errors (if present). If there are no validation errors for certain element, this view helper
does not produce any output.
An example of using the FormElementErrors view helper is presented below:

<?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>&#039;hostname&#039; 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>

7.12.3 Rendering an Element’s Label


The FormLabel helper allows you to render the text label for an element:
Collecting User Input with Forms 226

<?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:

<label for="email">Your E-mail</label>

7.12.4 Rendering a Form Row


The FormRow view helper is designed to simplify the rendering of a form field, it’s label, and
validation errors. With this class, you are able to render these in a single step. This helper is
flexibly configurable, so you can apply a different decoration to the form row. The methods of
this view helper class are listed in table 7.11.

Table 7.11. Methods provided by the FormRow view helper

Method name Description


render($element) Renders the form row.
__invoke($element, $labelPosition, Renders the form row (convenience wrapper).
$renderErrors, $partial)
setInputErrorClass($inputErrorClass) Sets input error CSS class.
setLabelAttributes($labelAttributes) Sets label attributes.
setLabelPosition($labelPosition) Sets label position (before or after the field).
setRenderErrors($renderErrors) Set if the errors are rendered by this helper.
setPartial($partial) Set a partial view script to use for rendering the
row.

An example of using the FormRow view helper is presented below:

<?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

<label for="email">Your E-mail</label>


<input type="text" name="email" id="email">
<ul>
<li>&#039;hostname&#039; 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>

7.12.5 Rendering the Entire Form


The Form view helper allows you to render the opening <form> tag and its attributes; and the
closing </form> tag. But its major purpose is to render the entire form and all of its fields with
a single line of code. Public methods of the Form view helper class are summarized in table 7.12.

Table 7.12. Methods provided by the Form view helper

Method name Description


render($form) Renders the entire form and all its elements.
__invoke($form) PHP magic method which renders the entire form and all its
elements (the effect is the same as render()).
openTag($form) Renders the opening <form> tag.
closeTag() Renders the closing </form> tag.

You can render the whole form with the help of the Form’s render() method as follows:

// We assume that the form model is stored in $form variable

// Render the whole form


$this->form()->render($form);

The same effect can be achieved with the __invoke magic method (see example below):

// The same, but with `__invoke`


$this->form($form);

7.13 Example: Creating the View Template for the


Contact Form
Now we are ready to define the presentation for our feedback form. If you remember, earlier we
added the contact-us.phtml view template in application/index/ directory under the module’s
view/ directory. Replace the code in that file with the following:
Collecting User Input with Forms 228

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

<form action="/contact" method="post" name="contact-form">


<label for="email">Your E-mail</label>
<input type="text" name="email" id="email" value="">

<label for="subject">Subject</label>
<input name="subject" type="text" id="subject" value="">

<label for="body">Message Body</label>


<textarea name="body" id="body"></textarea>

<input name="submit" type="submit" value="Submit">


</form>

In the code above, we mostly used the FormElement, FormElementErrors and


FormLabel view helpers. You may use the generic FormRow or Form view helpers if you
want to reduce the amount of code to write, but this may result in less control of form
decoration.

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:

<label for="email">Your E-mail</label>


<input type="text" name="email" value="123@hostname">
<ul>
<li>&#039;hostname&#039; 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>

7.13.1 Applying the Bootstrap CSS Styles to Form


The HTML markup above is missing CSS styling. What we want to achieve is to use Twitter
Bootstrap CSS classes to give the form a nice, professional-looking appearance. To add Bootstrap
styling to the form, you have to modify the code in the .phtml file to make it look like below:
Collecting User Input with Forms 230

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).

7.13.2 Styling the Validation Errors List


The error messages on your form, by default, look like a typical unordered list (<ul>). To give
them a nice visual appearance, we add a couple of CSS rules to the style.css file in APP_DIR/public
directory:

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:

<h1>Error Sending Email!</h1>

<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

Figure 7.19. Contact Form

Figure 7.20. Form validation errors


Collecting User Input with Forms 234

Figure 7.21. Thank You page

Figure 7.22. Error Sending Email page


Collecting User Input with Forms 235

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.

ZF2 components covered in this chapter:

Component Description
Zend\Filter Contains various filters classes.
Zend\InputFilter Implements a container for filters/validators.

8.1 About Filters


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).

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).

8.2 Standard Filters Overview


Standard filters implementing the FilterInterface interface belong to Zend\Filter component
¹. A filter class inheritance diagram is shown in figure 8.1. From that figure, you can see that
base concrete class for most standard filters is the AbstractFilter class, which implements the
FilterInterface interface ².

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

Figure 8.1. Filter class inheritance

Table 8.1. Standard filters

Class name Description


Boolean Returns a boolean representation of $value.
Int Casts the input $value to int.
Digits Returns the string $value, removing all but digit characters.
Null Returns null if the input value can be treated as null; otherwise returns the
$value itself.
DateTimeFormatter Takes a date & time string in an arbitrary format and produces a date & time
string in a given format.
BaseName Given a string containing the path to a file or directory, this filter will return
the trailing name component.
Dir Given a string containing the path of a file or directory, this filter will return
the parent directory’s path.
RealPath Returns canonicalized absolute pathname.
Compress Compresses the input data with the specified algorithm (GZ by default).
Transforming Input Data with Filters 239

Table 8.1. Standard filters

Class name Description

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.

8.3 Instantiating a Filter


In Zend Framework 2, you can use several methods of creating a filter:

• instantiating it manually (with the new operator);


• creating it with a factory class (by passing an array configuration), a method most
frequently used when adding filtering and validation rules in a form; and
• instantiating it implicitly with the StaticFilter wrapper class.

Next, we will cover these three methods in more details.


Transforming Input Data with Filters 240

8.3.1 Method 1: Instantiating a Filter Manually


As we previously said, a filter in general can be used not only with forms but also for filtering
an arbitrary data. To do that, you simply create an instance of the filter class, configure the filter
by using the methods it provides, and call the filter() method on the filter.
For example, let’s consider the usage of the StringTrim filter which removes the white space
characters from the beginning and the end of a string.

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.

The methods provided by the filter are listed in table 8.2:

Table 8.2. Public methods of the StringTrim filter

Method name Description


__construct($charlistOrOptions) Constructs the filter. Accepts the list of options.
filter($value) Removes the predefined characters from the beginning and
the end of the string.
setCharList($charList) Defines the list of characters to strip off.
getCharList() Returns the list of characters to strip off.

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

8.3.2 Method 2: Constructing a Filter with StaticFilter


An alternative way of manual filter instantiation is by using the StaticFilter class. The
StaticFilter class is some kind of a “proxy” designed for automatic filter instantiation,
configuration, and execution. For example, let’s consider how to create the same StringTrim
filter, configure it, and call its filter() method:

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.

8.3.3 Method 3: Constructing a Filter From Array


When using filters with form’s validation rules, you typically do not construct a filter object
explicitly as we did in the previous section; instead, you pass an array configuration to the factory
class, which automatically constructs the filter for you and (optionally) configures it. We already
saw how this works when adding validation rules for the feedback form in Chapter 7.
For example, let’s show how to construct the same StringTrim filter with the help of the factory:

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.

8.4 About Filter Plugin Manager


In the previous example, you saw that you can use either the fully qualified filter class name
or its short alias when instantiating the filter from the array. The short aliases for the standard
filters are defined by the FilterPluginManager class.

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.

8.5 Filter’s Behavior in Case of Incorrect Input Data


Different filters behave differently if you pass it input data that the filter cannot process correctly.
Some filters (such as the Int filter) will process only scalar data. If you pass an array to such
filter, it will return the array as is.
Some filters can work with data in certain format only (e.g., with dates only). If filtering of
input data is impossible (for example, when you pass the filter some wrong data that it is
unable to process), the filter() method may throw a Zend\Form\Exception\RuntimeException
exception. This behavior can be seen in DateTimeFormatter filter.
Some filters (e.g., Int or StringToLower) may rise a PHP warning if the value provided is in
incorrect format and cannot be filtered.

It is recommended to read filter’s documentation carefully to know what to expect of


the filter you plan to use in your form.
Transforming Input Data with Filters 244

8.6 Filter Usage Examples


Next, we will consider the usage of the most important standard filters. These describe the
methods (and options) a filter has and provide code examples showing how to instantiate the
filter and apply it to input data. If you need to use a filter not covered in this section, please refer
to Standard Filters section of the Zend Framework 2 Reference Manual.

8.6.1 Filters Casting Input Data to a Specified Type


In this section, we will consider several filters from the group of filters related to casting input
data to the specified type and provide their usage examples.

8.6.1.1 Int Filter

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.

8.6.1.2 Boolean Filter

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.

Its public methods are listed in table 8.3.


Transforming Input Data with Filters 245

Table 8.3. Public methods of the Boolean filter

Method name Description


filter($value) Returns a boolean representation of $value.
setCasting($flag) Sets casting flag.
getCasting() Returns the casting flag.
setType($type) Sets types from which to cast.
getType() Returns types.
setTranslations($translations) Sets translations.
getTranslations() Returns the translations.

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:

Table 8.4. Type constants

Constant Numeric Value Literal Equivalent Description


TYPE_BOOLEAN 1 “boolean” Returns a boolean value as is.
TYPE_INTEGER 2 “integer” Converts an integer 0 value to false.
TYPE_FLOAT 4 “float” Converts a float 0.0 value to false.
TYPE_STRING 8 “string” Converts an empty string ‘’ to false.
TYPE_ZERO_STRING 16 “zero” Converts a string containing the
single character zero (‘0’) to false.
TYPE_EMPTY_ARRAY 32 “array” Converts an empty array to false.
TYPE_NULL 64 “null” Converts a null value to false.
TYPE_PHP 127 “php” Converts values according to PHP
when casting them to boolean. (This
is the default behavior.)
TYPE_FALSE_STRING 128 “false” Converts a string containing the
word “false” to a boolean false.
TYPE_LOCALIZED 256 “localized” Converts a localized string which
contains certain word to boolean.
TYPE_ALL 511 “all” Converts all above types to boolean.

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;

// Call the setType() and pass it a combination of constants.


$filter->setType(Boolean::TYPE_BOOLEAN|
Boolean::TYPE_INTEGER|
Boolean::TYPE_STRING);

// Call the setType() and pass it an array with literal equivalents.


$filter->setType(array('boolean', 'integer', 'string'));

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();

// Optionally configure the filter.


$filter->setCasting(true);
$filter->setType(\Zend\Filter\Boolean::TYPE_ALL);
$filter->setTranslations(array('yes'=>true, 'no'=>false));

// Filter a value casting it to a boolean number.


$filteredValue = $filter->filter('false'); // Returns boolean false.
$filteredValue2 = $filter->filter('1'); // Returns boolean true.
$filteredValue3 = $filter->filter('false'); // Returns boolean false.
$filteredValue4 = $filter->filter('yes'); // Returns boolean true.
Transforming Input Data with Filters 247

8.6.1.3 Null Filter

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.

Table 8.5. Public methods of the Null filter

Method name Description


filter($value) Casts the $value to null, if possible; otherwise returns values as is.
setType($type) Defines from which types to cast.
getType() Returns defined types.

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.

Table 8.6. Type constants

Constant Numeric Value Literal Equivalent Description


TYPE_BOOLEAN 1 “boolean” Converts a boolean false value to
null.
TYPE_INTEGER 2 “integer” Converts an integer 0 value to null.
TYPE_EMPTY_ARRAY 4 “array” Converts an empty array to null.
TYPE_STRING 8 “string” Converts an empty string ‘’ to null.
TYPE_ZERO_STRING 16 “zero” Converts a string containing the
single character zero (‘0’) to null.
TYPE_FLOAT 32 “float” Converts a float 0.0 value to null.
TYPE_ALL 63 “all” Converts all above types to null. This
is the default behavior.

The following code example illustrates two equivalent ways you can call the setType() method:

<?php
use Zend\Filter\Null;

// Call the setType() and pass it a combination of constants.


$filter->setType(Null::TYPE_ZERO_STRING|Null::TYPE_STRING);

// Call the setType() and pass it an array with literal equivalents.


$filter->setType(array('zero', 'string'));

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();

// Optionally configure the filter.


$filter->setType(\Zend\Filter\Null::TYPE_ALL);

$filteredValue = $filter->filter('0'); // Returns null.


$filteredValue2 = $filter->filter('1'); // Returns string '1'.
$filteredValue3 = $filter->filter(false); // Returns null.

8.6.1.4 DateTimeFormatter Filter

The DateTimeFormatter filter accepts a date in an arbitrary format and converts it into the
desired format.

This filter can accept a string (e.g., ‘2014-03-22 15:36’), an integer


timestamp (like the time() PHP function returns) or an instance of
the DateTime PHP class. The DateTimeFormatter filter may throw a
Zend\Filter\Exception\InvalidArgumentException exception if you pass it a
date in an incorrect format.

Filter’s public methods are listed in table 8.7.

Table 8.7. Public methods of the DateTimeFormatter filter

Method name Description


__construct($options) Constructs the filter.
filter($value) Transforms the date into the desired format.
setFormat($format) Sets the date 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();

// Set filter's format (optional).


$filter->setFormat('F j, Y g:i A');

// Transform the date to the specified format.


$filteredValue = $filter->filter('2014-03-22 15:36');

// The expected output is 'March 22, 2014 3:36 PM'.


Transforming Input Data with Filters 249

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.

8.6.2 Filters Performing Manipulations on a File Path


In this section, we will consider usage examples of the filters from the group of filters related to
manipulating file paths.

8.6.2.1 BaseName Filter

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();

// Filter a file path and return its last part.


$filteredValue = $filter->filter('/var/log/httpd/error.log');

// The expected filter's output is the 'error.log'.

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.

8.6.2.2 Dir Filter

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();

// Filter a file path and return its directory name part.


$filteredValue = $filter->filter('/var/log/httpd/error.log');

// The expected filter's output is the '/var/log/httpd'.

8.6.2.3 RealPath Filter

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 is a wrapper over the realpath() PHP function.

Filter’s public methods are listed in table 8.8.

Table 8.8. Public methods of the RealPath filter

Method name Description


__construct($options) Constructs the filter.
filter($value) Returns canonicalized absolute pathname.
setExists($flag) Specifies if the path must exist for this filter to succeed. The value true
means the path must exist; the value false means a nonexisting path can
be given.
getExists() Returns true if the filtered path must exist.

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();

// Filter a file path (it is assumed that the current


// working directory is /var/log/httpd and that it contains
// the error.log file).
$filteredValue = $filter->filter('./error.log');

// The expected filter's output is the '/var/log/httpd/error.log'.


Transforming Input Data with Filters 251

The RealPath filter will not process a non-scalar value. If you pass it an array, it will
return the array as is.

8.6.3 Filters Performing Compression and Encryption of Input


Data
In this section, we will consider several filters from the group of filters related to compressing
and encrypting the input data. These filters are not very usable for filtering form data but can
be used outside of forms with a great success.

8.6.3.1 Compress Filter

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.

Table 8.9. Public methods of the Compress filter

Method name Description


__construct($options) Constructs the filter.
filter($value) Performs data compression using the specified algorithm.
getAdapter() Returns the current adapter, instantiating it if necessary.
getAdapterName() Retrieves adapter name.
setAdapter($adapter) Sets compression adapter.
getAdapterOptions() Retrieves adapter options.
setAdapterOptions($options) Sets adapter options.
getOptions($option) Gets individual or all options from underlying adapter.

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.

Table 8.10. Compression adapters

Class name Description


Bz2 Bzip2³ (Burrows–Wheeler) compression algorithm.
Gz Gzip⁴ compression algorithm is based on the Deflate algorithm, which is a
combination of LZ77 and Huffman coding.
Zip ZIP is a compression algorithm widely used in Windows operating system.

³http://www.bzip.org/
⁴http://www.gzip.org/
Transforming Input Data with Filters 252

Table 8.10. Compression adapters

Class name Description


Tar Tarball⁵ file format is now commonly used to collect many files into one larger file for
archiving while preserving file system information such as user and group
permissions, dates, and directory structures. Widely used in Linux operating system.
Lzf LZF is a very fast compression algorithm, ideal for saving space with only slight speed
cost.
Snappy Snappy⁶ is a fast data compression and decompression library developed by Google
based on ideas from LZ77.
Rar RAR is an archive file format that supports data compression, error recovery, and file
spanning.

Figure 8.2. Compression algorithm adapter inheritance

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.

8.6.3.2 Encrypt Filter

The Encrypt filter’s purpose is encrypting the input data with the specified algorithm. Filter’s
public methods are listed in table 8.11.

Table 8.11. Public methods of the Encrypt filter

Method name Description


__construct($options) Constructs the filter.
filter($value) Performs data compression using the specified algorithm.
getAdapter() Returns the current adapter, instantiating it if necessary.
setAdapter($adapter) Sets compression adapter.

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.

• BlockCipher – implements symmetric block cipher algorithm.


• Openssl – uses an encryption algorithm from the OpenSSL library.
Transforming Input Data with Filters 254

Figure 8.3. Encryption algorithm adapter inheritance

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 expected result is a string encrypted with the block cipher.

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.

8.6.4 Filters Manipulating String Data


In this section, we will consider usage examples of the filters from the group of filters related to
manipulating string data.

8.6.4.1 StringToLower Filter

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.

Table 8.12. Public methods of the StringToLower filter

Method name Description


__construct($options) Constructs the filter.
filter($value) Converts the string to lowercase letters.
setEncoding($encoding) Sets the input encoding for the given string.
getEncoding() Returns the encoding.
Transforming Input Data with Filters 255

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();

// (Optionally) set encoding on the filter.


$filter->setEncoding('UTF-8');

// Filter a string.
$filteredValue = $filter->filter('How to Start a Business in 10 Days');

// The expected filter's output is the 'how to start a business in 10 days'.

The StringToUpper filter (converting a string to uppercase letters) is a “mirror reflec-


tion” of the StringToLower filter and can be used by analogy. By that reason, we do
not cover the StringToUpper filter in this section.

8.6.4.2 PregReplace Filter

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

Table 8.13. Public methods of the PregReplace filter

Method name Description


__construct($options) Constructs the filter.
filter($value) Performs a regular expression search and replace.
setPattern($pattern) Sets the pattern to search for. It can be either a string or an array
with strings.
getPattern() Returns the pattern.
setReplacement($replacement) Sets the string or an array with strings to replace.
getReplacement() Gets currently set replacement value.

Below, a code example showing how to use the StringToLower filter is provided:

<?php
// Create PregReplace filter.
$filter = new \Zend\Filter\PregReplace();

// Configure the filter.


$filter->setPattern("/\s\s+/");
$filter->setReplacement(' ');

// Filter a string.
$filteredValue = $filter->filter('An example with multiple spaces.'\
);

// The expected filter's output is the 'An example with multiple spaces.'

8.6.4.3 StripTags Filter

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.

Table 8.14. Public methods of the StripTags filter

Method name Description


__construct($options) Constructs the filter.
filter($value) Returns the value with tags stripped off it.
getAttributesAllowed() Returns the list of attributes allowed for the tags.
setAttributesAllowed($attributesAllowed) Sets the list of attributes allowed for the tags.
getTagsAllowed() Returns the list of tags allowed.
setTagsAllowed($tagsAllowed) Sets the list of tags allowed.
Transforming Input Data with Filters 257

Below, a code example showing how to use the StripTags filter is provided:

<?php
// Create StripTags filter.
$filter = new \Zend\Filter\StripTags();

// Configure the filter.


$filter->setTagsAllowed(array('p'));

// Filter a string.
$filteredValue = $filter->filter(
'<p>Please click the following <a href="example.com">link</a>.</p>');

// The expected filter's output is the


// '<p>Please click the following link.</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.

8.6.4.4 StripNewlines Filter

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 expected filter's output is the 'A multi line 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.

8.6.4.5 UriNormalize Filter

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

Table 8.15. Public methods of the UriNormalize filter

Method name Description


filter($value) Filter the URL by normalizing it and applying a default
scheme if set.
setDefaultScheme($defaultScheme) Set the default scheme to use when parsing schemeless
URIs.
setEnforcedScheme($enforcedScheme) Set a URI scheme to enforce on schemeless URIs.

The URL normalization procedure typically consists of the following steps:

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();

// Configure the filter.


$filter->setDefaultScheme('http');
$filter->setEnforcedScheme('https');

// Filter an URL string.


$filteredValue = $filter->filter('www.example.com');

// The expected filter's output is the 'https://www.example.com/'.


Transforming Input Data with Filters 259

8.6.5 Organizing Filters in a Chain


Filters can be organized in a sequence. This is accomplished by the FilterChain class. When
such a compound filter is run, the value filtered by the first filter is passed as an input for the
second one, and then the value filtered by the second filter will be passed to the third one, and
so on.

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:

Table 8.16. Public methods of the FilterChain filter

Method name Description


filter($value) Returns value filtered through each filter in the
chain. Filters are run in the order in which they
were added to the chain (FIFO).
setOptions($options) Sets options.
attach($callback, $priority) Attaches an existing filter instance (or a callback
function) to the chain.
attachByName($name, $options, $priority) Instantiates the filter by class name or alias and
inserts it into the chain.
merge($filterChain) Merges the filter chain with another filter chain.
getFilters() Returns all the attached filters.
count() Returns the count of attached filters.

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.

Figure 8.4. Filter chain

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;

// Instantiate the filter chain.


$filter = new FilterChain();

// Insert filters into filter chain.


$filter->setOptions(array('filters'=>array(
array('name'=>'StringTrim',
'options'=>array('charlist'=>"\r\n\t "),
'priority'=>FilterChain::DEFAULT_PRIORITY
),
array('name'=>'StripTags',
'options'=>array('tagsallowed'=>array('p')),
'priority'=>FilterChain::DEFAULT_PRIORITY
),
array('name'=>'StripNewLines',
'priority'=>FilterChain::DEFAULT_PRIORITY
)
)
));

// Execute all filters in the chain.


$filteredValue = $filter->filter(" name@example.com<html>\n ");

// The expected output is 'name@example.com'.

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.

8.6.6 Custom Filtering with the Callback Filter


Standard filters are designed to be used in frequently appearing situations. For example, you may
often need to trim a string or convert it to lowercase. However, sometimes there are cases where
you cannot use a standard filter. Here, the Callback filter will be handy.
Transforming Input Data with Filters 261

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.

You implement your custom filtering algorithm as a callback function or a callback


class method. A callback is a function or a public method of a class which is called by
the Callback filter and is passed the value to be filtered and, optionally, user-defined
argument(s).

The public methods provided by the Callback filter are listed in table 8.17.

Table 8.17. Public methods of the Callback filter

Class name Description


filter($value) Executes a callback function as a filter.
setCallback($callback) Sets a new callback for this filter.
getCallback() Returns callback set for the filter.
setCallbackParams($params) Sets parameters for the callback.
getCallbackParams() Gets parameters for the callback.

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:

1. Strip out any non-numeric characters of the input value.


Transforming Input Data with Filters 262

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).

In the filterPhone() callback method, we do the following:

• 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.

8.7 Writing Your Own Filter


An alternative to using the Callback filter is writing your own filter class implementing the
FilterInterface interface. Then, this filter may be used in forms of your web application (or, if
you wish, outside a form).
To demonstrate how to create your own filter, we will write the PhoneFilter class encapsulating
the phone filtering algorithm we used with the Callback filter example.

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

Table 8.18. Public methods of the PhoneFilter filter

Method name Description


__construct($options) Constructor - accepts an optional argument $options, which is needed to
set filter options at once.
setFormat($format) Sets the phone format option.
getFormat() Returns the phone format option.
filter($value) Runs the phone filter.

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

79 // Pad with zeros if the number of digits is incorrect.


80 $digits = str_pad($digits, 7, "0", STR_PAD_LEFT);
81
82 // Add the dash.
83 $phoneNumber = substr($digits, 0, 3) . '-'. substr($digits, 3, 4);
84 }
85
86 return $phoneNumber;
87 }
88 }

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.

8.7.1 Using the PhoneFilter Class


When the PhoneFilter filter class is ready, you can easily start using it in the feedback form
(or in another form) as follows. It is assumed that you call the following code inside of the
ContactForm::addInputFilter() method:
Transforming Input Data with Filters 268

$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;

// Create PhoneFilter filter.


$filter = new PhoneFilter();

// Configure the filter.


$filter->setFormat(PhoneFilter::PHONE_FORMAT_INTL);

// Filter a string.
$filteredValue = $filter->filter('12345678901');

// The expected filter's output is the '1 (234) 567-8901'.

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:

• filters casting input data to a specified type;


• filters performing manipulations on a file path;
Transforming Input Data with Filters 269

• filters performing compression and encryption of input data;


• filters manipulating string data; and
• proxy filters wrapping other filters.

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.

ZF2 components covered in this chapter:

Component Description
Zend\Validator Implements various validator classes.
Zend\InputFilter Implements a container for filters/validators.

9.1 About Validators


A validator is designed to take some input data, check it for correctness, and return a boolean
result telling whether the data is correct. If the data is incorrect, the validator generates the list
of errors describing why the check didn’t pass.

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

10 // the most recent isValid() call returned false.


11 public function getMessages();
12 }

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.

A concrete validator class implementing the ValidatorInterface interface may have


additional methods. For example, many validator classes have methods allowing to
configure the validator (set validation options).

9.2 Standard Validators Overview


Standard ZF2 validators are provided by the Zend\Validator component ¹. Standard validator
classes inheritance is shown in figure 9.1. As you can see from the figure, most of them are
derived from AbstractValidator base class.
Standard validators together with their brief description are listed in table 9.1. As you may notice
from the table, they can be roughly divided into several groups:

• 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

Figure 9.1. Validator class inheritance

Table 9.1. Standard validators

Class name Description


EmailAddress Returns boolean true if the value is a valid E-mail address; otherwise returns
false.
Hostname Checks whether the value is a valid host name.
Barcode Returns boolean true if and only if the value contains a valid barcode.
CreditCard Returns true if and only if the value follows the common format of credit card
number (Luhn algorithm, mod-10 checksum).
Iban Returns true if the value is a valid International Bank Account Number
(IBAN); otherwise returns false.
Checking Input Data with Validators 273

Table 9.1. Standard validators

Class name Description

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).

9.3 Validator Behaviour in Case of Invalid or


Unacceptable Data
If you pass a validator some data that doesn’t pass the check, the validator internally creates the
list of error messages that can be retrieved with the getMessages() method. For example, look
Checking Input Data with Validators 274

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.

It is recommended to check certain validator’s documentation to be aware of its actual


behaviour in case of unacceptable data.

9.4 Instantiating a Validator


In Zend Framework 2, there are several methods of creating a validator:

• instantiating it manually (with the new operator);


• creating it with a factory class (by passing an array configuration); this way is used the
most frequently when adding validation rules in a form;
• instantiating it implicitly with the StaticValidator wrapper class.

Next, we will cover these three methods in more details.


Checking Input Data with Validators 275

9.4.1 Method 1. Manual Instantiation of a Validator


A validator in general can be used not only with forms, but also for validation of an arbitrary
data. In order to do that, you simply create an instance of the validator class, configure the
validator by using the methods it provides, and call the isValid() method on the validator.
For example, let’s consider the usage of the EmailAddress validator which checks an E-mail
address for conformance to RFC-2822² standard. An E-mail address typically consists of the
local part (user name) followed by the “at” character (@), which is in turn followed by the host
name. For example, in the “name@example.com” E-mail address, “name” is the local part, and
“example.com” is the host name.

The EmailAddress validator is useful for checking an user-entered E-mail addresses


on your forms for correctness. The validator will check for the correctness of the local
part and the host name, for presence of the “at” character (@) and, optionally, will
connect to the recipient’s host and query the DNS service for existence of the MX
(Mail Exchanger) record ³.

The methods provided by the EmailAddress validator are listed in table 9.2:

Table 9.2. Public methods of the EmailAddress validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options
allowing to configure it.
isValid($value) Returns true if the value is a valid E-mail address
according to RFC-2822; otherwise returns false.
getMessages() If validation failed, this method will return an array
of error messages.
useDomainCheck($domain) Tells the validator to check the host name part for
correctness.
getDomainCheck() Returns true if host name part check is enabled.
setHostnameValidator($hostnameValidator) Attaches the validator to use for checking host
name part of the E-mail address.
getHostnameValidator() Returns the validator used for checking host name
part of the E-mail address.
setAllow($allow) Sets the allowed types of host names to be used in
an E-mail address.
getAllow() Returns the allowed types of host names.
useMxCheck($mx) Sets whether to perfrom the check for a valid MX
record via DNS service.
getMxCheck($mx) Returns true if MX check mode is enabled.
useDeepMxCheck($deep) Sets whether to use deep validation for MX records.
getDeepMxCheck() Returns true if the deep MX check mode is enabled;
otherwise returns false.
isMxSupported() Returns true if MX checking via getmxrr() PHP
function is supported in the system; otherwise
²https://tools.ietf.org/html/rfc2822 returns false.
³An MX record is a type of record used in the Domain Name System (DNS). MX records define one or several mail server addresses assigned
to recipient’s domain.
Checking Input Data with Validators 276

Table 9.2. Public methods of the EmailAddress validator

Method name Description


getMXRecord() After validation, returns the found MX record
information.

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:

• ALLOW_DNS Allow a domain name (this is the default),


• IP_ADDRESS Allow an IP address,
• ALLOW_LOCAL Allow local network name,
• ALLOW_ALL Allow all of the above.

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).

9.4.2 Method 2. Using StaticValidator Wrapper


An alternative way of manual validator instantiation is by using the StaticValidator class. The
StaticValidator class is some kind of a “proxy” designed for automatic validator instantiation,
configuration and execution. For example, let’s consider how to create the same EmailAddress
validator, configure it and call its isValid() method:

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.

The StaticValidator doesn’t provide an ability to extract the list of human-readable


validation errors. However, since the StaticValidator is designed to be used outside
forms, and not intended for displaying results to a human, this seems to be not a big
disadvantage.

9.4.3 Method 3. Using an Array Configuration


When using validators with form’s validation rules, you typically do not construct a validator
object explicitly as we did in the previous section, instead you pass an array configuration to the
factory class which automatically constructs the validator for you and (optionally) configures it.
We already saw how this works when adding validation rules for the feedback form in Chapter
7.
For example, let’s show how to construct the same EmailAddress filter with the help of the
factory:

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.

9.5 About Validator Plugin Manager


When creating a validator with a factory, you can use either the fully qualified validator
class name or its short alias. The short aliases for the standard validators are defined by the
ValidatorPluginManager class.

The ValidatorPluginManager class defines validator aliases.

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 Validator Usage Examples


Next, we will consider the usage of the most important standard validators. These describe the
methods (and options) a validator has, and provide a code example showing how to instantiate
and apply the validator to input data.

9.6.1 Validators for Checking Value Conformance to Certain


Format
In this section, we will consider usage examples of the validators from the group of validators
designed for checking if input value conforms to certain format.

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

Table 9.3. Public methods of the Ip validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true if and only if value is a valid IP address.
getMessages() If validation failed, this method will return an array of error messages.
setOptions($options) Sets validator options.

The setOptions() method provides an ability to set allowed types of IP addresses:

• allowipv4 to allow IPv4 addresses;


• allowipv6 to allow IPv6 addresses;
• allowipvfuture to allow IPvFuture addresses;
• allowliteral to allow IPv6 literal addresses.

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();

// Configure the validator.


$validator->setOptions(array(
'allowipv4' => true, // Allow IPv4 addresses.
'allowipv6' => true, // Allow IPv6 addresses.
'allowipvfuture' => false, // Allow IPvFuture addresses.
'allowliteral' => true, // Allow IP addresses in literal format.
);

// Check if input value is a valid IP address (IPv4).


$isValid = $validator->validate('192.168.56.101'); // Returns true

// Check if input value is a valid IP address (IPv6).


$isValid2 = $validator->validate(
'2001:0db8:85a3:0000:0000:8a2e:0370:7334'); // Returns true

// Pass an invalid string (not containing an IP address).


$isValid3 = $validator->validate('abc'); // Returns false

9.6.1.2 Hostname Validator

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

• a DNS Hostname (e.g. “example.com”);


• an IP address (e.g. “192.168.56.101”);
• a local host name (e.g. “localhost”).

The public methods provided by the validator are listed in table 9.4:

Table 9.4. Public methods of the Hostname validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true when the value is a valid host name; otherwise
returns false.
getMessages() If validation failed, this method will return an array of error
messages.
setIpValidator($ipValidator) Optionally, allows to set own IP address validator.
getIpValidator() Retrieves attached IP address validator.
setAllow() Defines the type(s) of host names which are allowed.
getAllow() Returns allowed host names types.
useIdnCheck() Defines if Internationalized Domain Names (IDN) check is
enabled. This option defaults to true.
getIdnCheck() Returns true if IDN check is enabled.
useTldCheck() Defines if Top Level Domain (TLD) check is enabled. This option
defaults to true.
getTldCheck() Returns true if TLD check is enabled.

You can set which host name types are allowed with the setAllow() method. It accepts a
combination of the following constants:

• ALLOW_DNS Allows Internet domain names (e.g., example.com);


• ALLOW_IP Allows IP addresses;
• ALLOW_LOCAL Allows local network names (e.g., localhost, www.localdomain);
• ALLOW_URI Allows URI host names.
• ALLOW_ALL Allows all types of host names.

By default, only Internet domain names are allowed.


The host name check consists of several stages, some of which may be omitted based on validator
options:

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;

// Create the Hostname validator.


$validator = new Hostname();

// Configure the validator.


$validator->setAllow(Hostname::ALLOW_DNS|Hostname::ALLOW_IP);

// Check a host name.


$isValid = $validator->validate('site1.example.com');
// Returns true.
$isValid2 = $validator->validate('abc');
// Returns false (not a valid host name).

9.6.1.3 Uri Validator

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:

Table 9.5. Public methods of the Uri validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true when the value is a valid URI; otherwise
returns false.
getMessages() If validation failed, this method will return an array of error
messages.
setUriHandler($uriHandler) Sets the URI handler object for this validator.
getUriHandler() Retrieves the URI handler object.
setAllowAbsolute($allowAbsolute) Tells the validator whether absolute URIs are accepted.
⁹An internationalized domain name (IDN) is an Internet domain name that contains at least one label that is displayed, in whole or in part,
in a language-specific script or alphabet, like Arabic, Chinese or Russian.
¹⁰A Uniform Resource Identifier (URI) is a compact sequence of characters that identifies an abstract or physical resource. An Uniform
Resource Locator (URL) is a type of URI. But, that doesn’t mean all URIs are URLs.
Checking Input Data with Validators 284

Table 9.5. Public methods of the Uri validator

Method name Description


getAllowAbsolute() Returns true if absolute URIs are accepted.
setAllowRelative($allowRelative) Tells the validator whether relative URIs are accepted.
getAllowRelative() Returns true if relative URIs are accepted.

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;

// Create the Uri validator.


$validator = new Uri();

// Configure the validator.


$validator->setAllowAbsolute(true);
$validator->setAllowRelative(true);

// Check an URI.
$isValid = $validator->validate(
'http://site1.example.com/application/index/index');
// Returns true.
$isValid2 = $validator->validate('index/index');
// Returns true.

9.6.1.4 Date Validator

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

Table 9.6. Public methods of the Date validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true when the value is a string containing a date in expected
format; otherwise returns false.
getMessages() If validation failed, this method will return an array of error messages.
setFormat($format) Sets an acceptable date format.
getFormat() Retrieves the expected format.

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;

// Create validator instance.


$validator = new Date();

// Configure validator.
$validator->setFormat('Y-m-d');

// Check if the input value is a date having expected format.


$isValid = $validator->isValid('2014-04-04'); // Returns true.
$isValid2 = $validator->isValid('April 04, 2014');
// Returns false (format is unexpected).

9.6.1.5 Regex Validator

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:

Table 9.7. Public methods of the Regex validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true if and only if $value matches the given regular expression
pattern.
getMessages() If validation failed, this method will return an array of error messages.
Checking Input Data with Validators 286

Table 9.7. Public methods of the Regex validator

Method name Description


setPattern($pattern) Sets the regular expression pattern.
getPattern() Retrieves the regular expression pattern.

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;

// Create Regex validator.


$validator = new Regex();

// Set regular expression to check for an IP address.


$validator->setPattern('\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b');

// Check for regular expression match.


$isValid = $validator->isValid("127.0.0.1"); // returns true.
$isValid2 = $validator->isValid("123"); // returns false.

9.6.2 Validators for Checking a Numerical Value Lies in a Given


Range
In this section, we will consider usage examples of the validators from the group of validators
designed for checking if input value lies in a given range.

9.6.2.1 NotEmpty Validator

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

Table 9.8. Public methods of the NotEmpty validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true if and only if $value is not an empty value.
getMessages() If validation failed, this method will return an array of error messages.
setType($type) Set the value types that to consider as empty values.
getType() Returns the types.
getDefaultType() Returns the default types.

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.

Table 9.9. Type constants

Constant Numeric Value Literal Equivalent Description


BOOLEAN 1 “boolean” Consider boolean false as an empty
value.
INTEGER 2 “integer” Consider integer 0 as an empty value.
FLOAT 4 “float” Consider float 0.0 as an empty value.
STRING 8 “string” Consider empty string ‘’ as an empty
value.
ZERO 16 “zero” Consider string containing the single
character zero (‘0’) as an empty value.
EMPTY_ARRAY 32 “array” Consider an empty array as an empty
value.
NULL 64 “null” Consider null as an empty value.
PHP 127 “php” Consider the value empty if the empty()
PHP function would return true on it.
SPACE 128 “space” Consider a string which contains only
white spaces as an empty value.
OBJECT 256 “object” Returns true. false will be returned
when object is not allowed but an object
is given.
OBJECT_STRING 512 “objectstring” Returns false when an object is given
and it’s __toString() method returns an
empty string.
OBJECT_COUNT 1024 “objectcount” Returns false when an object is given, it
has an Countable interface and it’s count
is 0.
ALL 2047 “all” Consider all above types as empty values.

Below, a code example demonstrating the usage of the NotEmpty validator is provided.
Checking Input Data with Validators 288

<?php
use Zend\Validator\NotEmpty;

// Create validator instance.


$validator = new NotEmpty();

// Configure validator.
$validator->setType(NotEmpty::ALL);

// Check if input value not empty.


$isValid1 = $validator->isValid('some string'); // returns true
$isValid2 = $validator->isValid(''); // returns false
$isValid3 = $validator->isValid(0); // returns false

9.6.2.2 Between Validator

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:

Table 9.10. Public methods of the Between validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true if and only if value’s length is within the given range.
getMessages() If validation failed, this method will return an array of error messages.
setMin($min) Sets the minimum limit.
getMin() Retrieves the minimum limit.
setMax($max) Sets the maximum limit.
getMax() Retrieves the maximum limit.
setInclusive($inclusive) Sets whether to compare if the value lies in the given boundaries
inclusively.
getInclusive() Returns the inclusive option.

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;

// Create validator instance.


$validator = new Between();

// Configure validator.
$validator->setMin(1);
$validator->setMax(10);
$validator->setInclusive(true);

$isValid1 = $validator->isValid(5); // returns true.


$isValid2 = $validator->isValid(10); // returns true.
$isValid3 = $validator->isValid(0); // returns false (value is too small).
$isValid4 = $validator->isValid(15); // returns false (value is too big).

9.6.2.3 InArray Validator

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:

Table 9.11. Public methods of the InArray validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true if and only if value belongs to the given array.
getMessages() If validation failed, this method will return an array of error messages.
setHaystack($haystack) Sets the array to search in.
getHaystack() Returns the array of allowed values.
setStrict($strict) Sets strict comparison mode.
getStrict() Whether strict comparison mode enabled?
setRecursive($recursive) Tells the validator to search recursively.
getRecursive() Whether the recursive search is enabled?

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:

• COMPARE_NOT_STRICT Do not perform strict check of variable type.


• COMPARE_NOT_STRICT_AND_PREVENT_STR_TO_INT_VULNERABILITY Do not perform strict check
of variable type, but prevent false positive comparisons of string to integer (e.g. "asdf" ==
0). This is the default option.
Checking Input Data with Validators 290

• COMPARE_STRICT Check both variable type and value.

Below, a code example demonstrating the usage of the InArray validator is provided.

<?php
use Zend\Validator\InArray;

// Create validator instance.


$validator = new InArray();

// Configure validator.
$validator->setHaystack(array(1, 3, 5));

// Perform validation.
$isValid1 = $validator->isValid(1); // returns true.
$isValid2 = $validator->isValid(2); // returns false.

9.6.2.4 StringLength Validator

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:

Table 9.12. Public methods of the StringLength validator

Method name Description


__construct($options) Constructs the validator. Accepts the list of options.
isValid($value) Returns true if and only if value’s length is within the given range.
getMessages() If validation failed, this method will return an array of error messages.
setMin($min) Sets the minimum limit.
getMin() Retrieves the minimum limit.
setMax($max) Sets the maximum limit.
getMax() Retrieves the maximum limit.
setEncoding($encoding) Sets a new encoding to use.
getEncoding() Retrieves the encoding.

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;

// Create validator instance.


$validator = new StringLength();

// Configure the validator.


$validator->setMin(1);
$validator->setMax(10);

$isValid1 = $validator->isValid("string"); // returns true.


$isValid2 = $validator->isValid(""); // returns false (value is too short).
$isValid3 = $validator->isValid(
"a very long string"); // returns false (value is too long).

9.6.3 Organizing Validators in a Chain


Validators can be organized in a sequence. This is accomplished by the ValidatorChain class.
When such a compound validator is run, the input value is passed to all validators in turn. The
ValidatorChain validator’s isValid() method returns true if all validators in the chain return
true; otherwise it returns false.

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:

Table 9.13. Public methods of the ValidatorChain validator

Method name Description


isValid($value) Returns true if all validators in the chain return
true.
getMessages() Returns the array of validation error messages.
getValidators() Returns the array of validators in the chain.
count() Returns count of validators in the chain.
attach($validator, $breakChainOnFailure) Attaches a validator to the end of the chain.
prependValidator($validator, Adds a validator to the beginning of the chain.
$breakChainOnFailure)
Checking Input Data with Validators 292

Table 9.13. Public methods of the ValidatorChain validator

Method name Description


attachByName($name, $options, Use the plugin manager to add a validator by
$breakChainOnFailure) name.
prependByName($name, $options, Use the plugin manager to prepend a validator by
$breakChainOnFailure) name.
merge($validatorChain) Merge the validator chain with the one given in
parameter.

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”.

Figure 9.2. Validator chain

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();

// Insert validators into validator chain.


$validator->attachByName('NotEmpty');
$validator->attachByName('StringLength', array('min'=>1, 'max'=>16));
$validator->attachByName('Date', array('format'=>'Y-m-d'));

// Execute all validators in the chain.


$isValid = $validator->isValid('2014-04-04'); // Returns true.

9.6.4 Custom Validation with the Callback Validator


The Callback validator can be a wrapper for your custom validation algorithm. For example,
this may be useful when a standard validator is not suitable, and you need to apply your own
checking algorithm to the data. The public methods provided by the Callback validator are listed
in table 9.14.
Checking Input Data with Validators 293

Table 9.14. Public methods of the Callback validator

Class name Description


isValid($value, $context) Executes a callback function as a validator.
getMessages() If validation failed, this method will return an array of error
messages.
setCallback($callback) Sets a new callback for this filter.
getCallback() Returns callback set for the filter.
setCallbackOptions($options) Sets options for the callback.
getCallbackOptions() Get parameters for the callback.

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:

• international format looking like “1 (234) 567-8901”;


• and local format, which looks like “567-8901”.

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

17 'label' => 'Your Phone',


18 ),
19 ));
20 }
21
22 private function addInputFilter() {
23
24 // ...
25
26 $inputFilter->add(array(
27 'name' => 'phone',
28 'required' => true,
29 'validators' => array(
30 array(
31 'name' => 'Callback',
32 'options' => array(
33 'callback' => array($this, 'validatePhone'),
34 'callbackOptions' => array(
35 'format' => 'intl'
36 )
37 )
38 )
39 )
40 );
41 }
42
43 // Custom validator for a phone number.
44 public function validatePhone($value, $context, $format) {
45
46 // Determine the correct length and pattern of the phone number,
47 // depending on the format.
48 if($format == 'intl') {
49 $correctLength = 16;
50 $pattern = '/^\d\ (\d{3}\) \d{3}-\d{4}$/';
51 } else { // 'local'
52 $correctLength = 8;
53 $pattern = '/^\d{3}-\d{4}$/';
54 }
55
56 // Check phone number length.
57 if(strlen($value)!=$correctLength)
58 return false;
59
60 // Check if the value matches the pattern.
61 $matchCount = preg_match($pattern, $value);
62
Checking Input Data with Validators 295

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.

9.7 Writing Own Validator


An alternative of using the Callback validator is writing your own validator class implementing
the ValidatorInterface interface. Then, this validator may be used in forms of your web
application.
To demonstrate how to create your own validator, we will write the PhoneValidator class
encapsulating the phone validation algorithm we used with the Callback validator example.

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):

Table 9.18. Public methods of the Callback validator

Method name Description


__construct($options) Constructor. Accepts an optional argument $options which is needed to
set validator options at once.
setFormat($format) Sets the phone format option.
getFormat() Returns the phone format option.
isValid($value) Returns true when the value is a valid phone number; otherwise returns
false.
getMessages() If validation failed, this method will return an array of error messages.
Checking Input Data with Validators 296

For the PhoneValidator, we will have three possible error messages:

• 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).

9.7.1 Using the PhoneValidator Class


When the PhoneValidator validator class is ready, you can easily start using it in the feedback
form (or in another form) as follows. It is assumed that you call the following code inside of the
ContactForm::addInputFilter() method:

$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).

Figure 9.3. Phone number validation error

If you wish, you can use the PhoneValidator outside of forms, as shown in code example below:

<?php
use Application\Service\PhoneValidator;

// Create PhoneValidator validator


$validator = new PhoneValidator();

// Configure the validator.


$validator->setFormat(PhoneValidator::PHONE_FORMAT_INTL);

// Validate a phone number


$isValid = $validator->isValid('1 (234) 567-8901'); // Returns true.
$isValid2 = $validator->isValid('12345678901'); // Returns false.

if(!$isValid2) {
// Get validation errors.
$errors = $validator->getMessages();
}

9.8 Using Filters & Validators Outside a Form


In this section, we will provide an example of how you can use filters and/or validators in your
controller to transform and check the data extracted from GET and/or POST variables.
Checking Input Data with Validators 301

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.

Below, you can find the code of the action method:

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:

• validators for checking value conformance to certain format;


• validators for checking a numerical value lies in a given range;
• validators working as “proxies” to other 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.

10.1 About HTTP File Uploads


HTML forms have capability for uploading files of arbitrarily large size ¹. The files are typically
transmitted through HTTP POST method ².
By default, HTTP uses the URL encoding for transfers of form data, and you could see how
that encoding looks like when reading the GET and POST Methods section of the previous
chapter. However, this is inefficient for uploading large files, since URL-encoding binary data
dramatically increases the length of the HTTP request. For the purpose of uploading files, it
is instead recommended to use the so called “binary transfer encoding” described in the next
section.

10.1.1 HTTP Binary Transfer Encoding


A simple HTML form capable of file uploads is shown in the code example below. The binary
encoding type is enabled by setting the enctype attribute of the form with the value of
“multipart/form-data”:

¹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

1 <form action="upload" method="POST" enctype="multipart/form-data">


2 <input type="file" name="myfile">
3 <br/>
4 <input type="submit" name="Submit">
5 </form>

In line 1, we explicitly set form encoding (enctype attribute) to “multipart/form-data” to utilize


effective binary content transfer encoding for the form.
In line 2, we define an input field with type “file” and name “myfile”. This input field will allow
site visitor to select the file for upload.
If you now save the above mentioned markup to an .html file and open it in your web browser,
you will see the page like in figure 10.1.

Figure 10.1. A simple HTML form capable of file upload

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:

1 POST http://localhost/upload HTTP/1.1


2 Host: localhost
3 Content-Length: 488
4 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64)
5 Content-Type: multipart/form-data; boundary=----j1bOrwgLvOC3dy7o
6 Accept-Encoding: gzip,deflate,sdch
7
8 ------j1bOrwgLvOC3dy7o
9 Content-Disposition: form-data; name="myfile"; filename="Somefile.txt"
10 Content-Type: text/html
11
12 (file binary data goes here)
13 ------j1bOrwgLvOC3dy7o
14 Content-Disposition: form-data; name="Submit"
15
16 Submit Request
17 ------j1bOrwgLvOC3dy7o--
Uploading Files with Forms 305

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.

10.1.2 $_FILES Super-Global Array in PHP


When a site visitor uploads some files to your Apache Web Server, the files are placed to
a temporary location (usually to system temporary directory which is /tmp in Linux and
C:\Windows\Temp in Windows). The PHP script receives the file information to the special super-
global array named $_FILES.

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

• name – original file name (line 4).


• type – MIME ³ type of the file (line 5).
• tmp_name – temporary name for the uploaded file (line 6).
• error – error code signalling about the status of the upload (line 7); error code zero means
the file was uploaded correctly.
• size – file size in bytes (line 8).

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.

Specifying the directory name as the second argument of the move_uploaded_file()


function is suitable when you do not want to rename the file. If you need to save the
uploaded file under another name than its original name, you can specify the full file
path instead of the directory name.

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

10.2 Accessing Uploaded Files in ZF2


In your controller class, you typically do not communicate with the $_FILES array directly,
instead you may use the Request class or the Params controller plugin, as shown in code example
below:

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.

10.3 File Uploads & ZF2 Form Model


To add file uploading capability to your form model, you need to add an element of the
Zend\Form\Element\File class as follows:
Uploading Files with Forms 308

1 // Add the following code inside of form's addElements() method.


2
3 // Add the "file" field.
4 $this->add(array(
5 'type' => 'file',
6 'name' => 'file',
7 'attributes' => array(
8 'id' => 'file'
9 ),
10 'options' => array(
11 'label' => 'Upload file',
12 ),
13 ));

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”.

10.4 Validating Uploaded Files


Uploaded files need to be checked for correctness as any other form data. For example, you may
need to check that:

• 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

Table 10.1. Standard File Validators

Class name Short alias Description


Count FileCount Checks whether the file count is in a given range
(min, max).
WordCount FileWordCount Calculates the number of words in a file and checks
whether it lies in a given range.
Upload FileUpload Performs security checks ensuring that all given files
were really uploaded through HTTP POST and there
were no upload errors.
UploadFile FileUploadFile Performs security checks ensuring that a file really
was uploaded through HTTP POST and there were
no upload errors.
Size FileSize Checks whether the file size lies in a given range.
FilesSize FileFilesSize Checks that the summary size of all given files lies in
a given range.
Extension FileExtension Checks that the extension of a file belongs to a set of
allowed extensions.
ExcludeExtension FileExcludeExtension Checks that the extension of a file DOES NOT belong
to a set of extensions.
MimeType FileMimeType Checks that the MIME type of a file belongs to the list
of allowed MIME types.
ExcludeMimeType FileExcludeMimeType Checks that the MIME type of a file DOES NOT
belong to the list of MIME types.
IsImage FileIsImage Checks that the file is a graphical image (JPEG, PNG,
GIF, etc.)
ImageSize FileImageSize Checks that the image file’s dimensions lie in a given
range.
Exists FileExists Checks whether the file exists on disk.
NotExists FileNotExists Checks whether the file doesn’t exist on disk.
IsCompressed FileIsCompressed Checks that the file is an archive (ZIP, TAR, etc.)
Hash FileHash Checks that the file content matches the given
hash(es).
Crc32 FileCrc32 Checks that the file content has the given CRC32
check sum.
Sha1 FileSha1 Checks that the file content has the given SHA-1 hash.
Md5 FileMd5 Checks that the file content has the given MD5 hash.

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.

10.5 Filtering Uploaded Files


Zend Framework 2 provides several filters intended for “transforming” file fields. Those filter
classes (listed in table 10.2) belong to Zend\Filter component and live in Zend\Filter\File
namespace.

Table 10.2. Standard File Filters

Class name Short alias Description


Rename FileRename Renames/moves an arbitrary file.
RenameUpload FileRenameUpload Renames/moves the uploaded file with security checks.
Encrypt FileEncrypt Encrypts a given file and stores the encrypted file content.
Decrypt FileDecrypt Decrypts a given file and stores the decrypted file content.
LowerCase FileLowerCase Converts file content to lower case letters.
UpperCase FileUpperCase Converts file content to upper case letters.

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.

10.6 InputFilter Container & File Uploads


As you might remember, the filters and validators attached to a form model are typically stored
in an InputFilter container which consists of inputs (an input is typically represented by the
Input class belonging to the Zend\InputFilter namespace. For usual form fields, the filters are
executed before validators, and validators are executed after filters.
However, for file uploads, there are some important differences:

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).

10.6.2 Executing Validators before Filters


For usual form fields, the filters are typically executed before validators, and validators are
executed after filters. However, for file uploads, this sequence is opposite.

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:

• Call the setData() method to fill in the form with data.


• Call the isValid() method to execute validators in the input filter attached to form.
• On successful validation, call the getData() to execute filters and extract the filtered and
validated data from the input filter attached to form.
• On failure, call the getMessages() to retrieve the validation error messages.
Uploading Files with Forms 313

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.

10.7 Controller Action & File Uploads


In this section, we will provide a short code example showing how to handle file uploads in a
controller action method. We will attract reader’s attention to the aspects specific to file uploads.
Assume we want to add a web page displaying a form (let’s name it YourForm) capable of file
uploads. For that page, we need to add the uploadAction() method to a controller class:

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.

10.8 Example: Image Gallery


To demonstrate the usage of file uploads in Zend Framework 2, we will create an Image Gallery
that will consist of two web pages: the image upload page allowing to upload an image (figure
10.2); and the gallery page containing the list of uploaded images (figure 10.3).

You can see the working Image Gallery example in the Form Demo sample application
bundled with this book.
Uploading Files with Forms 315

Figure 10.2. Image Upload Page

Figure 10.3. Image Gallery Page

For this example, we will create the following things:

• the ImageForm form model capable of image file uploads;


• the ImageManager service class designed for getting the list of uploaded images, retrieving
information about an image, and resizing an image;
• the ImageController class which will contain action methods serving the web pages;
• a view template .html file per each controller’s action method.
Uploading Files with Forms 316

10.8.1 Adding ImageForm Model


For this example, we will need a form model which will be used for image file uploads. We will
call that form model class the ImageForm. This class will allow us to upload an image file to the
server. The form will have the following fields:

• 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

34 'options' => array(


35 'label' => 'Image file',
36 ),
37 ));
38
39 // Add the submit button.
40 $this->add(array(
41 'type' => 'submit',
42 'name' => 'submit',
43 'attributes' => array(
44 'value' => 'Submit',
45 'id' => 'submitbutton',
46 ),
47 ));
48 }
49 }

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.

Actually, explicitly setting the “enctype” attribute in form’s constructor is optional,


because Zend\Form\Element\File element performs that automatically when you call
form’s prepare() method.

10.8.2 Adding Validation Rules to ImageForm Model


To demonstrate the usage of validators and filters designed to work with file uploads, we will
add those to the ImageForm form model class. We want to achieve the following goals:

• 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 }

In the code above, we add the following file validators:

• 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

After that, do not forget to restart Apache HTTP Server.

10.8.3 Writing ImageManager Service


Because we strive to write code conforming to Domain Driven Design pattern, we will create
a service model class encapsulating the functionality for image management. We will call this
Uploading Files with Forms 320

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):

Table 10.3. Public methods of the ImageManager class.

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.

To add those three methods, change the code as follows:

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');

10.8.4 Adding ImageController


For the Image Gallery example, we will create the ImageController controller class. The
controller will have the following action methods (listed in table 10.4):

Table 10.4. Action methods of the ImageController class.

Action Method Description


ImageController:uploadAction() Shows the image upload page allowing to upload a single
image.
ImageController:indexAction() Displays the image gallery page with the list of uploaded
images.
ImageController:fileAction() Provides an ability to download a full-size image or a small
thumbnail for an image.
Uploading Files with Forms 325

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.

10.8.4.1 Adding Upload Action & Corresponding View Template

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 }

In the uploadAction() method above, we do the following.


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
Uploading Files with Forms 327

$_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.

10.8.4.2 Adding Index Action & Corresponding View Template

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).

10.8.4.3 Adding File Action

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:

• the “name” parameter defines the file name for preview;


• the “thumbnail” parameter is a flag telling whether we want to dump the full image or its
small copy.

Change the ImageController.php file as follows:


Uploading Files with Forms 331

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.

10.8.4.4 Registering the ImageController

Finally, register the ImageController in the module.config.php configuration file:


Uploading Files with Forms 333

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

chmod -R 775 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.

Figure 10.4. Image Gallery Page


Uploading Files with Forms 334

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).

Figure 10.5. File Validation Errors

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

Figure 10.6. Opening an Image in Natural Size

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 Form Security Elements


We will consider the usage of two form security elements provided by Zend Framework 2:
Captcha and Csrf (both classes belong to Zend\Form\Element namespace). By adding those
elements to your form model (and rendering them in a view template), you will make your
form resistant to hacker attacks.

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).

Figure 11.1. CAPTCHA examples

A typical CAPTCHA test works using the following algorithm:


Advanced Usage of Forms 337

1. Some secret sequence of characters (word) is generated server-side.


2. The secret word is saved in a PHP session variable.
3. The distorted image is generated based on the secret word. The image is then displayed on
the web page to site user.
4. The site user is asked to type characters shown on the image.
5. If the characters typed by user are the same as the secret word saved in the session, the
test is considered passed.

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).

11.1.1.1 CAPTCHA Types

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

11.1.1.2 CAPTCHA Form Element & View Helper

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.

Figure 11.2. CAPTCHA adapter classes

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:

<?php echo $this->formElement($form->get('captcha')); ?>

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.

11.1.2 Example 1: Adding Image CAPTCHA to the ContactForm

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>

Figure 11.3. Image CAPTCHA

11.1.3 Example 2: Adding a FIGlet CAPTCHA to the ContactForm


To use the FIGlet CAPTCHA element with your form, replace the form element definition from
the previous example with the following code:
Advanced Usage of Forms 343

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

Figure 11.4. FIGlet CAPTCHA

11.1.4 Example 3: Adding reCaptcha CAPTCHA to the


ContactForm
It may look surprising, but there are two purposes of the reCAPTCHA service: the main one is to
use wide community of users to help Google to recognize text on images, and the second one is to
generate CAPTCHA images. How does this work? Well, probably you’ve heard of Google Books
Search that is an automated service for digitizing books. But, sometimes the text recognition
algorithms become stuck on certain word or phrase. In this situation, a human attention would
be required to recognize the word and type it in. People in Google are smart enough to use other
people to do that work for free.
Look at figure 11.1, c for example of reCAPTCHA image. It usually consists of two words. The
first word is known to machine, and the second one is the word on which text recognition
algorithm has stuck. The user enters both words, and only the first one is used for CAPTCHA
test; by entering the second one, the user helps digitizing books.
To use reCAPTCHA in your forms, you first need to install the ZendService\Recaptcha
component ⁵ by typing the following from your command shell:

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.

Figure 11.5. ReCaptcha Registration Page

To add the reCAPTCHA element to your form model, replace the form element definition from
the previous example with the following code:

1 // Add the CAPTCHA field


2 $this->add(array(
3 'type' => 'captcha',
4 'name' => 'captcha',
5 'attributes' => array(
6 ),
7 'options' => array(
8 'label' => 'Human check',
9 'captcha' => array(
10 'class' => 'ReCaptcha',
11 // Here, paste your public key string
12 'privKey' => '<your_public_key>',
13 // Here, paste your private key string
14 'pubKey' => '<your_private_key>',

⁶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.

Figure 11.6. reCAPTCHA

11.1.5 CSRF Prevention


Cross-site request forgery (CSRF) is a kind of hacker attack which forces the user’s browser to
transmit an HTTP-request to an arbitrary site. Through the CSRF attack, the malicious script is
Advanced Usage of Forms 347

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.

Figure 11.7. A CSRF attack example

Figure 11.7 illustrates an example CSRF attack on a payment gateway web-site:

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.

11.1.5.1 Example: Adding a CSRF Element to Form

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:

1 // Add the CSRF field


2 $this->add(array(
3 'type' => 'csrf',
4 'name' => 'csrf',
5 'options' => array(
6 'csrf_options' => array(
7 'timeout' => 600
8 )
9 ),
10 ));

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:

<?php echo $this->formElement($form->get('csrf')); ?>

When the PHP renderer evaluates the view template, it generates the HTML markup for the
CSRF field like shown below:

<input type="hidden" name="csrf" value="1bc42bd0da4800fb55d16e81136fe177">

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.

What happens if CSRF element validation fails?


If during the form validation the CSRF check fails, the form is considered invalid and
the user will see it again to fix input errors, but he won’t see the error message for the
CSRF element (we don’t want hackers to know for sure what’s wrong with the form).

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.

Doctrine is a third-party library, it is not part of Zend Framework 2. We cover it in this


book because it provides an easy way of adding database support to your ZF2-based
web application.

12.1 Get Blog Example from GitHub


For demonstration of Doctrine ORM usage, in this chapter, we will create a real-life Blog web
site that does the following:

• 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

Figure 12.1. Blog home page

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

Figure 12.2. Blog admin page

12.2 Creating a Simple MySQL Database


For the Blog example to work, we need to have a database. In this book, we use MySQL database
management system, which is very simple in installation and administration.

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

Welcome to the MySQL monitor. Commands end with ; or \g.


Your MySQL connection id is 1768
Server version: 5.5.37-0ubuntu0.12.04.1 (Ubuntu)

Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its


affiliates. Other names may be trademarks of their respective
owners.

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.

12.2.1 Creating New Schema


Let’s create a database schema and name it blog. To do that, type the following MySQL statement
and press Enter:

CREATE SCHEMA blog;

The expected output of this command is the following:


Query OK, 1 row affected (0.01 sec)

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:

GRANT ALL PRIVILEGES ON blog.* TO `blog`@`localhost` IDENTIFIED BY '<passwd>';

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 |
+--------------------+

12.2.2 Creating Tables


Next, we will create three tables typical for any simple blog: the post table will contain posts,
the comment table will contain comments to posts, and, finally, the tag table will contain tags (a
tag is some kind of a key word describing a blog post well).
Additionally, we will create the fourth auxiliary table post_tag that will be used to create many-
to-many relation between the post and the tag tables.
Make the blog database current by typing the following from MySQL command prompt:
use blog;

To create the post table, type the following MySQL statement:

CREATE TABLE `post` (


`id` int(11) PRIMARY KEY AUTO_INCREMENT,
`title` text NOT NULL,
`content` text NOT NULL,
`status` int(11) NOT NULL,
`date_created` timestamp NOT NULL
);

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

The expected output of this command is the following:


Query OK, 0 rows affected (0.22 sec)
Next, create the comment table by typing the following:

CREATE TABLE `comment` (


`id` int(11) PRIMARY KEY AUTO_INCREMENT,
`content` text NOT NULL,
`author` varchar(128) NOT NULL,
`date_created` timestamp NOT NULL
);

Then, create the tag table:

CREATE TABLE `tag` (


`id` int(11) PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(128)
);

And finally, create the post_tag table:

CREATE TABLE `post_tag` (


`id` int(11) PRIMARY KEY AUTO_INCREMENT,
`post_id` int(11) NOT NULL,
`tag_id` int(11) NOT NULL
);

Let’s fill the tables we have created with some sample data:

INSERT INTO tag(`name`) VALUES('zf2');


INSERT INTO tag(`name`) VALUES('book');
INSERT INTO tag(`name`) VALUES('magento');

INSERT INTO post(`title`, `content`, `status`, `date_created`) VALUES(


'Top 10+ Books about Zend Framework 2',
'Post content', 2, '2014-08-09 18:49');

INSERT INTO post(`title`, `content`, `status`, `date_created`) VALUES(


'Getting Started with Magento Extension Development Book Review',
'Post content 2', 2, '2014-08-09 18:51');

INSERT INTO post_tag(`post_id`, `tag_id`) VALUES(1, 1);


INSERT INTO post_tag(`post_id`, `tag_id`) VALUES(1, 2);
INSERT INTO post_tag(`post_id`, `tag_id`) VALUES(2, 2);
INSERT INTO post_tag(`post_id`, `tag_id`) VALUES(2, 3);
Database Management with Doctrine ORM 356

INSERT INTO comment(`post_id`, `content`, `author`, `date_created`) VALUES(


1, 'Excellent post!', 'Oleg Krivtsov', '2014-08-09 19:20');

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.

Figure 12.3. Graphical representation of database schema

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).

12.2.3 Importing Ready Database Schema


In the previous section, we’ve shown how to create the complete database schema that is used
in the Blog sample web application. In the real life, you typically do not type all those SQL
statements in MySQL prompt. Instead, you could type the CREATE TABLE statements to a file and
save it to disk. Then you could just import that file and have ready schema.
For your convenience, the ready schema for Blog sample can be found in APP_DIR/data/schema.mysql.sql
file. The file is a plain text file containing SQL statements. To import the file, go to the
Database Management with Doctrine ORM 357

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)

12.3 Integrating Doctrine ORM with Zend


Framework 2
For easy integration with Zend Framework 2, Doctrine project provides the following two
components (that are actually ZF2 modules):

• DoctrineModule² is a ZF2 module that provides Doctrine basic functionality required by


the ORM component;
• DoctrineORMModule³ integrates Doctrine 2 Object Relational Mapper with Zend Frame-
work 2.

Each of the above Doctrine components is distributed as a Composer-installable package and is


registered in Packagist.org⁴ catalogue. This is very similar to the way that Zend Framework 2
uses for installing its components.
Since Composer packages may depend on each other, it is enough to declare dependency only
on DoctrineORMModule. This package depends on DoctrineModule and on some other Doctrine
components (Doctrine\ORM, Doctrine\DBAL, Doctrine\Common, Doctrine\Annotations, etc.). So,
when you install this component, Composer will install other required components automati-
cally.
²https://github.com/doctrine/DoctrineORMModule
³https://github.com/doctrine/DoctrineORMModule
⁴https://packagist.org/
Database Management with Doctrine ORM 358

12.3.1 Installing Doctrine Components with Composer


In order to install required Doctrine components, we first add a dependency to the composer.json
file located in the root directory of the web application (in this book, we typically denote that
directory as APP_DIR).
To add the dependency, type the following commands from your command shell (replace the
APP_DIR placeholder with the actual directory name of your application):
cd APP_DIR

php composer.phar require doctrine/doctrine-orm-module *

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:

./composer.json has been updated


Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing doctrine/lexer (v1.0)
Downloading: 100%

- Installing doctrine/annotations (v1.1.2)


Downloading: 100%
Database Management with Doctrine ORM 359

- Installing doctrine/collections (v1.2)


Downloading: 100%

- Installing doctrine/cache (v1.3.0)


Downloading: 100%

- Installing doctrine/inflector (v1.0)


Downloading: 100%

- Installing doctrine/common (v2.4.2)


Downloading: 100%

- Installing doctrine/dbal (v2.4.2)


Downloading: 100%

- Installing symfony/console (v2.5.0)


Downloading: 100%

- Installing doctrine/orm (v2.4.2)


Downloading: 100%

- Installing doctrine/doctrine-module (0.8.0)


Downloading: 100%

- Installing doctrine/doctrine-orm-module (0.8.0)


Downloading: 100%

symfony/console suggests installing symfony/event-dispatcher ()


symfony/console suggests installing psr/log (For using the console logger)
doctrine/orm suggests installing symfony/yaml (If you want to use YAML
Metadata Mapping Driver)
doctrine/doctrine-module suggests installing doctrine/data-fixtures (Data
Fixtures if you want to generate test data or bootstrap data for your
deployments)
doctrine/doctrine-orm-module suggests installing zendframework/zend-developer-
tools (zend-developer-tools if you want to profile operations executed by the
ORM during development)
doctrine/doctrine-orm-module suggests installing doctrine/migrations
(doctrine migrations if you want to keep your schema definitions versioned)
Writing lock file
Generating autoload files

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

As a bonus, at the end of installation, Composer “suggests” you to install


some additional packages that might be useful for you (doctrine/migrations,
doctrine/data-fixtures, etc.) If you strongly wish, you may add those dependencies
with the Composer’s require command as well.

When the installation has been finished, you can find the Doctrine files in your APP_DIR/vendor
directory (see the figure 12.4 below).

Figure 12.4. Doctrine files are installed to vendor directory

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.

12.3.2 Loading Doctrine Integration Modules on Application


Start Up
Once you have installed the DoctrineORMModule and all its dependencies, you need to add the
following lines to your APP_DIR/config/application.config.php file to enable the modules:

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).

12.3.3 Doctrine Configuration Overview


To use Doctrine with your ZF2-based web application, you have to provide its configuration.
The configuration tells Doctrine what databases present, how to connect to a database (what
database driver, host, user name and password to use), where to locate entity classes and how
to extract their annotations (metadata), how to store cached data (in the file system or to use a
caching extension like APC), and so on. This section’s goal is to give you a general idea of how
Doctrine configuration looks like.
The default Doctrine configuration is located in the module.config.php config file of the
DoctrineORMModule. Look at the figure 12.5 below to have an idea of how the Doctrine config
“tree” may look like ⁵. You may also refer to the module.config.php file of DoctrineORMModule
for the same reason.
As you can see from the figure 12.5, there is the top-level key named doctrine. Under that key,
there is a number of subkeys containing the following settings:

• 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

Figure 12.5. Graphical representation of Doctrine configuration “tree”

• 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.

• the migrations_configuration key contains settings for database migrations. Database


migrations are used for initializing and updating database schema in a standard and
consistent way.

12.3.4 Overriding the Default Doctrine Configuration


As you already know from Chapter 3, in a ZF2-based web application configuration is typically
divided into two categories: application-wide configuration and module-specific configuration.

• 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”.

By adding your application-specific Doctrine configuration, you extend and/or over-


ride the default configuration tree provided by the DoctrineORMModule.

12.4 Specifying Database Connection Parameters


Below we provide content of the autoload/local.php file of the Blog web application. This config
file contains the application-wide database connection settings for the blog MySQL database
that we created earlier in this chapter:

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.

• The params key contains the connection parameters:


– host may be either the domain name or IP address of the database server;
– user is the MySQL user name with granted permissions to the database;
– password is the secret word for the user name;
– dbname is the name of the database.

Table 12.3. Often Used Database Driver Classes

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

Because the autoload/local.php file contains environment-specific parameters, in ver-


sion control system you store its “distribution template” local.php.dist. Each developer
in your team then renames the local.php.dist file into local.php and enters his own
password instead of the placeholder. The local.php file should not be stored under
version control, because you might want that other people in your team (or other people
having access to your code repository) do not see the actual password.

What happens if I need several database connections?


You can easily add more database connections by pasting other keys below the orm_-
default key. For example, lets assume the case that you have another database for
testing purposes. To let Doctrine know about this database, you create the orm_test
subkey below the orm_default key and fill it with connection parameters.

12.5 About Doctrine Entities


An entity is a PHP class that is designed for storing data. For example, below you can find several
oftenly used examples of entities:

• 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 terms of Model-View-Controller pattern, entities are a kind of models designed for


storing data. For additional examples of entities and other types of models, please refer
to Chapter 4.

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 }

Let’s review the code above:


In line 2, we declared the Application\Entity namespace in which entity classes for the
Application module live.
In line 4, you may notice that we use the Doctrine\ORM\Mapping class and its short ORM alias for
Doctrine annotations ⁸.
In lines 6-9, you can see a Dockblock annotation for the Post class. Each annotation tag begins
with the @ character, has the name and (optional) parameters enclosed into the round braces.
Doctrine-provided tags used in annotations may be of two types: class-level and property-level.
In the code above, we use the following class-level tags (describing the whole entity class):

• @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;

Entity’s properties are described with the following property-level tags:

• @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⁹.

12.6 Creating Entities


For the Application module, entities are (by convention) stored inside the Application/Entity
directory under the module’s source directory. Entity classes live inside the Application\Entity
namespace.

12.6.1 Adding Post Entity


We start with creating the Post entity. Create the Post.php file under module’s Application/Entity
directory. (If you haven’t created the Application/Entity directory yet, its the right time to do
that.) Put the following code into the file:

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 }

In the code above, we have the following things:

• 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).

Table 12.4. Getter and setter methods of the Post entity

Property Mapped on Column Description


$id id Unique ID of this post.
$title title Title of this post.
$content content Content of this post.
$status status Status (draft/published) of this post.
$dateCreated date_created Date when this post was 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).

Table 12.5. Getter and setter methods of the Post entity

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.

12.6.2 Adding the Comment and Tag Entities


By analogy with the Post entity, we next create the Comment and the Tag entity classes in the
Application/Entity directory. To do that, first, create Comment.php file and put the following
Database Management with Doctrine ORM 372

code inside of it:

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.

12.6.3 Specifying Relations between Entities


Now its time to use annotation tags allowing define relations between entities. If you remember,
we have two relations between our entities:

• the Post and Comment entities are related as “one-to-many”;


• and the Post and Tag entities are related as “many-to-many”.

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

36 public function addTag($tag)


37 {
38 $this->tags[] = $tag;
39 }
40
41 // Removes association between this post and the given tag.
42 public function removeTag($tag)
43 {
44 $this->tags->removeElement($tag);
45 }
46 }

In the code above, we do the following:

• add $tags private property


• mark the $tags property with Docblock annotations with @ORM\ManyToMany and @ORM\JoinTable
annotation tags
• initialize the property in constructor;
• add three methods getTags(), addTag() and removeTag() allowing to get/modify the
property’s value.

Finally, modify the Tag entity as follows:

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.

12.6.4 Specifying Entity Locations


To let Doctrine know where to find entities for your Application module (or for another module
you have), you add the following lines into your module.config.php file:

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.

12.7 About Entity Manager


Entity manager is the primary access point to ORM functionality provided by Doctrine.

EntityManager is a Doctrine class that lives in Doctrine\ORM namespace and used to


retrieve entities from their repositories using search criteria and save entities back to
repositories.

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):

// Get Doctrine entity manager


$entityManager = $this->getServiceLocator()
->get('doctrine.entitymanager.orm_default');

The most used methods provided by the EntityManager class are listed in table 12.6 below.

Table 12.6. Methods of the EntityManager

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.

Let’s review the methods from table 12.6.


To add a newly created entity to repository (to make the entity “managed”), you use entity
manager’s persist() method. To remove an entity from repository, you use entity manager’s
remove() method.

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

// Create new Post entity.


$post = new Post();
$post->setTitle('Top 10+ Books about Zend Framework 2');
$post->setContent('Post body goes here');
$post->setStatus(Post::STATUS_PUBLISHED);
$currentDate = date('Y-m-d H:i:s');
$post->setDateCreated($currentDate);

// Add the entity to entity manager.


$entityManager->persist($post);

// Apply changes to database.


$entityManager->flush();

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')

12.7.1 Entity Repositories


Conceptually, each entity class has its own repository. The repository provides methods for
retrieving entities from database. The repository can be considered as a collection of all available
entities of certain class. For example, there are repositories for our Post, Comment, and Tag entities.
To load data from the database, you retrieve an entity from its repository. 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.

The Doctrine\ORM\EntityRepository class implements the default repository. If


needed, you can, by extending the EntityRepository, create your own repository for
certain entity class. We will show how to do that later.

The most used methods provided by the EntityRepository class are listed in table 12.6.

Table 12.6. Methods of the EntityRepository

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

Table 12.6. Methods of the EntityRepository

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:

// Get Doctrine entity manager


$posts = $entityManager->getRepository('\Application\Entity\Post')->findAll();

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.

// Get Doctrine entity manager


$post = $entityManager->getRepository('\Application\Entity\Post')->find(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:

// Get Doctrine entity manager


$posts = $entityManager->getRepository('\Application\Entity\Post')->findBy(
array('status'=>Post::STATUS_PUBLISHED),
array('$dateCreated'=>'DESC'), 50);

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.

12.8 Adding Blog Home Page


To show how to use EntityManager class, we will create the main page for the Blog web
application. This page will display the list of posts sorted by date in descending order.
To do that, add the indexAction() method to the IndexController controller class, as follows:
Database Management with Doctrine ORM 384

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).

Figure 12.3. List of posts

12.9 Adding New Post


In this section, we will create the Add New Post web page that will allow to add a new post to
blog. For this, we will need four things:

• 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.

12.9.1 Adding PostForm


First, we add the PostForm form that will allow to enter data of a single post: its title, content,
comma-separated list of tags associated with the post, and status (Published or Draft). To do that,
create the PostForm.php file in Application/Form directory under module’s source directory. Put
the following content into the file:

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

35 // Add "title" field


36 $this->add(array(
37 'type' => 'text',
38 'name' => 'title',
39 'attributes' => array(
40 'id' => 'title'
41 ),
42 'options' => array(
43 'label' => 'Title',
44 ),
45 ));
46
47 // Add "content" field
48 $this->add(array(
49 'type' => 'textarea',
50 'name' => 'content',
51 'attributes' => array(
52 'id' => 'content'
53 ),
54 'options' => array(
55 'label' => 'Content',
56 ),
57 ));
58
59 // Add "tags" field
60 $this->add(array(
61 'type' => 'text',
62 'name' => 'tags',
63 'attributes' => array(
64 'id' => 'tags'
65 ),
66 'options' => array(
67 'label' => 'Tags',
68 ),
69 ));
70
71 // Add "status" field
72 $this->add(array(
73 'type' => 'select',
74 'name' => 'status',
75 'attributes' => array(
76 'id' => 'status'
77 ),
78 'options' => array(
79 'label' => 'Status',
80 'value_options' => array(
Database Management with Doctrine ORM 388

81 '2' => 'Published',


82 '1' => 'Draft',
83 )
84 ),
85 ));
86
87 // Add the submit button
88 $this->add(array(
89 'type' => 'submit',
90 'name' => 'submit',
91 'attributes' => array(
92 'value' => 'Create',
93 'id' => 'submitbutton',
94 ),
95 ));
96 }
97
98 /**
99 * This method creates input filter (used for form filtering/validation).
100 */
101 private function addInputFilter()
102 {
103
104 $inputFilter = new InputFilter();
105 $this->setInputFilter($inputFilter);
106
107 $inputFilter->add(array(
108 'name' => 'title',
109 'required' => true,
110 'filters' => array(
111 array('name' => 'StringTrim'),
112 array('name' => 'StripTags'),
113 array('name' => 'StripNewLines'),
114 ),
115 'validators' => array(
116 array(
117 'name' => 'StringLength',
118 'options' => array(
119 'min' => 1,
120 'max' => 1024
121 ),
122 ),
123 ),
124 )
125 );
126
Database Management with Doctrine ORM 389

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

12.9.2 Adding PostManager Service


According to Domain Driven Design pattern, we put business logic into service models. In our
Blog sample, we will create and register the PostManager service. This service will have the
addNewPost() public method that will contain business logic of adding Post entity to database
and associating it with one or several Tag entities.

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

12.9.3 Creating Controller Action and View Template


For post management (e.g. adding, editing, viewing and removing posts), we will create
the PostController controller class. We create the addAction() action method inside the
PostController controller class that will allow to add a new post to blog (see code below):

1 class PostController extends AbstractActionController


2 {
3 /**
4 * This action displays the "New Post" page. The page contains
5 * a form allowing to enter post title, content and tags. When
6 * the user clicks the Submit button, a new Post entity will
7 * be created.
8 */
9 public function addAction()
10 {
11 // Create the form.
12 $form = new PostForm();
13
14 $postManager = $this->getServiceLocator()->get('post_manager');
15
16 // Check whether this post is a POST request.
17 if ($this->getRequest()->isPost()) {
18
19 // Get POST data.
20 $data = $this->params()->fromPost();
21
22 // Fill form with data.
23 $form->setData($data);
24 if ($form->isValid()) {
25
26 // Get validated form data.
27 $data = $form->getData();
28
29 // Use post manager service to add new post to database. \
30
31 $postManager->addNewPost($data['title'], $data['content'],
32 $data['tags'], $data['status']);
33
34 // Redirect the user to "index" page.
35 return $this->redirect()->toRoute('application/default',
36 array('controller'=>'index', 'action'=>'index'));
37 }
38 }
39
40 // Render the view template.
Database Management with Doctrine ORM 394

41 return new ViewModel(array(


42 'form' => $form
43 ));
44 }
45 }

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

32 <?php echo $this->form()->openTag($form); ?>


33
34 <div class="form-group">
35 <?php echo $this->formLabel($form->get('title')); ?>
36 <?php echo $this->formElement($form->get('title')); ?>
37 <?php echo $this->formElementErrors($form->get('title')); ?> \
38
39 </div>
40
41 <div class="form-group">
42 <?php echo $this->formLabel($form->get('content')); ?>
43 <?php echo $this->formElement($form->get('content')); ?>
44 <?php echo $this->formElementErrors($form->get('content')); ?> \
45
46 </div>
47
48 <div class="form-group">
49 <?php echo $this->formLabel($form->get('tags')); ?>
50 <?php echo $this->formElement($form->get('tags')); ?>
51 <?php echo $this->formElementErrors($form->get('tags')); ?> \
52
53 <p class="help-block">Separate tags with comma.</p>
54 </div>
55
56 <div class="form-group">
57 <?php echo $this->formLabel($form->get('status')); ?>
58 <?php echo $this->formElement($form->get('status')); ?>
59 <?php echo $this->formElementErrors($form->get('status')); ?> \
60
61 </div>
62
63 <?php echo $this->formElement($form->get('submit')); ?>
64
65 <?php echo $this->form()->closeTag(); ?>
66 </div>
67 </div>

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

Figure 12.5. Add New Post page

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.

12.10 Editing Existing Post


In this section, we will implement the Edit Post page which contains the form allowing to
edit the data of existing post, send new data to server and apply changes to database. Site
visitor will be able to see the page by entering the following URL in browser’s navigation bar:
http://localhost/application/post/edit/<id>, where <id> is the unique identifier of the post.
To implement this page we need the following things:
Database Management with Doctrine ORM 397

• 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.

12.10.1 Modifying PostManager


First, we add the updatePost() and convertTagsToString() methods to the PostManager service
model as follows:

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.

12.10.2 Adding Controller Action and View Template


Next, add the editAction to PostController controller class as follows:

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):

Figure 12.5. Edit Post page

Clicking the Save button results in saving the changes to database.

12.11 Deleting Post


In this section, we will implement the deleteAction() action of the PostController. This
action will allow to delete certain post given its ID. The action will take ID as a GET variable,
look if a post with such ID exists, and if exists, deletes the post, its related comments and tag
Database Management with Doctrine ORM 403

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.

12.11.1 Modifying PostManager


First, we’ll add the removePost() method to the PostManager service. This method will remove
the post and its associated comments. It will also remove associations between post and tags.

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 // Removes association between this post and the given tag.


2 public function removeTagAssociation($tag)
3 {
4 $this->tags->removeElement($tag);
5 }

12.11.2 Adding Controller Action


The PostController::deleteAction() method retrieves the ID of the post to be removed,
checks whether this is a valid post ID. If so, it calls the PostManager::removePost() method
to remove the post and apply changes to database. Finally, it redirects the site visitor to the
Admin page.

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

12.12 Implementing Post Preview


In this section, we will create controller’s action and its corresponding view template that would
allow site visitors to preview certain post by entering the following URL in browser’s navigation
bar: http://localhost/application/post/view/<id>, where <id> is the unique identifier of the post.
The page will also allow to add comments to the post using the form located at the bottom of
the page. For example of what we are trying to achive, please look at the figure 12.10 below:
Database Management with Doctrine ORM 406

Figure 12.10. View Post page

So, for this we need four things:

• 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

12.12.1 Adding CommentForm


First, we implement the CommentForm form that will allow to add a comment to a post. Create
the CommentForm.php file in Application/Form directory under module’s source directory. Put
the following content into the file:

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

41 // Add "comment" field


42 $this->add(array(
43 'type' => 'textarea',
44 'name' => 'comment',
45 'attributes' => array(
46 'id' => 'comment'
47 ),
48 'options' => array(
49 'label' => 'Comment',
50 ),
51 ));
52
53 // Add the submit button
54 $this->add(array(
55 'type' => 'submit',
56 'name' => 'submit',
57 'attributes' => array(
58 'value' => 'Save',
59 'id' => 'submitbutton',
60 ),
61 ));
62 }
63
64 // This method creates input filter (used for form filtering/validation).
65 private function addInputFilter()
66 {
67 $inputFilter = new InputFilter();
68 $this->setInputFilter($inputFilter);
69
70 $inputFilter->add(array(
71 'name' => 'author',
72 'required' => true,
73 'filters' => array(
74 array('name' => 'StringTrim'),
75 ),
76 'validators' => array(
77 array(
78 'name' => 'StringLength',
79 'options' => array(
80 'min' => 1,
81 'max' => 128
82 ),
83 ),
84 ),
85 )
86 );
Database Management with Doctrine ORM 409

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.

12.12.2 Modifying PostManager


Here, we add two methods: * the getCommentCountStr() method will format the comment
count string for the given post (e.g., “No comments”, “1 comment”, “2 comments”, etc.) * and
the addCommentToPost() method will be used for adding a new comment to post.

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

12 public function getCommentCountStr($post)


13 {
14 $commentCount = count($post->getComments());
15 if ($commentCount == 0)
16 return 'No comments';
17 else if ($commentCount == 1)
18 return '1 comment';
19 else
20 return $commentCount . ' comments';
21 }
22
23
24 // This method adds a new comment to post.
25 public function addCommentToPost($post, $author, $content)
26 {
27 // Get Doctrine entity manager.
28 $entityManager = $this->getServiceLocator()->get('doctrine.entitymana\
29 ger.orm_default');
30
31 // Create new Comment entity.
32 $comment = new Comment();
33 $comment->setPost($post);
34 $comment->setAuthor($author);
35 $comment->setContent($content);
36 $currentDate = date('Y-m-d H:i:s');
37 $comment->setDateCreated($currentDate);
38
39 // Add the entity to entity manager.
40 $entityManager->persist($comment);
41
42 // Apply changes.
43 $entityManager->flush();
44 }
45 }

12.12.3 Adding Controller Action and View Template


Now, add the PostController::viewAction() method and put the following code there:
Database Management with Doctrine ORM 411

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

47 // Get POST data.


48 $data = $this->params()->fromPost();
49
50 // Fill form with data.
51 $form->setData($data);
52 if($form->isValid()) {
53
54 // Get validated form data.
55 $data = $form->getData();
56
57 // Use post manager service to add new comment to post.
58 $postManager->addCommentToPost(
59 $post, $data['author'], $data['comment']);
60
61 // Redirect the user again to "view" page.
62 return $this->redirect()->toRoute('application/default',
63 array('controller'=>'post', 'action'=>'view', 'id'=>$po\
64 stId));
65 }
66 }
67
68 // Render the view template.
69 return new ViewModel(array(
70 'post' => $post,
71 'commentCount' => $commentCount,
72 'form' => $form,
73 'postManager' => $postManager
74 ));
75 }
76 }

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>

12.13 Implementing Admin Page


Admin page of the Blog sample web application contains the list of all blog posts (either published
or drafts), and allows to view, edit and delete posts.
To implement this page, add the adminAction() action method to the PostController class, as
follows:

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

12 // Get Doctrine entity manager


13 $entityManager = $this->getServiceLocator()
14 ->get('doctrine.entitymanager.orm_default');
15
16 $postManager = $this->getServiceLocator()->get('post_manager');
17
18 // Get posts
19 $posts = $entityManager->getRepository('\Application\Entity\Post')
20 ->findBy(array(), array('dateCreated'=>'DESC'));
21
22 // Render the view template
23 return new ViewModel(array(
24 'posts' => $posts,
25 'postManager' => $postManager
26 ));
27 }
28 }

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

26 <a href="<?php echo $this->url('application/default',


27 array('controller'=>'post', 'action'=>'view',
28 'id'=>$post->getId())); ?>">
29 <?php echo $this->escapeHtml($post->getTitle()); ?>
30 </a>
31 </td>
32 <td><?php echo $post->getDateCreated(); ?></td>
33 <td><?php echo $postManager->getPostStatusAsString($post); ?></td>
34 <td>
35 <a class="btn btn-info" href="<?php echo $this->url(
36 'application/default',
37 array('controller'=>'post', 'action'=>'edit',
38 'id'=>$post->getId())); ?>">
39 <span class="glyphicon glyphicon-pencil" ></span> Edit
40 </a>
41 <a class="btn btn-danger"
42 href="<?php echo $this->url('application/default',
43 array('controller'=>'post', 'action'=>'delete',
44 'id'=>$post->getId())); ?>">
45 <span class="glyphicon glyphicon-remove"></span> Delete
46 </a>
47 </td>
48 </tr>
49
50 <?php endforeach; ?>
51
52 </table>

Now, if you open the URL http://localhost/application/post/admin in web browser’s navigation


bar, you should be able to see the page like in figure 12.9 below:
Database Management with Doctrine ORM 417

Figure 12.2. Blog Admin page

12.14 Implementing Tag Cloud


The last feature we implement in the Blog sample will be the tag cloud. The tag cloud appears on
the Home page. The tag cloud contains most popular tags, and tag’s font size varies depending
on popularity of the tag: most popular tags appear larger than less popular ones. Clicking the tag
in the tag cloud results in filtering posts by this tag.
For example of what we are trying to achieve, please look at figure below:

Figure 12.10. Tag cloud

For this feature, we need the following things:

• 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.

12.14.1 Adding Custom Post Repository


Earlier we mentioned that by default Doctrine uses the Doctrine\ORM\EntityRepository as the
default repository class. Custom repository is a class extended from EntityRepository class. It
is typically used when you need to encapsulate complex DQL queries and search logic in a single
place in your code.

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.

12.14.2 Calculating Tag Cloud


Business logic for the tag cloud feature will be stored inside of the PostManager::getTagCloud()
method, as follows:

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 }

12.14.3 Modifying Controller Action


Here we will modify the IndexController::indexAction() action method to implement tag
filter.

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.

12.14.4 Rendering Tag Cloud


Finally, modify the index.phtml file to make it finally look like follows:

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

65 <?php endforeach; ?>


66 </div>
67 </div>
68 </div>
69 </div>

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.

Installing Apache, PHP and MySQL in Linux


In general, it is recommended that you use a popular and well supported Linux distribution,
either 32-bit (x86) or 64-bit (amd64). A 64-bit version can give a great performance, but deliver
more problems (like driver compatibility issues). 32-bit systems are with us for a longer time and
have less problems, which is important for novice users.
There are two big families of Linux distributions: Debian and Red Hat. Debian is a free and
open-source project, which has several branches, the most popular of which is Linux Ubuntu.
Red Hat is a commercially distributed operating system, which has “free” branches named Linux
CentOS and Linux Fedora.
Red Hat Linux is being developed by Red Hat Inc. Red Hat Linux (or its “free” modification
CentOS) is known as a “corporate” operating system. Its main advantage is “stability” (low rate
of system crashes). However, this stability is achieved through carefully choosing the software
which is installed out of the box. When you install such an operating system for the purpose
of PHP development, this “stability” may become a problem, because you have access to some
old (but “stable”) version of PHP and other software. They do not include a new “bleeding-edge”
¹²http://using-zend-framework-2-book.com/tutorials/
Appendix A. Configuring Web Development Environment 426

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:

Table A.1. Available PHP versions in different Linux distributions

Linux Distribution PHP Version


Linux Ubuntu 14.04 Trusty Tahr 5.5.4
Linux Ubuntu 13.04 Raring Ringtail 5.4.9
Linux Ubuntu 12.10 Quantal Quetzal 5.4.6
Linux Ubuntu 12.04 Precise Pangolin LTS 5.3.10
Linux Debian 7.0 Wheezy 5.4.4
Linux Debian 6.0 Squeeze 5.3.3
Red Hat Enterprise Linux 6.4 5.3.3
Linux Cent OS 6.4 5.4.15
Linux Fedora 19 5.5.0
Linux Fedora 18 5.4.9

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.

Installing Apache and PHP


In modern Linux distributions, you can easily download and install software from a centralized
repository. The repository contains so called packages. A package has a name (for example, php5,
apache2), and a version.
In general you can install a package with a single command. However the command (and a
package name) may differ based on linux distribution you use. For example, to download and
Appendix A. Configuring Web Development Environment 427

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.

Next, from a command shell, run the following commands:


sudo apt-get install apache2
sudo apt-get install php5
sudo apt-get install libapache2-mod-php5
The commands above download from repository and install the latest available versions of
Apache HTTP Server, PHP engine and PHP extension module for Apache.

The commands above may ask you for confirmation when installing a package. It is
recommended to answer Yes (press “y” and then press Enter).

Fedora, CentOS or Red Hat Linux


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 command:
sudo yum update
The command above runs the YUM tool and installs the newest system package updates.
Next, from a command shell, run the following commands:
sudo yum install httpd
sudo yum install php
The commands above download from repository and install the latest available versions of
Apache HTTP Server and PHP engine.
Next, run the following commands to add Apache HTTP Server to system autorun and start it:
Appendix A. Configuring Web Development Environment 428

sudo chkconfig --level 235 httpd on


sudo service httpd start

Checking Web Server Installation


After you set up your Apache HTTP web server, check that it is installed correctly and that
the server sees the PHP engine. To do that, create phpinfo.php file in Apache document root
directory.
The document root is a directory where you can (by default) store the web files. Typically, the
Apache document root directory is /var/www (in Debian or Linux Ubuntu) or /var/www/html
(in Fedora, CentOS or Red Hat Linux).

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.

In the phpinfo.php file, enter the PHP method phpinfo() as follows:

<?php
phpinfo();
?>

Open the file in your web browser. The standard PHP information page should display (see figure
A.1 for example).

Editing PHP Configuration


To configure PHP for your development environment, you need to edit the PHP config file
(php.ini) and adjust some parameters.

In different distributions of Linux, PHP configuration file can be located in different


paths. To edit the PHP config file in Debian or Linux Ubuntu, type the following:
sudo mcedit /etc/php5/apache2/php.ini

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

Figure A.1. PHP Information

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

To conveniently search within the file, press F7 in Midnight Commander’s editor


window and enter the search string (the name of the parameter to search for).

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>

Set max_execution_time, upload_max_filesize and post_max_size parameters to allow large


file uploads through POST. For example, setting upload_max_filesize with 128M allows to
upload files up to 128 megabytes in size. Setting max_execution_time with zero allows to execute
the PHP script indefinitely long.
max_execution_time = 0
Appendix A. Configuring Web Development Environment 430

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.

Restarting Apache Web Server


After editing configuration files, you usually have to restart Apache HTTP Server to apply your
changes. You do this with the following command (in Debian or Linux Ubuntu):
sudo service apache2 restart

or the following (in Fedora, CentOS or Red Hat):


sudo service httpd restart

As a result, you should see output like below:

* Restarting web server apache2


apache2: Could not reliably determine the server's fully qualified
domain name, using 127.0.0.1 for ServerName
... waiting apache2: Could not reliably determine the server's fully
qualified domain name, using 127.0.0.1 for ServerName [OK]

Enabling Apache’s mod_rewrite module


Zend Framework 2 requires that you have Apache’s mod_rewrite module enabled. The mod_-
rewrite module is used to rewrite requested URLs based on some rules, redirecting site users to
another URL.
In Debian or Ubuntu Linux
To enable Apache mod_rewrite module, type the following commands:
cd /etc/apache2/mods-enabled

sudo ln -s ../mods-available/rewrite.load ./rewrite.load

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.

A symbolic link in Linux is an analog of a shortcut in Windows.

In Fedora, CentOS or Red Hat Linux


In these Linux distributions, mod_rewrite is enabled by default, so you don’t need to do anything.
Appendix A. Configuring Web Development Environment 431

Creating Apache Virtual Host


Zend Framework 2 requires that you create a virtual host for your web site. A virtual host term
means that you can run several web-sites on the same machine.
The virtual sites are differentiated by domain name (like site.mydomain.com and site2.mydomain.com).
Each virtual host has its own document root directory, allowing you to place your web files
anywhere on the system (not only to /var/www or /var/www/html directory).

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.

In Debian or Ubuntu Linux


You have an example default virtual host at /etc/apache2/sites-enabled/000-default (see below).

<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

# Possible values include: debug, info, notice, warn, error, crit,


# alert, emerg.
LogLevel warn

</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

sudo cp default vhost2

cd ../sites-enabled

sudo ln -s ../sites-available/vhost2 ./010-vhost2

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.

In Fedora, CentOS or Red Hat Linux


There is an example virtual host in /etc/httpd/conf/httpd.conf file. Scroll down to the very bottom
of the document to the section called Virtual Hosts. You can edit this section as you need and
restart Apache to apply your changes.

Installing XDebug PHP extension


To be able to debug your web sites, it is recommended that you install the XDebug extension. The
XDebug extension allows to look inside a running program, see the variables passed from the
client, walk the call stack and profile your PHP code. XDebug also provides the code coverage
analysis capabilities, which are useful when you write unit tests for your code.
In Debian or Ubuntu Linux
To install XDebug, simply type the following command:
sudo apt-get install php5-xdebug

Then edit the 20-xdebug.ini file by typing the following:


sudo mcedit /etc/php5/apache2/conf.d/20-xdebug.ini

Add the following lines:

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

After install, it is required to create the file xdebug.ini in /etc/php.d directory:


mcedit /etc/php.d/xdebug.ini

In that file, add the following line:


zend_extension = /usr/lib/php/modules/xdebug.so

Edit your php.ini file


mcedit /etc/php.ini

and add the following section:

Figure A.2. XDebug Information

[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

Installing APC PHP Extension


The Alternative PHP Cache (APC) is an opcode cache for PHP. It is designed to provide your
PHP web sites with the ability of caching and optimizing the PHP intermediate code generated
by the PHP interpreter. Although this extension is optional, it is recommended that you install
it to speed up the operation of your ZF2-based web sites.
To install APC extension in Linux Ubuntu, type the following from your command shell:
sudo apt-get install php-apc

To do that in Red Hat, CentOS or Fedora, type the following line:


sudo yum install php-apc

Finally, restart Apache web server to apply the changes.

Installing MySQL Database Server


MySQL¹³ is a free relational database management system being developed and supported by
Oracle. MySQL is the most popular database system used with PHP.
Debian or Linux Ubuntu
In order to install MySQL database, type the following:
sudo apt-get install mysql-server

sudo apt-get install mysql-client

sudo apt-get install php5-mysql

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

sudo yum install mysql

sudo yum install php-mysql

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

sudo service mysqld start

¹³http://www.mysql.com/
Appendix A. Configuring Web Development Environment 435

Configuring the MySQL Database Server


During the installation of the MySQL server, a root user is created. By default the root user has
no password, so you have to set it manually. You will need that password for creating other
MySQL database users.
To connect to the MySQL server enter the following command:
mysql -u root -p

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>');

If the command is executed successfully, the following message is displayed:


Query OK, 0 rows affected (0.00 sec)

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.

Installing Apache, PHP and MySQL in Windows


Download the latest stable version (current stable version 2.2 recommended) of Apache HTTP
Server from Apache Download Page¹⁴. On that page, look for the Apache HTTP Server 2.2.24
(httpd) section (see the figure A.3), under that section click Other files link.
¹⁴http://httpd.apache.org/download.cgi
Appendix A. Configuring Web Development Environment 436

Figure A.3. Apache HTTP Server download page

We need to download the binary installer for Windows, so on the page that appears, click binaries
link (see the figure A.4).

Figure A.4. Binaries folder

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

Figure A.5. Apache HTTP Server MSI installer

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).

Figure A.6. Apache icon in system tray

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:

# Load PHP module


LoadModule php5_module "C:/Program Files (x86)/php/php5apache2_2.dll"
AddHandler application/x-httpd-php .php

# Specify path to php.ini


PHPIniDir "C:/Program Files (x86)/php"

After editing the config file, restart Apache. You can do this by clicking the Apache tray icon
and choosing Restart from the context menu.

In Windows Vista/Windows 7, httpd.conf file permissions may prevent you from


saving the changes to file. To be able to save your changes to httpd.conf, you should
run your editor as administrator. For example, if you use notepad editor, you should
run it as administrator by right-clicking its icon and choosing Run as administrator
from context menu.

¹⁵http://www.php.net/downloads.php
Appendix A. Configuring Web Development Environment 438

Checking Web Server Installation


After you set up your web server, check that it is installed correctly and that your Apache server
recognizes the PHP engine.
To check that Apache and PHP are installed correctly, create phpinfo.php file in Apache
document root directory.

Typically, the Apache document root directory is C:Program Files (x86)Apache Software
FoundationApache2.2\htdocs.

In Windows Vista/Windows 7, htdocs directory permissions may prevent you from


creating files in it. A workaround for this is to modify directory permissions. Right-
click the htdocs directory name, choose Properties from context menu. Properties dialog
appears (see the figure A.7). Click Security tab, then in the list select Users and click
Edit button. In the appered dialog, set check on Full control checkbox and click OK
button.

Figure A.7. Directory Permissions Dialog

In the phpinfo.php file, enter the PHP method phpinfo() as follows:


Appendix A. Configuring Web Development Environment 439

<?php
phpinfo();
?>

Open the file in your browser. The standard PHP information page should display (figure A.8).

Figure A.8. PHP Information

Enabling Apache’s mod_rewrite module


Zend Framework 2 requires that you have Apache’s mod_rewrite module enabled. To enable
mod_rewrite, open your the php.ini config file, then find the following line:

#LoadModule rewrite_module modules/mod_rewrite.so

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

LoadModule rewrite_module modules/mod_rewrite.so

Finally, restart Apache web server to apply your changes.

Creating Apache Virtual Host


A virtual host term means that you can run several web-sites on the same machine. The virtual
sites are differentiated by domain name (like site.mydomain.com and site2.mydomain.com)
Virtual hosts are disabled by default. To enable this feature, open C:Program FilesApache
Software FoundationApache2.2\conf\httpd.conf config file in a text editor. Scroll down the file,
and locate the following section:

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

Uncomment the second line, so it looks like this:

#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.

Installing XDebug PHP extension


To be able to debug your web sites in NetBeans IDE, it is recommended that you install the
XDebug extension of your PHP engine. Download an appropriate DLL from this site¹⁶. For
example, if you have installed PHP 5.5, you should download php_xdebug-2.2.3-5.5-vc11.dll
Then edit your php.ini file and add the following section:

zend_extension="C:\Program Files (x86)\PHP\ext\php_xdebug-2.2.3-5.5-vc11.dll"

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

Figure A.9. XDebug Information

Installing MySQL Database Server


Download the latest version of MySQL Community Server from this page¹⁷. On the download
page, choose Windows (x86, 32-bit), MSI Installer.
Run the MySQL installer and follow the instructions of the wizard.

Configuring the MySQL Database Server


Now we want to create a database schema and a database user. We will use MySQL Command
Line Client. You can launch the client from Start Menu | All Programs | MySQL | MySQL Server
5.x | MySQL 5.x Command Line Client. The MySQL Command Line Client console appears (see
the figure A.10):
¹⁷http://www.mysql.com/downloads/
Appendix A. Configuring Web Development Environment 443

Figure A.10. MySQL Command-Line Client

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

Installing NetBeans IDE in Linux


You can install NetBeans IDE using two methods: either from repository, as you did with Apache,
PHP and MySQL, or by downloading the installer from NetBeans web site and running it. The
first method is simpler, but it may give you an outdated version of NetBeans, depending on your
Linux distribution. The second method is a little more complicated, but it gives you the most
up-to-date version of the IDE. The latter one is recommended.

Method 1. Installing from Repository


To install NetBeans IDE in Debian or Linux Ubuntu, type the following command from your
command shell:
sudo apt-get install netbeans

or the following command to install it in Fedora, CentOS or Red Hat Linux:


sudo yum install netbeans

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

The NetBeans IDE window is shown in figure A.11.

Figure A.11. NetBeans IDE


Appendix A. Configuring Web Development Environment 445

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.

Method 2. Downloading from Web Site


Because NetBeans is written in Java, we have to first install Java virtual machine (JVM). JVM
can be installed from an external Oracle-provided repository. To let your system know about the
repository, run the following commands from your command shell:
sudo add-apt-repository ppa:webupd8team/java

sudo apt-get update

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

If everything is OK, you should see something like below:


java version "1.7.0_25"

Java(TM) SE Runtime Environment (build 1.7.0_25-b15)

Java HotSpot(TM) Client VM (build 23.25-b01, mixed mode)

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

Figure A.12. NetBeans Plugins

Figure A.13. NetBeans Plugins


Appendix A. Configuring Web Development Environment 447

Figure A.14. Download NetBeans PHP bundle

When download has been finished, go to your Downloads directory and run the installer by
typing the commands below:
cd ∼/Downloads

sudo chmod +x netbeans-7.3.1-php-linux.sh

./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

Installing NetBeans IDE in Windows


Installing NetBeans in Windows is strightforward. You just need to download the installer
from NetBeans site²⁰ and run it. You may encounter several bundles of NetBeans available for
download, and you should download the bundle that is intended for PHP development (see the
figure A.14 for example).

Figure A.14. NetBeans PHP Download Page

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

These installation instructions do not work for me. What do I do?


Please contact the author using the dedicated Google Group²² forum. The author
appreciates your feedback and will be happy to impove the installation instructions.

²²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

Figure B.1. Properties | Sources

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).

Figure B.2. Browse Folders Dialog

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

Figure B.3. Properties | Run Configuration

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.

Finally, click the OK button to close the Properties dialog.

Running the Web Site


Running the web site means opening it in your default web browser. To launch the site, press
the Run button on the Run Toolbar (figure B.4). Alternatively, you can press F6 button on your
keyboard.
Appendix B. Introduction to PHP Development in NetBeans IDE 453

Figure B.4. Run Toolbar

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.

Site Debugging in NetBeans


The “conventional” debugging technique in PHP is putting the var_dump() function in the code
block that you want to examine:
var_dump($var);

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

Figure B.5. Debugging Session

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):

Figure B.6. Debug Toolbar

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):

Figure B.7. Setting a breakpoint


Appendix B. Introduction to PHP Development in NetBeans IDE 456

Be careful not to set a breakpoint on an empty line or on a comment line. Such a


breakpoint will be ignored by XDebug, and it will be marked by the “broken” square
(see figure B.8 for example):

Figure B.8. Inactive breakpoint

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.

Figure B.9. Breakpoints window

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

Figure B.10. Variables window

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

Figure B.11. Call Stack window

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

Figure B.12. PHP Debugging Options

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):

Figure B.13. Webgrind Output Page

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.

Figure B.14. Webgrind Select

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.

Clicking a column header allows to sort data in ascending or descending order.


You can click the triangle icon next to a function name to expand a list of function invocations.
This list allows you to see who called this function and what the amount of time spent is, and
contains the following columns:

• 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:

• Blue denotes PHP internal (built-in) functions;


• Lavender is time taken to include (or require) PHP files;
• Green shows the contribution of your own class methods;
• Orange denotes time taken on traditional “procedural” functions (functions that are not
part of a PHP classes).

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.

Overview of Bootstrap Files


The source code of Bootstrap framework’s components is spread across many CSS files. It is
known that downloading multiple small files is typically slower than downloading a single
large file. For this reason, Bootstrap CSS stylesheets are “concatenated” with the special tool
and distributed in a form of a single file named bootstrap.css.
However, this bootstrap.css file has a disadvantage: it contains many characters (white space
characters, new line characters, comments, etc.) unneeded for code execution, wasting network
bandwidth when downloading the file, thus increasing page load time. To fix this problem, the
minification is used.
The minification is the process of removing all unnecessary characters from the source code
without changing its functionality. The minified Bootstrap file is called bootstrap.min.css.

It is generally recommended to use the minified file, especially in production environ-


ment, because it reduces the page load time. However, if you plan to dig into Bootstrap
code to understand how it works, you better use the usual (non-minified) file, or even
download the original source files (not concatenated ones).

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 fonts directory contains several files (e.g. glyphicons-halflings-regular.svg) needed by


Bootstrap for rendering icons. These icons (also called Glyphicons) can be used to enhance the
appearance of the buttons and dropdown menus.

Figure C.1. Structure of the APP_DIR/public directory

The APP_DIR/public/js subdirectory contains JavaScript extensions of the Bootstrap framework.


They are implemented as jQuery plugins:

• 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).

Figure C.2. A typical site layout

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).

Figure C.3. Bootstrap grid system

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).

Why does Bootstrap’s grid consist of only 12 columns?


Probably because 12 columns are enough for most web sites. If you have more fine-
grained grid with lots of columns, it would be more difficult to compute column spans
without the calculator. Fortunately, Bootstrap allows for customizing the count of
columns per row, so you can have as many columns as you wish.

²⁷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

Defining the Grid


To arrange elements in a grid on your web page, you start from defining the container by adding
a <div> element having the .container CSS class. To add a new row to the grid, use a <div>
element having the .row CSS class, as shown in the example below:

<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:

Figure C.4. Column offsets

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.

Figure C.5. Nested grid

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.

“Mobile First” Concept


Twitter Bootstrap v3.0 is designed to support different devices varying from wide displays
to tablets and mobile phones. By this reason, the layout grid is adapted to different screen
resolutions.

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.

Figure C.6. Grid adapts to screen size

Table C.1 provides the summary of available grid classes and their breakdown page width.

Table C.1. CSS classes for defining layout grid

Class name Breakdown width


.col-xs-* <768px
.col-sm-* >=768px
.col-md-* >=992px
.col-lg-* >=1200px

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.

Bootstrap’s Interface Components


In this section, we will give a summary on useful interface components provided by Twitter
Bootstrap. We plan to use some of these components in our further examples, and this section
should give an idea of how you can use these widgets in your own web sites.
Appendix C. Introduction to Twitter Bootstrap 471

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):

Figure C.7. Navbar

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:

1 <nav class="navbar navbar-default" role="navigation">


2 <div class="navbar-header">
3 <a class="navbar-brand" href="#">Hello World</a>
4 </div>
5 <ul class="nav navbar-nav">
6 <li><a href="#">Home</a></li>
7 <li><a href="#">Download</a></li>
8 <li><a href="#">Support</a></li>
9 <li><a href="#">About</a></li>
10 </ul>
11 </nav>

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

Figure C.8. Navbar with dropdown menu

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.

Figure C.9. Collapsed navbar

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

1 <nav class="navbar navbar-default" role="navigation">


2 <!-- Brand and toggle get grouped for better mobile display -->
3 <div class="navbar-header">
4 <button type="button" class="navbar-toggle" data-toggle="collapse"
5 data-target=".navbar-ex1-collapse">
6 <span class="sr-only">Toggle navigation</span>
7 <span class="icon-bar"></span>
8 <span class="icon-bar"></span>
9 <span class="icon-bar"></span>
10 </button>
11 <a class="navbar-brand" href="#">Hello World</a>
12 </div>
13
14 <!-- Collect the nav links, forms, and other content for toggling -->
15 <div class="collapse navbar-collapse navbar-ex1-collapse">
16 <ul class="nav navbar-nav">
17 <li><a href="#">Home</a></li>
18 <li><a href="#">Download</a></li>
19 <li class="dropdown">
20 <a href="#" class="dropdown-toggle" data-toggle="dropdown">
21 Support <b class="caret"></b>
22 </a>
23 <ul class="dropdown-menu">
24 <li><a href="#">Documentation</a></li>
25 <li><a href="#">Help</a></li>
26 </ul>
27 </li>
28 <li><a href="#">About</a></li>
29 </ul>
30 </div><!-- /.navbar-collapse -->
31 </nav>

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.

Inverse Navbar Style

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

Figure C.10. Navbar inverse style

The inverse theme is defined by simply replacing the .navbar-default class of the <nav> element
by the .navbar-inverse class:

<nav class="navbar navbar-inverse" role="navigation">


...
</nav>

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).

Figure C.11. Breadcrumbs

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):

Figure C.12. Pagination

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="#">&laquo; Newest</a></li>
<li><a href="#">&lt; 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 &gt;</a></li>
<li><a href="#">Oldest &raquo</a></li>
</ul>

Buttons & Glyphicons


Twitter Bootstrap provides a nice visual style for button elements (figure C.13).

Figure C.13. Buttons

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

Figure C.14 shows the resulting button appearance:

Figure C.14. Button styles

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.

Figure C.15. Buttons with icons

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.

Figure C.16. Large button

To define such a button, you can use the following HTML code:
Appendix C. Introduction to Twitter Bootstrap 477

<button type="button" class="btn btn-success btn-lg">


<span class="glyphicon glyphicon-download"></span> Download
</button>

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).

Figure C.17. Bootstrap’s Customize Page

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.

Doctrine and Database Management Systems


There are many database management systems (DBMS) on the market. Those systems can be
divided into two groups: traditional relational databases utilizing SQL language for querying
and manipulating data, and NoSQL databases (also called “non-relational” databases) utilizing
“not only SQL” methods for accessing and managing the data. In each particular project you
may prefer certain DBMS because of its capabilities and competitive advantages.

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.

SQL vs. DQL

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

Doctrine supports a number of NoSQL document store databases: MongoDB³⁷, CouchDB³⁸,


OrientDB³⁹ and PHPCR⁴⁰.
For example, in a blog web site, you would have a document named post and a document
named comment. The post document would have fields named id, title, content, author,
date_created; and the comment document would have fields named id, author, content and
date_created. This is very similar to the tables you would have in a relational database.

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.

Components Supporting Relational Databases


Main Doctrine components designed for working with relational databases are shown in figure
D.2 and marked with green. Blue blocks denote the PHP engine and PHP extensions Doctrine is
built on top of.
As you can see from the figure, Doctrine is based on PHP engine features and on PHP extensions
that are actually used as drivers to particular database management systems. Above that base
layer, there are core Doctrine components (like Annotations, Common, etc.) providing essential
functionality for other top-level components. The DBAL component provides an abstraction layer
of database type. And on top of all that there is the ORM component providing API for working
with data in object-oriented way. DoctrineModule and DoctrineORMModule components are
designed for integration with Zend Framework 2.
³⁷https://www.mongodb.org/
³⁸http://couchdb.apache.org/
³⁹http://www.orientechnologies.com/orientdb/
⁴⁰http://phpcr.github.io/
⁴¹http://www.doctrine-project.org/
⁴²https://packagist.org/
Appendix D. Introduction to Doctrine 483

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:

• Doctrine\Common. Common Library for Doctrine projects. This component contains


commonly used functionality. Its Composer-installable package name is doctrine/common.
• Doctrine\Annotations. Docblock Annotations Parser. Its Composer-installable package
name is doctrine/annotations.
⁴³http://en.wikipedia.org/wiki/Data_mapper_pattern
Appendix D. Introduction to Doctrine 484

• Doctrine\Inflector. Common String Manipulations with regard to casing and singular/-


plural rules. Its Composer-installable package name is doctrine/inflector.
• Doctrine\Lexer. Base library for a lexer that can be used in Top-Down, Recursive Descent
Parsers. Its Composer-installable package name is doctrine/lexer.
• Doctrine\Cache. Caching library offering an object-oriented API for many cache back-
ends. Its Composer-installable package name is doctrine/cache.
• Doctrine\DBAL. Database Abstraction Layer. This is a lightweight and thin runtime layer
around a PDO-like API and a lot of additional, horizontal features like database schema
introspection and manipulation through an object oriented API. Its Composer-installable
package name is doctrine/dbal.
• Doctrine\Collections. Collections Abstraction library. Its Composer-installable package
name is doctrine/collections.
• Doctrine\ORM. Object-Relational-Mapper for PHP. This is a Doctrine component provid-
ing a way to work with entity models in object-oriented way instead of raw SQL queries.
Its composer installable package name is doctrine/orm.
• Doctrine\Migrations. Database Schema migrations using Doctrine DBAL. Provide a
consistent way to manage database schema and update it. Its composer installable package
name is doctrine/migrations.
• Doctrine\DataFixtures. Data Fixtures for all Doctrine Object Managers. Provides a
framework for making database fixtures. Its composer installable package name is doctrine/data-fixture

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.

Components Supporting NoSQL Document Databases


Doctrine components designed for working with NoSQL document databases (MongoDB,
CouchDB, etc.) are shown in figure D.3 and marked with green. Blue blocks denote the PHP
engine and PHP extensions Doctrine is built on top of.
As you can see from the figure D.3, Doctrine NoSQL components are based on PHP engine
features and on PHP extensions that can be considered as “drivers” to particular database
management systems. Above that base layer, there are middle level components. The Common
component is the same component that was shown in figure D.2; it provides commonly used
functionality. The Mongodb and CouchDB are components providing low-level API to correspond-
ing databases. The MongodbODM, CouchdbODBM, OrientdbODM and PhpcrODM components provide
Object Document Mappers (ODM) for corresponding databases. ODM concept is very similar
to ORM in sence that it provides an ability to work with a NoSQL database in object oriented
way by mapping a document to a PHP entity class. The DoctrineMongoODMModule component is
intended for integration with ZF2.
Below, you can find the list of components together with their Composer-installable package
names and brief descriptions:

• Doctrine\MongoDB is Doctrine MongoDB Abstraction Layer. Its Composer-installable


package name is doctrine/mongodb.
Appendix D. Introduction to Doctrine 485

• Doctrine\MongodbODM (Object Document Mapper) provides a way to map NoSQL docu-


ments to PHP entity models. Its Composer-installable package name is doctrine/mongodb-odm.

Figure D.3. Doctrine components designed for working with document databases

• Doctrine\MongoODMModule is Zend Framework 2 Module that provides Doctrine Mon-


goDB ODM functionality. It serves for easy integration with ZF2. Its Composer-installable
package name is doctrine/doctrine-mongo-odm-module.
• Doctrine\CouchDB component provides Simple API that wraps around CouchDBs HTTP
API. Its Composer-installable package name is doctrine/couchdb.
• Doctrine\CouchDB component is CouchDB Document Object Mapper. It is analogous to
Doctrine ORM in sence that it provides the way to access database in object oriented way.
Its Composer-installable package name is doctrine/couchdb.
• Doctrine\OrientdbODM is a set of PHP libraries in order to use OrientDB from PHP. Its
Composer-installable package name is doctrine/orientdb-odm.
• Doctrine\PhpcrODM is Object-Document-Mapper for PHPCR. Its Composer-installable
package name is doctrine/phpcr-odm.

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/

You might also like