Using Zend Framework 2

506

Transcript of Using Zend Framework 2

Page 1: Using Zend Framework 2
Page 2: Using Zend Framework 2

Using Zend Framework 2The first book about Zend Framework 2 easy to readand 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 Publishingprocess. Lean Publishing is the act of publishing an in-progress ebook using lightweight toolsand many iterations to get reader feedback, pivot until you have the right book and buildtraction once you do.

©2013 - 2014 Oleg Krivtsov

Page 3: Using Zend Framework 2

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.

Page 4: Using Zend Framework 2

Contents

Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iAbout this Leanpub Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iWhy to Read this Book? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iZend Framework Explained . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iSee ZF2 Wider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iiZF2 Book for Beginners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iiStructure of the Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iiLearn ZF2 by Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iiiBook Site . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ivBook Reviews . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ivTestimonials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ivYour Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vAffiliate Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vAcknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vi

1. Introduction to Zend Framework 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.1 What is Zend Framework 2? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11.2 License . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.3 What Companies Prefer Zend Framework 2? . . . . . . . . . . . . . . . . . . . . 31.4 Release Schedule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51.5 Distributions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.6 User Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.7 Supported Operating Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.8 Server Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81.9 Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91.10 Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.11 Design Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101.12 Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111.13 ZF2 Service Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151.14 Differences with Zend Framework 1 . . . . . . . . . . . . . . . . . . . . . . . . . 16

1.14.1 Backwards Compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . 161.14.2 ZFTool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161.14.3 Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171.14.4 Aspect Oriented Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171.14.5 Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171.14.6 Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171.14.7 Service Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

Page 5: Using Zend Framework 2

CONTENTS

1.15 Competing Frameworks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171.16 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

2. Zend Skeleton Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222.1 Getting Zend Skeleton Application . . . . . . . . . . . . . . . . . . . . . . . . . . 222.2 Typical Directory Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232.3 Installing Dependencies with Composer . . . . . . . . . . . . . . . . . . . . . . . 252.4 Apache Virtual Host . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272.5 Opening the Web Site in Your Browser . . . . . . . . . . . . . . . . . . . . . . . 292.6 Creating NetBeans Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302.7 Hypertext Access File (.htaccess) . . . . . . . . . . . . . . . . . . . . . . . . . . . 322.8 Blocking Access to the Web Site by IP Address . . . . . . . . . . . . . . . . . . . 342.9 HTTP Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352.10 Having Multiple Virtual Hosts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362.11 Hosts File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362.12 Advanced Composer Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37

2.12.1 Package Names and Versions . . . . . . . . . . . . . . . . . . . . . . . . . 372.12.2 Installing and Updating Packages . . . . . . . . . . . . . . . . . . . . . . . 382.12.3 Virtual Packages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392.12.4 Composer and Version Control Systems . . . . . . . . . . . . . . . . . . . 40

2.13 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

3. Web Site Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423.1 PHP Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423.2 PHP Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443.3 PHP Class Autoloading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463.4 PSR-0 Standard . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473.5 HTTP Request and Response . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493.6 Site Entry Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503.7 Events & Application’s Life Cycle . . . . . . . . . . . . . . . . . . . . . . . . . . 513.8 Application Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

3.8.1 Application-Level Config Files . . . . . . . . . . . . . . . . . . . . . . . . . 533.8.2 Application-Level Extra Config Files . . . . . . . . . . . . . . . . . . . . . 553.8.3 Module-Level Config Files . . . . . . . . . . . . . . . . . . . . . . . . . . . 553.8.4 Combining the Configuration Files . . . . . . . . . . . . . . . . . . . . . . 56

3.9 Module Entry Point . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 573.10 Class Autoloading in Zend Framework 2 . . . . . . . . . . . . . . . . . . . . . . 58

3.10.1 Composer-provided Autoloader . . . . . . . . . . . . . . . . . . . . . . . . 583.10.2 Zend\Loader Component . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

3.10.2.1 Standard Autoloader . . . . . . . . . . . . . . . . . . . . . . . . . 603.10.2.2 PSR-0 and Src Directory Structure . . . . . . . . . . . . . . . . . . 613.10.2.3 Class Map Autoloader . . . . . . . . . . . . . . . . . . . . . . . . 62

3.11 Service Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623.11.1 Service Locator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643.11.2 Canonical Service Names . . . . . . . . . . . . . . . . . . . . . . . . . . . 653.11.3 Registering a Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

Page 6: Using Zend Framework 2

CONTENTS

3.11.4 Registering Invokable Classes . . . . . . . . . . . . . . . . . . . . . . . . . 673.11.5 Registering Factories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 673.11.6 Registering Service Aliases . . . . . . . . . . . . . . . . . . . . . . . . . . . 683.11.7 Service Manager Configuration . . . . . . . . . . . . . . . . . . . . . . . . 69

3.12 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

4. Model-View-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 724.1 Get the Hello World Example from GitHub . . . . . . . . . . . . . . . . . . . . . 724.2 Separating Business Logic from Presentation . . . . . . . . . . . . . . . . . . . . 734.3 Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

4.3.1 Base Controller Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 774.3.2 Retrieving Data from HTTP Request . . . . . . . . . . . . . . . . . . . . . 784.3.3 Retrieving GET or POST Variables . . . . . . . . . . . . . . . . . . . . . . 794.3.4 Putting Data to HTTP Response . . . . . . . . . . . . . . . . . . . . . . . . 80

4.4 Variable Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 804.5 Controller Registration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 824.6 When to Create a New Controller? . . . . . . . . . . . . . . . . . . . . . . . . . . 834.7 Controller Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

4.7.1 Writing Own Controller Plugin . . . . . . . . . . . . . . . . . . . . . . . . 844.8 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 854.9 View Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 894.10 View Template Names & View Resolver . . . . . . . . . . . . . . . . . . . . . . . 914.11 Disabling the View Rendering . . . . . . . . . . . . . . . . . . . . . . . . . . . . 934.12 Error Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 954.13 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 974.14 Model Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97

4.14.1 Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 984.14.2 Value Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 994.14.3 Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1004.14.4 Factories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1024.14.5 Repositories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

4.15 Determining the Correct Model Type . . . . . . . . . . . . . . . . . . . . . . . . 1034.16 Skinny Controllers, Fat Models, Simple Views . . . . . . . . . . . . . . . . . . . . 104

4.16.1 Skinny Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1044.16.2 Fat Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1054.16.3 Simple View Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

4.17 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

5. URL Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1075.1 URL Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1075.2 Route Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1085.3 Combining Route Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

5.3.1 Simple Route Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1105.3.2 Tree Route Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

5.4 Routing Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1115.4.1 Configuration for Simple Routes . . . . . . . . . . . . . . . . . . . . . . . . 112

Page 7: Using Zend Framework 2

CONTENTS

5.4.2 Configuration for Nested Routes . . . . . . . . . . . . . . . . . . . . . . . 1125.4.3 Default Routing Configuration in Zend Skeleton Application . . . . . . . . 113

5.5 Literal Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1155.6 Segment Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1165.7 Regex Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1205.8 Wildcard Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1225.9 Other Route Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1245.10 Extracting Parameters from Route . . . . . . . . . . . . . . . . . . . . . . . . . . 126

5.10.1 Retrieving the RouteMatch and the Router Object . . . . . . . . . . . . . . 1275.11 Generating URLs from Route . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128

5.11.1 Generating URLs in View Templates . . . . . . . . . . . . . . . . . . . . . 1285.11.1.1 Passing Parameters . . . . . . . . . . . . . . . . . . . . . . . . . . 1295.11.1.2 Generating Absolute URLs . . . . . . . . . . . . . . . . . . . . . . 1305.11.1.3 Specifying Query Part . . . . . . . . . . . . . . . . . . . . . . . . 131

5.11.2 Generating URLs in Controllers . . . . . . . . . . . . . . . . . . . . . . . . 1315.11.3 URL Encoding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

5.12 Writing Own Route Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1335.12.1 RouteInterface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1335.12.2 Custom Route Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

5.13 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

6. Page Appearance and Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1446.1 About CSS Stylesheets and Twitter Bootstrap . . . . . . . . . . . . . . . . . . . . 1446.2 Page Layout in Zend Framework 2 . . . . . . . . . . . . . . . . . . . . . . . . . . 1466.3 Modifying the Default Page Layout . . . . . . . . . . . . . . . . . . . . . . . . . 1506.4 Switching between Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154

6.4.1 Setting Layout for All Actions of a Controller . . . . . . . . . . . . . . . . 1556.5 Partial Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1566.6 Placeholder View Helper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1586.7 Adding Scripts to a Web Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160

6.7.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1646.8 Adding CSS Stylesheets to a Web Page . . . . . . . . . . . . . . . . . . . . . . . 167

6.8.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1686.9 Writing Own View Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169

6.9.1 Menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1716.9.2 Breadcrumbs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

6.10 View Models and Page Composition . . . . . . . . . . . . . . . . . . . . . . . . . 1796.11 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181

7. Collecting User Input with Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1837.1 Get the Form Demo Sample from GitHub . . . . . . . . . . . . . . . . . . . . . . 1837.2 About HTML Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184

7.2.1 Fieldsets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1867.2.2 Example: “Contact Us” Form . . . . . . . . . . . . . . . . . . . . . . . . . . 1877.2.3 GET and POST Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188

7.3 Styling HTML Forms with Twitter Bootstrap . . . . . . . . . . . . . . . . . . . . 189

Page 8: Using Zend Framework 2

CONTENTS

7.4 Retrieving Form Data in a Controller’s Action . . . . . . . . . . . . . . . . . . . 1917.5 Forms and Model-View-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . 194

7.5.1 A Typical Form Usage Workflow . . . . . . . . . . . . . . . . . . . . . . . 1957.6 A Form Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1967.7 Form Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198

7.7.1 Adding Elements to a Form Model . . . . . . . . . . . . . . . . . . . . . . 2017.7.2 Method 1: Passing an Instance of an Element . . . . . . . . . . . . . . . . . 2017.7.3 Method 2: Using Array Specification . . . . . . . . . . . . . . . . . . . . . 203

7.8 Example: Creating the Contact Form Model . . . . . . . . . . . . . . . . . . . . . 2047.9 Adding Form Validation Rules . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208

7.9.1 Input Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2087.9.2 Adding Inputs to Input Filter . . . . . . . . . . . . . . . . . . . . . . . . . 209

7.9.2.1 Filter Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . 2107.9.2.2 Validator Configuration . . . . . . . . . . . . . . . . . . . . . . . 2107.9.2.3 Attaching Input Filter to Form Model . . . . . . . . . . . . . . . . 211

7.9.3 Creating Input Filter for the Contact Form . . . . . . . . . . . . . . . . . . 2117.10 Using the Form in a Controller’s Action . . . . . . . . . . . . . . . . . . . . . . . 214

7.10.1 Passing Form Data to a Model . . . . . . . . . . . . . . . . . . . . . . . . . 2187.11 Form Presentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222

7.11.1 Preparing the Form Model for Rendering . . . . . . . . . . . . . . . . . . . 2227.12 Standard Form View Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223

7.12.1 Rendering a Form Element . . . . . . . . . . . . . . . . . . . . . . . . . . . 2247.12.2 Rendering an Element’s Validation Errors . . . . . . . . . . . . . . . . . . 2257.12.3 Rendering an Element’s Label . . . . . . . . . . . . . . . . . . . . . . . . . 2257.12.4 Rendering a Form Row . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2267.12.5 Rendering the Entire Form . . . . . . . . . . . . . . . . . . . . . . . . . . . 227

7.13 Example: Creating the View Template for the Contact Form . . . . . . . . . . . . 2277.13.1 Applying the Bootstrap CSS Styles to Form . . . . . . . . . . . . . . . . . . 2297.13.2 Styling the Validation Errors List . . . . . . . . . . . . . . . . . . . . . . . 2317.13.3 Adding the “Thank You” & “Error Sending Email” Pages . . . . . . . . . . 2327.13.4 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232

7.14 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235

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

8.1.1 FilterInterface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2368.2 Standard Filters Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2378.3 Instantiating a Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239

8.3.1 Method 1: Instantiating a Filter Manually . . . . . . . . . . . . . . . . . . . 2408.3.2 Method 2: Constructing a Filter with StaticFilter . . . . . . . . . . . . . . . 2428.3.3 Method 3: Constructing a Filter From Array . . . . . . . . . . . . . . . . . 242

8.4 About Filter Plugin Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2438.5 Filter’s Behavior in Case of Incorrect Input Data . . . . . . . . . . . . . . . . . . 2438.6 Filter Usage Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244

8.6.1 Filters Casting Input Data to a Specified Type . . . . . . . . . . . . . . . . 2448.6.1.1 Int Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244

Page 9: Using Zend Framework 2

CONTENTS

8.6.1.2 Boolean Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2448.6.1.3 Null Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2478.6.1.4 DateTimeFormatter Filter . . . . . . . . . . . . . . . . . . . . . . . 248

8.6.2 Filters Performing Manipulations on a File Path . . . . . . . . . . . . . . . 2498.6.2.1 BaseName Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2498.6.2.2 Dir Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2498.6.2.3 RealPath Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250

8.6.3 Filters Performing Compression and Encryption of Input Data . . . . . . . 2518.6.3.1 Compress Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2518.6.3.2 Encrypt Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253

8.6.4 Filters Manipulating String Data . . . . . . . . . . . . . . . . . . . . . . . . 2548.6.4.1 StringToLower Filter . . . . . . . . . . . . . . . . . . . . . . . . . 2548.6.4.2 PregReplace Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . 2558.6.4.3 StripTags Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2568.6.4.4 StripNewlines Filter . . . . . . . . . . . . . . . . . . . . . . . . . . 2578.6.4.5 UriNormalize Filter . . . . . . . . . . . . . . . . . . . . . . . . . . 257

8.6.5 Organizing Filters in a Chain . . . . . . . . . . . . . . . . . . . . . . . . . 2598.6.6 Custom Filtering with the Callback Filter . . . . . . . . . . . . . . . . . . . 260

8.6.6.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2618.7 Writing Your Own Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264

8.7.1 Using the PhoneFilter Class . . . . . . . . . . . . . . . . . . . . . . . . . . 2678.8 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268

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

9.1.1 ValidatorInterface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2709.2 Standard Validators Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2719.3 Validator Behaviour in Case of Invalid or Unacceptable Data . . . . . . . . . . . 2739.4 Instantiating a Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274

9.4.1 Method 1. Manual Instantiation of a Validator . . . . . . . . . . . . . . . . 2759.4.2 Method 2. Using StaticValidator Wrapper . . . . . . . . . . . . . . . . . . . 2789.4.3 Method 3. Using an Array Configuration . . . . . . . . . . . . . . . . . . . 279

9.5 About Validator Plugin Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . 2809.6 Validator Usage Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280

9.6.1 Validators for Checking Value Conformance to Certain Format . . . . . . . 2809.6.1.1 Ip Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2809.6.1.2 Hostname Validator . . . . . . . . . . . . . . . . . . . . . . . . . . 2819.6.1.3 Uri Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2839.6.1.4 Date Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2849.6.1.5 Regex Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285

9.6.2 Validators for Checking a Numerical Value Lies in a Given Range . . . . . 2869.6.2.1 NotEmpty Validator . . . . . . . . . . . . . . . . . . . . . . . . . . 2869.6.2.2 Between Validator . . . . . . . . . . . . . . . . . . . . . . . . . . 2889.6.2.3 InArray Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . 2899.6.2.4 StringLength Validator . . . . . . . . . . . . . . . . . . . . . . . . 290

9.6.3 Organizing Validators in a Chain . . . . . . . . . . . . . . . . . . . . . . . 291

Page 10: Using Zend Framework 2

CONTENTS

9.6.4 Custom Validation with the Callback Validator . . . . . . . . . . . . . . . . 2929.6.4.1 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293

9.7 Writing Own Validator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2959.7.1 Using the PhoneValidator Class . . . . . . . . . . . . . . . . . . . . . . . . 299

9.8 Using Filters & Validators Outside a Form . . . . . . . . . . . . . . . . . . . . . . 3009.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302

10.Uploading Files with Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30310.1 About HTTP File Uploads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303

10.1.1 HTTP Binary Transfer Encoding . . . . . . . . . . . . . . . . . . . . . . . 30310.1.2 $_FILES Super-Global Array in PHP . . . . . . . . . . . . . . . . . . . . . 305

10.2 Accessing Uploaded Files in ZF2 . . . . . . . . . . . . . . . . . . . . . . . . . . . 30710.3 File Uploads & ZF2 Form Model . . . . . . . . . . . . . . . . . . . . . . . . . . . 30710.4 Validating Uploaded Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30810.5 Filtering Uploaded Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31010.6 InputFilter Container & File Uploads . . . . . . . . . . . . . . . . . . . . . . . . . 311

10.6.1 FileInput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31110.6.2 Executing Validators before Filters . . . . . . . . . . . . . . . . . . . . . . 312

10.7 Controller Action & File Uploads . . . . . . . . . . . . . . . . . . . . . . . . . . . 31310.8 Example: Image Gallery . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314

10.8.1 Adding ImageForm Model . . . . . . . . . . . . . . . . . . . . . . . . . . . 31610.8.2 Adding Validation Rules to ImageForm Model . . . . . . . . . . . . . . . . 31710.8.3 Writing ImageManager Service . . . . . . . . . . . . . . . . . . . . . . . . 31910.8.4 Adding ImageController . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324

10.8.4.1 Adding Upload Action & Corresponding View Template . . . . . . 32510.8.4.2 Adding Index Action & Corresponding View Template . . . . . . . 32810.8.4.3 Adding File Action . . . . . . . . . . . . . . . . . . . . . . . . . . 33010.8.4.4 Registering the ImageController . . . . . . . . . . . . . . . . . . . 332

10.8.5 Results . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33310.9 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335

11.Advanced Usage of Forms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33611.1 Form Security Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336

11.1.1 CAPTCHA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33611.1.1.1 CAPTCHA Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 33711.1.1.2 CAPTCHA Form Element & View Helper . . . . . . . . . . . . . . 338

11.1.2 Example 1: Adding Image CAPTCHA to the ContactForm . . . . . . . . . . 33911.1.3 Example 2: Adding a FIGlet CAPTCHA to the ContactForm . . . . . . . . 34211.1.4 Example 3: Adding reCaptcha CAPTCHA to the ContactForm . . . . . . . 34411.1.5 CSRF Prevention . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346

11.1.5.1 Example: Adding a CSRF Element to Form . . . . . . . . . . . . . 34811.2 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349

12.Database Management with Doctrine ORM . . . . . . . . . . . . . . . . . . . . . . 35012.1 Get Blog Example from GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . 35012.2 Creating a Simple MySQL Database . . . . . . . . . . . . . . . . . . . . . . . . . 352

12.2.1 Creating New Schema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353

Page 11: Using Zend Framework 2

CONTENTS

12.2.2 Creating Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35412.2.3 Importing Ready Database Schema . . . . . . . . . . . . . . . . . . . . . . 356

12.3 Integrating Doctrine ORM with Zend Framework 2 . . . . . . . . . . . . . . . . 35712.3.1 Installing Doctrine Components with Composer . . . . . . . . . . . . . . . 35812.3.2 Loading Doctrine Integration Modules on Application Start Up . . . . . . . 36012.3.3 Doctrine Configuration Overview . . . . . . . . . . . . . . . . . . . . . . . 36112.3.4 Overriding the Default Doctrine Configuration . . . . . . . . . . . . . . . 363

12.4 Specifying Database Connection Parameters . . . . . . . . . . . . . . . . . . . . 36312.5 About Doctrine Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365

12.5.1 Annotations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36612.6 Creating Entities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368

12.6.1 Adding Post Entity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36812.6.2 Adding the Comment and Tag Entities . . . . . . . . . . . . . . . . . . . . 37112.6.3 Specifying Relations between Entities . . . . . . . . . . . . . . . . . . . . . 375

12.6.3.1 OneToMany/ManyToOne . . . . . . . . . . . . . . . . . . . . . . . 37512.6.3.2 ManyToMany . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378

12.6.4 Specifying Entity Locations . . . . . . . . . . . . . . . . . . . . . . . . . . 38012.7 About Entity Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381

12.7.1 Entity Repositories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38212.8 Adding Blog Home Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38312.9 Adding New Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385

12.9.1 Adding PostForm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38612.9.2 Adding PostManager Service . . . . . . . . . . . . . . . . . . . . . . . . . 39012.9.3 Creating Controller Action and View Template . . . . . . . . . . . . . . . . 393

12.10 Editing Existing Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39612.10.1Modifying PostManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39712.10.2Adding Controller Action and View Template . . . . . . . . . . . . . . . . 398

12.11 Deleting Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40212.11.1Modifying PostManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40312.11.2Adding Controller Action . . . . . . . . . . . . . . . . . . . . . . . . . . . 404

12.12 Implementing Post Preview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40512.12.1Adding CommentForm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40712.12.2Modifying PostManager . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40912.12.3Adding Controller Action and View Template . . . . . . . . . . . . . . . . 410

12.13 Implementing Admin Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41412.14 Implementing Tag Cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417

12.14.1Adding Custom Post Repository . . . . . . . . . . . . . . . . . . . . . . . . 41812.14.2Calculating Tag Cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42012.14.3Modifying Controller Action . . . . . . . . . . . . . . . . . . . . . . . . . . 42112.14.4Rendering Tag Cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422

12.15 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424

Appendix A. Configuring Web Development Environment . . . . . . . . . . . . . . . 425Installing Apache, PHP and MySQL in Linux . . . . . . . . . . . . . . . . . . . . . . . 425

Installing Apache and PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426Checking Web Server Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . 428

Page 12: Using Zend Framework 2

CONTENTS

Editing PHP Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428Restarting Apache Web Server . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430Enabling Apache’s mod_rewrite module . . . . . . . . . . . . . . . . . . . . . . . 430Creating Apache Virtual Host . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431Installing XDebug PHP extension . . . . . . . . . . . . . . . . . . . . . . . . . . . 432Installing APC PHP Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434Installing MySQL Database Server . . . . . . . . . . . . . . . . . . . . . . . . . . 434Configuring the MySQL Database Server . . . . . . . . . . . . . . . . . . . . . . 435

Installing Apache, PHP and MySQL in Windows . . . . . . . . . . . . . . . . . . . . . 435Checking Web Server Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . 438Enabling Apache’s mod_rewrite module . . . . . . . . . . . . . . . . . . . . . . . 439Creating Apache Virtual Host . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440Installing XDebug PHP extension . . . . . . . . . . . . . . . . . . . . . . . . . . . 441Installing MySQL Database Server . . . . . . . . . . . . . . . . . . . . . . . . . . 442Configuring the MySQL Database Server . . . . . . . . . . . . . . . . . . . . . . 442

Installing NetBeans IDE in Linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444Method 1. Installing from Repository . . . . . . . . . . . . . . . . . . . . . . . . . 444Method 2. Downloading from Web Site . . . . . . . . . . . . . . . . . . . . . . . 445

Installing NetBeans IDE in Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . 448Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448

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

Appendix C. Introduction to Twitter Bootstrap . . . . . . . . . . . . . . . . . . . . . . 464Overview of Bootstrap Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464Grid System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466

Defining the Grid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467Offsetting Columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468Nesting Grids . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468“Mobile First” Concept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 469

Bootstrap’s Interface Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 470Navigation Bar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471

Dropdown Menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471Collapsible Navbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472Inverse Navbar Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473

Breadcrumbs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474

Page 13: Using Zend Framework 2

CONTENTS

Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474Buttons & Glyphicons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475

Customizing Bootstrap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478

Appendix D. Introduction to Doctrine . . . . . . . . . . . . . . . . . . . . . . . . . . . 479Doctrine and Database Management Systems . . . . . . . . . . . . . . . . . . . . . . . 479

Relational Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479SQL vs. DQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480

NoSQL Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481Document Databases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482

Doctrine Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482Components Supporting Relational Databases . . . . . . . . . . . . . . . . . . . . 482Components Supporting NoSQL Document Databases . . . . . . . . . . . . . . . 484

Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485

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

Page 14: Using Zend Framework 2

PrefaceAbout this Leanpub Book

Thanks to Leanpub¹, you have a chance to obtain this book at a lower cost and give your feedbackto the author. This makes it possible for the author to immediately correct mistakes and enhancethe 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 givematerial 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 Frameworkthat 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 andZend Framework 2 (ZF2). ZF2 is a modern PHP web development framework intended forbuilding professionally looking, scalable and secure web-sites. Such web sites are easy to testand maintain. The framework utilizes the best practices and common design patterns, inspiredby 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 implementautomatic code testing.

¹https://leanpub.com²http://youtu.be/A9BSV0RXn2k

Page 15: Using Zend Framework 2

Preface ii

See ZF2 Wider

This e-book is not only about Zend Framework, but also about closely related libraries. AlthoughZend Framework 2 has dedicated component for accessing the database, in this book we usethird-party library called Doctrine ORM — a de-facto standard object-oriented way to performdatabase management. In the sample applications we will create in chapters of this book, TwitterBootstrap CSS Framework is used, allowing to produce nice looking visual appearance and layoutof 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 authorstrives to give material starting with simple things that a beginner should understand. Advancedthings go last in a chapter. You do not need to be a guru in design patterns to understand mostof the stuff.

To read and understand this book, you need to have a basic knowledge of PHP language. A goodpoint for learning PHP is its official web site³ and the online documentation⁴. It would be goodif you have some idea of what is HTTP request, GET and POST variables, namespaces, classesand interfaces.

Because PHP is closely related to other web technologies, it is also recommended that you havesome basic experience in the following:

• HTML (Hyper Text Markup Language) – used for creating web pages that can be displayedin a web browser.

• CSS (Cascading Style Sheets) – used for defining the look and feel of a web page, like fontsize 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 tomake you familiar with fundamental conceptsand main components of the framework; Chapter 2 “Zend Skeleton Application” is dedicated togiving you instructions to install the skeleton application, which can be used for creation of yourown web sites, and so on.

³http://php.net/⁴http://php.net/docs.php⁵http://www.w3schools.com/

Page 16: Using Zend Framework 2

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 inaction. 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 youcan download the entire code archive by visiting this page⁶. To download the archive, click theDownload 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

Page 17: Using Zend Framework 2

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 youover that initial steep learning curve and providing enough information to get you started onbuilding 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 importantnotions, 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 availableresource to get started with ZF2.” ∼Janusz K

“I purchased your book on Zend framework 2 some days ago and I thought i should congratulateyou for your amazing work. I tried another books and methods to learn zf2, but definitely yourbook 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/

Page 18: Using Zend Framework 2

Preface v

Zf2 is something complex to me and your book is making it easier. I really like the detailedexplanations of the concepts and examples you use.” ∼Welington*

“I am one of (hopefully many) people who bought and read your ‘using ZF2’ book. […] Your booktaught me not only many new concepts, but also why these concepts came to be and (as a personalcomfort to me) that almost half of these new features (or rather: ways of thinking) were things Iwas already doing, albeit in some other, non-object oriented way; I just never realised it. Havingthings explained by someone who obviously knows what he is talking about was a great help tome, and while I have yet to reach any important milestone, I feel I understand what I have to domuch better now and I am much more confident that I will eventually successfully ‘refresh’ myhopelessly outdated projects.” ∼J.B.

Your Feedback

Thank you for reading this book and helping to make it better. You are encouraged to pointout errors, make suggestions and critical remarks. You can write the author a message throughthe dedicated Forum¹⁰. Alternatively, you can contact the author through his E-mail address([email protected]). Your feedback is highly appreciated.

Affiliate Program

Now the “Using Zend Framework 2” book is part of the Leanpub Affiliate Program¹¹. This meansthat anyone can earn 50% of profit for advertising the book on his/her web site. So, if you like thisbook 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/

Page 19: Using Zend Framework 2

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 thisbook, and for making a design for the book’s web site. Also thanks to Moriancumer Richard Uyand 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 isa recognised PHP user group in South England) for reviewing the book. Richard’s review¹⁵ isreally 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/

Page 20: Using Zend Framework 2

1. Introduction to Zend Framework 2In this chapter we’ll learn about Zend Framework 2, its main principles and components. We’llalso 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 yourcode 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 growsin size, it becomes more and more difficult to develop the code in such manner. Moreover, whenyou switch to the development of a new site, you will notice that a large portion of the code youhave already written for the old site can be used again with small modifications. This code canbe 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) providingweb 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 frameworkis the basement. The basement contains a lot of building blocks (components) and tools preparedfor 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 (andsponsored) by Zend, which is also known as the vendor of the PHP language itself. The firstversion (Zend Framework 1) was released in 2007 and since then it has become obsolete. ZendFramework 2 (or shortly ZF2) is the second version of this software, and it was released inSeptember 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 providesmany 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-Controllerpattern used by ZF2 allows to separate business logics and presentation layer of your website, making its structure consistent and maintainable.

• Scale your web site with the concept of modules. ZF2 uses the term module, allowing toseparate decoupled site parts, thus allowing to reuse models, views, controllers and assetsof your web-site in other works.

• Accessing database in an object-oriented way. Instead of directly interacting with thedatabase using SQL queries, you can use Doctrine Object-Relational Mapping (ORM) tomanage the structure and relationships between your data. With Doctrine you map yourdatabase table to a PHP class (also called an entity class) and a row from that table ismapped to an instance of that class. Doctrine allows to abstract of database type and makecode easier to understand.

Page 21: Using Zend Framework 2

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) andCross-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 commercialand 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 ZendFramework 2, it is also recommended that you mention about it in your site’s documentation oron About page.

Below, the Zend Framework 2 license text is presented. As you can see, it is rather short.

Page 22: Using Zend Framework 2

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 websites. 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 broadcastingstatutory corporation ².

¹http://framework.zend.com/²BBC – From Wikipedia, the free encyclopedia http://en.wikipedia.org/wiki/BBC

Page 23: Using Zend Framework 2

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 strongestbanks in the world according to the agency Standard & Poor’s ³.

³BNP Paribas – From Wikipedia, the free encyclopedia http://en.wikipedia.org/wiki/BNP_Paribas

Page 24: Using Zend Framework 2

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 bugsto be fixed quickly, thus allowing your sites to remain stable and secure. Release history⁴ forrecent versions of ZF2 is shown in the table 1.1:

Table 1.1. ZF2 Release History

Date Version

March 12, 2014 ZF 2.3.0October 31, 2013 ZF 2.2.5August 26, 2013 ZF 2.2.4August 21, 2013 ZF 2.2.3June 24, 2013 ZF 2.2.2June 12, 2013 ZF 2.2.1May 15, 2013 ZF 2.2.0 StableMay 10, 2013 ZF 2.2.0rc3May 6, 2013 ZF 2.2.0rc2May 1, 2013 ZF 2.2.0rc1April 17, 2013 ZF 2.1.5April 17, 2013 ZF 2.1.5

⁴http://framework.zend.com/blog

Page 25: Using Zend Framework 2

Introduction to Zend Framework 2 6

Table 1.1. ZF2 Release History

Date Version

March 14, 2013 ZF 2.1.4February 21, 2013 ZF 2.1.3February 20, 2013 ZF 2.1.2February 06, 2013 ZF 2.1.1January 30, 2012 ZF 2.1.0 StableJanuary 30, 2012 ZF 2.0.7December 19, 2012 ZF 2.0.6November 25, 2012 ZF 2.0.5November 20, 2012 ZF 2.0.4October 17, 2012 ZF 2.0.3September 21, 2012 ZF 2.0.2September 20, 2012 ZF 2.0.1September 05, 2013 ZF 2.0.0 Stable

As you can see from the table above, Zend Framework 2 is being constantly developed andupdated. Its developers listen to user community’s feedback and strive to keep the frameworkwell polished and ready for use in production systems. For the detailed list of changes betweenthe 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 infigure 1.4) to become familiar with its structure and components.

ZF2 can be downloaded in two types: full andminimum. A full-size archive contains a completeset of components plus demos; its size is about 3 Mb. Mimimum-size distribution contains librarycomponents 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 becomefamiliar with Composer later in Chapter 2.

1.6 User Support

Support is an important thing to consider when deciding whether to use the framework asthe base for your web site or not. Support includes well written documentation, webinars,community forums and (optionally) commercial support services, like trainings and certificationprograms.

⁵http://framework.zend.com/changelog/⁶http://framework.zend.com/

Page 26: Using Zend Framework 2

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

Page 27: Using Zend Framework 2

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 versionof the framework. It also shows how namespaces, class autoloading, and exceptions areused in ZF2.

• Getting started with ZF2. Teaches you the basics of developing ZF2-based applications, likecreating controllers and views, manipulating services and listening to events.

• The MVC architecture of ZF2. Teaches the MVC (Model View Controller) architecture ofZend Framework 2.

Training Classes with live instructors can be accessed by this link¹⁰. Here you can learn ZF2 bydoing exercises, mini-projects and developing real code.

Certification Program. Allows you to become a Zend Certified Engineer (ZCE), thus making iteasier 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 Linuxserver and on any other operating systems PHP can run in. For instance, for creating samplesfor 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 touse Linux, because most server software operates on Linux servers. You can refer to AppendixA 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 thatthis is a rather strict requirement. Not all cheap shared hostings and not all private servers havesuch 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 Composercommand-line tool. Some shared hostings provide FTP access only, so you won’t be able to installa 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 allfiles 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/

Page 28: Using Zend Framework 2

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 PHPextensions, like memory caching extension, to improve ZF2 performance. This can be a difficultywhen 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 installZF2 on is a server with the latest version of PHP and with shell access to be able to executeComposer, install PHP extensions and provide an ability to schedule console PHP scripts bycron.

If your company manages its own server infrastructure and can afford upgrading PHP versionto 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 hostingservice, like Amazon Web Services¹². Amazon provides Linux server instances as a part of EC2service. EC2 is rather cheap, and it provides a free usage tier¹³ letting you try it for free for oneyear.

1.9 Security

Zend Framework 2 follows the best practices to provide you with a secure code base for yourweb sites. ZF2 creators release security bug fixes on a regular basis. You can incorporate thosefixes 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. Ifa site user enters an invalid URL in a web browser’s navigation bar, he is automaticallyredirected to an error page.

• Access control lists and Role-Based Access Control (RBAC) allow to define flexible rulesfor granting or denying access to certain resources of your web site. For example, ananonymous user would have access to your index page only, authenticated users wouldhave access to their profile page, and the administrator user would have access to sitemanagement 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 tocheck 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 checksand 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/

Page 29: Using Zend Framework 2

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 thefirst version of the framework.

Lazy class autoloading. Classes are loaded once needed. You don’t have to write require_oncefor each class you want to load. Instead, the framework automatically discovers your classesusing the autoloader feature. Autoloader uses either class map or class naming conventions tofind 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 orMemcache) that can be usedto speed-up ZF2-basedweb sites. Caching saves frequently used data tomemory to speed-up dataretrieval. For example, a Zend Framework 2 application consists of many files which require timefor PHP interpreter to process the files each time you open the page in the browser. You can useAPC extension to cache precompiled PHP opcodes to speed up your site. Additionally, you canuse the ZF2’s event manager to listen to dispatching events, and cache HTML response data withMemcache extension.

Are there any benchmark tests of ZF2 performance?

As per the author’s knowledge, currently, there are no reliable benchmark tests thatwould 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 tounderstand patterns to read this book, this section is intended to give you an idea of what designpatterns 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 separationof concerns. With MVC, you can reuse your components in a different project. It is alsoeasy to substitute any part of this triad. For example, you can easily replace a view withanother one, without changing your business logics.

• Domain Driven Design (DDD) pattern In Zend Framework 2, by convention, you will havemodel 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), viewhelpers (reusable plugin classes intended for rendering stuff) and others.

Page 30: Using Zend Framework 2

Introduction to Zend Framework 2 11

• Aspect Oriented Design pattern. In ZF2, everything is event-driven. When a site userrequests a page, an event is generated (triggered). A listener (or observer) can catch eventand do something with it. For example, a router service parses the URL and determineswhat controller class to call. When the event finally reaches the page renderer, an HTTPresponse is generated and the user sees the web page.

• Singleton pattern. In ZF2, there is the service manager object which is the centralizedregistry of all services available in the application. Each service exists in a single instanceonly.

• Strategy pattern.While browsing ZF2 documentation or source code, you’ll encounter theword strategy for sure. A strategy is just a class encapsulating some algorithm. And youcan use different algorithms based on some condition. For example, the page renderer hasseveral rendering strategies, making it possible to render web pages differently based onAccept HTTP header (the renderer can generate an HTML page, a JSON response, an RSSfeed 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 usesadapters 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 cancreate 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 ageneric factory interface instead of hard-coding the concrete class name. This simplifiesthe testing of your model and controller classes.

1.12 Components

ZF2 developers believe that the framework should be a set of decoupled components withminimum 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 withanother framework. This becomes even easier, keeping in mind that each component of ZF2 isa Composer-installable package, so you can easily install any ZF2-component together with itsdependencies 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 anyweb application. They provide the base functionality for class auto-loading, for triggeringevents and listening to them, for loading modules, for organizing the code within a modulein conformance to theModel-View-Controller pattern, for creating console commands andmore.

• Web Forms. Forms are the way of collecting user-entered data on web pages. A formusually 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.

Page 31: Using Zend Framework 2

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

• Presentation Utilities. In this group, we can put components implementing useful web pageelements: navigation bars, progress bars, etc.

• Persistence. This group contains components whose purpose is to convert in-memory datainto 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 foraccessing 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 differenttransports.

• 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 savefrequently used data to memory (or another storage media) tospeed-up data retrieval.

Zend\Code Provides facilities to generate arbitrary code using an objectoriented interface. Also includes annotation parsing.

Zend\Console Provides an ability to create applications runnable from shellcommand line. Console can be used, for example, for executingscheduled actions, like mail delivery.

Zend\Di Dependency injection. Can be used to easily substitute and replacedependent classes.

Zend\EventManager Allows to send events and register listeners to react to them. Thiscomponent is covered in Chapter 3.

Zend\Http Provides an easy interface for performing Hyper-Text TransferProtocol (HTTP) requests. This component is covered in Chapter 4.

Zend\Loader PHP class discovery and autoloading support. Autoloading is a moreefficient replacement for require_once. This component is covered inChapter 3.

Zend\ModuleManager Zend Framework 2 module manager. In ZF2, every application consistsof modules. This component is covered in Chapter 3.

Zend\Mvc Support of Model-View-Controller pattern. Separation of businesslogic from presentation. This component is covered in Chapter 4.

Zend\ServiceManager Service manager. This is the registry of all services availablein the application, making it possible to access services from

Page 32: Using Zend Framework 2

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 variableescaping. Used in presentation layer. This component is covered inChapter 4.

Zend\Uri A component that aids in manipulating and validating UniformResource 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 anddecoding JSON to native PHP. Used for object serialization.

Zend\Serializer Provides an adapter based interface to simply generate storablerepresentation of PHP types by different facilities, and recover them.

User Management

Zend\Authentication Provides an API for user authentication. Users are typicallyauthenticated by providing a username and password which arecompared 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 cookiedata, across multiple page requests by the same client.

Presentation Utilities

Zend\Barcode Provides a generic way to generate barcodes. A barcode is asmall bar containing stripes of various width and is opticallyreadable by a machine. You may have seen barcodes when purchasinggoods in a supermarket. This component is covered in Chapter 5.

Zend\Captcha Human input check. Generates a random image ensuring that yoursite’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.

Page 33: Using Zend Framework 2

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 totroubleshoot possible errors with your site in development and productionenvironments.

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. Thiscomponent is covered in Chapter 8.

Zend\Form Web form data collection, filtering, validation and rendering. Thiscomponent is covered in Chapter 7 and Chapter 10.

Zend\InputFilter Provides an ability to define form data validation rules. This component iscovered in Chapter 7.

Zend\Validator Provides a set of commonly needed validators. This component is coveredin 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, andmodifying 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 ProcedureCall (RPC) protocol.

Mail

Zend\Mail Provides generalized functionality to compose and send both textand MIME-compliant multi-part E-mail messages. This component iscovered 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 foraccessing the configuration data within application code.

Zend\Crypt Contains implementation of symmetric and asymmetric cryptographicalgorithms.

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

Page 34: Using Zend Framework 2

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 iscovered 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 provideimplementations 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 (currentlyavailable) service components together with their brief descriptions:

Component Name Description

ZendService\Akismet Provides API for accessing Akismet¹⁵ (a spamfiltering service for your blog).

ZendService\Amazon Provides API for using Amazon¹⁶ web services.Amazon provides a number of web services, among them EC2 (webhosting in the cloud), S3 (storage in the cloud) and others.

ZendService\AppleApns Provides a client for the Apple Push Notification Service (APNs forshort), which is a service for propagating information to iOS andMac OS X devices.

ZendService\Audioscrobbler API for using the Audioscrobbler¹⁷ service, which is a database thattracks listening habits.

ZendService\Delicious API for using del.icio.us¹⁸ services. Provides access to posts atdel.icio.us and read-only access to public data of all users.

ZendService\DeveloperGarden Provides API for accessing services of Deutsche Telekom, such asvoice 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 MediaFile System (IMFS), a storage service that allows applications toupload, store and access files.

¹⁵http://akismet.com/¹⁶http://aws.amazon.com/¹⁷http://www.audioscrobbler.net/¹⁸https://delicious.com/¹⁹http://www.flickr.com/

Page 35: Using Zend Framework 2

Introduction to Zend Framework 2 16

Component Name Description

ZendService\Rackspace API to manage the Rackspace services Cloud Files and CloudServers.

ZendService\ReCaptcha Provides API for the reCAPTCHA²⁰ service, used to digitize booksand (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 DataQuality & Enhancement Solutions.

ZendService\Technorati Provides interface for using Technorati²³, which is a place storingindividual 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 webhosting platform.

Because the API to above mentioned web resources may be changed by their vendorswithout a notice, those components are not part of the “core” Zend Framework 2distribution. By the same reason, those components are not discussed deeply in thisbook.

1.14 Differences with Zend Framework 1

For readers who have an experience in Zend Framework 1, in this section we’ll give someinformation 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 rewrittenfrom 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 andcontrollers. In ZF2, you create your new applications by downloading Zend Skeleton Applicationavailable on GitHub. By the way, in ZF2 you can install a component called ZFTool, and it canalso 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/

Page 36: Using Zend Framework 2

Introduction to Zend Framework 2 17

1.14.3 Modules

In Zend Framework 1, your applicationwasmonolithic (although therewas a concept ofmodule).In ZF2, everything is a module. The skeleton application has single Application module bydefault. 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 itwill just work by listening to events occurring in the application without knowing about othermodules. 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\AbstractActionController,which can be easier to type with auto-completion feature and easier to understand. Namespacesallow 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 configurationfile in a form of PHP array. At application level, module configurations are finally merged intoa single large nested PHP array.

1.14.7 Service Manager

In ZF1, you had an application registry of classes, which allowed you to access applicationservices and even put your own class to the registry and use it later. In ZF2, we have theservice manager, which is a more complex version of the registry, implementing lazy loadingand dependency injection. With service manager, you can register a service class and use itacross your modules. For example, Doctrine ORM library registers the Entity Manager servicewhich 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/

Page 37: Using Zend Framework 2

Introduction to Zend Framework 2 18

frameworks in some way, we can use Google Trends³⁰ site, which allows to track count of akeyword 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 peakby 2010, and since then it has slowly lost its popularity. However, ZF is still one of the strongplayers on the market. On the other hand, Cake PHP, Symfony, CodeIgniter and Yii frameworkare becoming highly popular nowadays.

Let’s also look at the relative popularity of ZF1 and ZF2 by typing “Zend Framework, ZendFramework 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 tobecome popular. The author believes that ZF2 has all the necessary qualities to become popularover time.

³⁰http://www.google.ru/trends/

Page 38: Using Zend Framework 2

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 thecomparison of features provided by those PHP frameworks. Capabilities of Zend Framework 2are marked with bold.

Table 1.4. Comparison of Features provided by PHP frameworks

Feature ZF2 Symfony 2 Cake PHP CodeIgniter Yii

Currentversion

2.2.1 2.3.1 2.3.6 2.1.3 1.1.13

Distribution 3 Mb 4.4 Mb 2.0 Mb 2.2 Mb 3.9 Mbarchive size

Installation Composer Composer Archive Archive Archive

Compatibility Bad (requires Bad (requires Good Good Goodwith SSH and SSH andsharedhostings

vhosts) vhosts)

Monolithic or Components Components Components Components Monolithiccomponent-based?

Prefer Configuration Configuration Conventions Conventions Conventionsconventionsor configs?

Databaseaccess

Data Mapper Data Mapper Active Record Traditional Active Record,

pattern (Doctrine/ORM),(Doctrine/ORM) or PDOTableGateway,

Active Record

RowGateway

Page 39: Using Zend Framework 2

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

Yes (Doctrine- Yes Yes Yes

migrations provided) provided)

CSSFramework

Twitter Twitter Any Any Blueprint CSS

Bootstrap Bootstrap

ViewTemplate

Any youwant, or

Twig Smarty/Twig Any youwant,

None or Prado

Language none at all (recommended) or none

Unit testing Yes(PHPUnit)

Yes (PHPUnit) Yes (PHPUnit) Yes (PHPUnit) Yes (PHPUnit-

support based)

Functional Yes(PHPUnit)

Yes (PHPUnit) Yes No Yes (Selenium)

testing

Database Yes(Doctrine-

Yes (Doctrine Yes No Yes

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 PHPframeworks on the market. This allows to be sure that ZF2 creators won’t stop to updateand support it unexpectedly.

• The major way for installing ZF2 is through Composer dependency manager. Symfony 2is similar to ZF2 in this sence. Other PHP frameworks utilize the conventional installationfrom 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 installyour website to a shared hosting, you probably need to contact the hosting’s support forinstallation instructions.

• ZF2 provides the sophisticated configuration methods, so you can fine-tune and overrideany aspect of its work. Some other PHP frameworks prefer the “conventions overconfiguration” way, making it easier for newbies to start developing websites.

• For the presentation layer, ZF2 suggests the use of Twitter Bootstrap CSS Framework bydefault. 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 (withDoctrine ORM). Additionally, you can use Doctrine migrations mechanism for managingthe database schema in a standardized way.

• Comparing to other frameworks, ZF2 provides good capabilities for unit- and functionaltesting (based on PHPUnit framework). This makes it possible to automate the testing of

Page 40: Using Zend Framework 2

Introduction to Zend Framework 2 21

the code you write. For testing the database functionality, you can use Doctrine-providedfixture mechanism.

1.16 Summary

A PHP framework is a library, giving you the code base and defining consistent ways of creatingweb applications. Zend Framework 2 is a modern web development framework created bythe Zend Company, the vendor of PHP language. It provides the developers with outstandingcapabilities for building scalable and secure web sites. ZF2 is licensed under BSD-like license andcan 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 theframework. Among the features that contribute into the performance of ZF2, there are lazy classautoloading and support of caching.

On the market, Zend Framework is not the only web development framework. ZF2 is positionedas a good framework for corporate applications because of its pattern-based design andscalability. 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 contactthe author through his E-mail address ([email protected]). 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

Page 41: Using Zend Framework 2

2. Zend Skeleton ApplicationZend Framework 2 provides you with the so called “skeleton application” to make it easier tocreate your new applications from scratch. In this chapter, we will download and install theskeleton application which can be used as a base for creating your web sites. It is recommendedthat you refer to Appendix A before reading this chapter to get your development environmentconfigured.

2.1 Getting Zend Skeleton Application

The Skeleton Application is a simple ZF2-based application that contains most necessary thingsfor creating your own simple web site. The skeleton application’s code is stored on GitHub codehosting and can be publicly accessed by this link¹. To download the source code of the skeletonapplication 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, youcan 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 isrecommended 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 thatyou’ve downloaded to your home directory, then unpack the archive.

If you are using Windows, you can place the skeleton app directory anywhere in yoursystem, but ensure that file and directory access rights are sufficient for your web serverto read and write the directory and its files. Actually, if you don’t put your files toC:\Program Files, everything should be OK.

¹https://github.com/zendframework/ZendSkeletonApplication

Page 42: Using Zend Framework 2

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 recommendedway. Of course, you can configure your application to use a different directory layout, but thismay make it difficult to support your web-site by other people who are not familiar with such adirectory 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 areseveral 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 1of this book);

• composer.phar is an executable PHP archive containing the code of Composer dependencymanagement tool; we will use it later;

• composer.json is a JSON configuration file for Composer.

Page 43: Using Zend Framework 2

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 cacheused to speed-up Zend Framework.

The module directory contains all application modules. Currently there is a single module calledApplication. The Application is the main module of your web-site. You can also put othermodules 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 Framework2 library files. Currently this directory is almost empty, but we will install all required librarieslater.

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

Your web site will have a single entry point, index.php, because this is more secure thanallowing anyone to access all your PHP files.

Inside of the public directory, you can also find .htaccess file. Its main purpose is to define URLrewriting 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 HTTPauthorization 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.);

Page 44: Using Zend Framework 2

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 JavaScriptfiles here, too.

What is jQuery library?

jQuery is a JavaScript library which was created to simplify the client-side scriptingof HTML pages. jQuery’s selector mechanism allows to easily attach event handlers tocertain 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’sfiles. You can ignore them (or even remove them if you do not plan to store your code in a GITrepository).

Because we will later use the skeleton as the base for our Hello World application, let’s renamethe ZendSkeletonApplication-master directory into helloworld, which also sounds shorter. InLinux, 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 installationof your application’s dependencies. A dependence is some third-party code your app uses. Forexample Zend Framework 2 is the dependence for your web-site.

In Composer, any library is called a package. All packages installable by Composer are registeredon Packagist.org⁵ site. With Composer, you can identify the packages that your app requires andhave Composer to download and install them automatically.

The dependencies of the skeleton application are declared in APP_DIR/composer.json file (seebelow):

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

Page 45: Using Zend Framework 2

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-readablerepresentation of simple structures and nested associative arrays. Although JSONoriginates from Java, it is used in PHP and in other languages, because it is convenientfor 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. Thisinformation is optional, so you can even safely remove it, if you do not plan to publish your webapplication on Packagist catalog.

What is interesting for us now is the require key. The require key contains the dependenciesdeclarations for our application. We see that we require PHP engine version 5.3.3 or later andZend Framework 2.2.0 Release Candidate 1 or later.

The information contained in composer.json file is enough to locate the dependencies, downloadand install them into the vendor subdirectory. Let’s finally do that by typing the followingcommands from your command shell (replace APP_DIR placeholder with your actual directoryname):

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-updatethe 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 ifnot, it will warn you.

Page 46: Using Zend Framework 2

Zend Skeleton Application 27

If you look inside the vendor subdirectory, you can see that it now contains a lot of files. ZendFramework 2 files can be found inside the APP_DIR/vendor/zendframework/zendframework/librarydirectory (figure 2.3). Here you can encounter all the components that we described in Chapter1 (Authentication, Barcode, etc.)

Figure 2.3. Vendor directory

In some other frameworks, another (conventional) way of dependency installation isused. You just download the dependency library as an archive, unpack it and put itsomewhere inside of your directory structure (typically, to vendor directory). Thisapproach 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 isconfigure anApache virtual host. A virtual host termmeans that you can run several web-sites onthe same machine. The virtual sites are differentiated by domain name (like site.mydomain.comand site2.mydomain.com) or by port number (like localhost and localhost:8080). Virtual hostswork transparently for site users, that means users have no idea whether the sites are workingon the same machine or on different ones.

Page 47: Using Zend Framework 2

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 hostsinstead of putting the files inside of /var/www. This is because the public subdirectoryneeds 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. Withvirtual 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 youroperating system type. For example, in Linux Ubuntu it is located in/etc/apache2/sites-available/000-default file. Moreover, virtual host filename and content may look differently depending on Apache HTTP Server’s version.For OS- and server-specific information about virtual hosts, please refer to AppendixA.

Let’s now edit the default virtual host file to make it look like below (this example is applicableto 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>

Page 48: Using Zend Framework 2

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, Apachesends 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 underthe document root will be accessible by web-users. You should set this to be the absolutepath to skeleton application’s public directory. So, the directories and files inside public (likeindex.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 theweb 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 isan analog of a shortcut in Windows). The AllowOverride None directive forbids overriding anyrules using .htaccess files.

Lines 9-14 define rules for the document root directory (APP_DIR/public). These rules overridethe default rules mentioned above. For example, the AllowOverride All directive allows todefine 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 errorsoccurring in your site code. Line 23 defines the logging level to use (the warnmeans that warningsand 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-usersto entry script of your web-site. Please ensure that your web-server has mod_rewritemodule enabled. For instructions on how to enable themodule, please refer toAppendixA.

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 barcurrently 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 forbeginners on how to develop new ZF2-based applications.

Page 49: Using Zend Framework 2

Zend Skeleton Application 30

Figure 2.3. Zend Skeleton Application

2.6 Creating NetBeans Project

Now that we have the skeleton application set up andworking, wewill want to change somethingwith 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, weuse 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->NewProject…. 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 rightlist select Application with Existing Source (because we already have the skeleton application’scode). 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 ofPHP your code uses (PHP 5.3 or later). The PHP version is needed for the NetBeans PHP syntaxchecker which will scan your PHP code for errors and highlight them. Press the Next button togo to the next dialog page (shown in figure 2.6).

Page 50: Using Zend Framework 2

Zend Skeleton Application 31

Figure 2.4. Creating NetBeans Project - Choose Project Page

Figure 2.5. Creating NetBeans Project - Name and Location Page

Page 51: Using Zend Framework 2

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 (becausewe are using mod_rewrite, the actual path to your index.php file is hidden by Apache). If youare seeing the warning message like “Index File must be specified in order to run or debug projectin command line”, just ignore it.

Click the Finish button to create the project. When the helloworld project has been successfullycreated, 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 yourproject files are listed, and, in the right part of the window, you can see the code of the index.phpentry file.

Please refer to Appendix B for more NetBeans usage tips, including launching and interactivelydebugging ZF2-based web sites.

It’s time for some advanced stuff…

Congratulations!We’ve done the hardwork of installing and running the Zend SkeletonApplication, and now it’s time to have a rest and read about some advanced things inthe last part of this chapter.

2.7 Hypertext Access File (.htaccess)

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

Page 52: Using Zend Framework 2

Zend Skeleton Application 33

Figure 2.7. NetBeans Project Window

The .htaccess (hypertext access) file is actually an Apache web server’s configuration fileallowing 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]

Page 53: Using Zend Framework 2

Zend Skeleton Application 34

Line 1 tells Apache web server to enable URL rewrite engine (mod_rewrite). The rewrite enginemodifies the incoming URL requests, based on regular expression rules. This allows you to maparbitrary 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) requestsa file that exists in the document root directory, than to return the contents of that file as HTTPresponse. Because we have our public directory inside of the virtual host’s document root, weallow 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 whichdoes 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 existingfiles, so mod_rewrite returns the requested file paths. The URL in the third example points toa non-existent file htpasswd (which may be a symptom of a hacker atack), and based on ourrewrite 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 fileAPP_DIR/public/index.php

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

http://localhost/htpasswd File does not exist; returnAPP_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 exceptyours. For example, when you develop aweb site, you don’t want someone to see your incompletework. 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 tothe 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 mayneed to replace 127.0.0.1 with your external IP address.

Page 54: Using Zend Framework 2

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 IPaddress. The external IP address is the address bywhich other computers on the Internetmay access your site.

2.9 HTTP Authentication

You may want to allow access to your site to certain users. For example, when you aredemonstrating your web site to your boss, you will give her username and password for logginginto your site.

To allow access to your web site by username and password, you can modify the .htaccess fileand 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 thehtpasswd utility.

Line 2 defines Basic authentication method. The most common method is Basic. It is importantto be aware, however, that Basic authentication sends the password from the client to theserver unencrypted. This method should therefore not be used for highly sensitive data. Apachesupports 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 enterstheir 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 theuser. You can choose an arbitrary name, for example “admin”. The command will request theuser’s password and write the password to the file:

⁶http://www.whatismyip.com/

Page 55: Using Zend Framework 2

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 Authenticationand 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 virtualhosts. For each virtual host you need to specify a domain name (like site1.mydomain.com). Butif you currently don’t have a domain name, you can specify a different port instead (see theexample 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 toremember which port each site presents. To simplify this, you can define an alias for your website.

To do this, you should edit the hosts file. The hosts file is a system file which contains mappingsbetween IP addresses and host names. The hosts file contains lines of text consisting of an IPaddress 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 theexample below.

⁷http://httpd.apache.org/docs/current/howto/auth.html

Page 56: Using Zend Framework 2

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 ofremembering the port number.

In Linux, the hosts file is located in /etc/hosts. In Windows, the file is typicallylocated in C:\Windows\System32\drivers\etc\hosts. To edit the file, you need to bean administrator. Please also note that some anti-virus software may block changesto hosts file, so you’ll have to temporarily disable your anti-virus to edit the file, andenable it after.

2.12 Advanced Composer Usage

Earlier in this chapter, we have used Composer to install Zend Framework 2 library code. Nowlet’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 tellswhat 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 therequire 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 Candidate1), e.g.: “2.2.0rc2”, “2.2.0”, “2.2.1” and so on. In table 2.1, possible ways of specifying acceptableversions are presented:

⁸https://packagist.org/search/?q=zendframework

Page 57: Using Zend Framework 2

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 numbercan 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 callthis command, Composer will find, download and install the dependencies to your vendor

subdirectory.

Page 58: Using Zend Framework 2

Zend Skeleton Application 39

Is it safe to install dependencies with Composer?

Well, some people may be afraid of Composer-style dependency management, becausethey think someone can update the dependencies system-wide by mistake or intention-ally, causing the web application to break. Note, that Composer never installs thesesystem-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 containsactual 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 installedand 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 packagesare released. You will want to update your packages to keep your web site secure. You can dothat 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 rollback 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 versioncontrol system, like GIT or SVN. If you don’t use a version control system, make abackup copy of composer.lock before updating.

If you want to add new dependency to the application, you can either edit composer.jsonmanually, or issue require command. For example, to install Doctrine ORM module to yourweb site (to add the “doctrine/doctrine-module” package to the application dependencies), typethe following:

php composer.phar require doctrine/doctrine-module 2.*

The command above edits composer.json file, and downloads and installs the package. We willuse 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 alreadyseen howwe require “php>=5.3.3”. PHP package is a virtual package representing PHP itself. Youcan also require other stuff, like PHP extensions:

Page 59: Using Zend Framework 2

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 packagesfor 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 shouldbe 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, youshould store your composer.lock file under version control. The composer.lock file guaranteesthat everyone will install the same versions of dependencies as you have. This is useful indevelopment teams having more than one developer, because all developers should have thesame 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 isremoved from packagist. By the way, the same concept of dependency installation isused in Linux (remember APT or RPMmanager?), so did anyone see any Linux packagelost?

But there may be situations when you should store some dependent libraries under versioncontrol:

• If you have to make custom changes to third-party code. For example, assume you haveto fix a bug in a library, and you cannot wait for the library’s vendor to fix it for you (orif the library vendor cannot fix the bug). In this case, you should place the library codeunder version control to ensure your custom changes won’t be lost.

• If you havewritten a reusablemodule or library andwant to store it in the vendor directorywithout publishing it on Packagist.org. Because you don’t have an ability to install thiscode 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 isminimum, for some applications it is critical to be autonomous and not depend on packageavailability on Packagist.org.

Page 60: Using Zend Framework 2

Zend Skeleton Application 41

2.13 Summary

In this chapter, we have downloaded the Zend Skeleton Application project code from GitHubcode hosting and installed it using Composer dependency management tool. We’ve configuredthe Apache Virtual Host to tell the web server about location of the web site’s document rootdirectory.

The skeleton application demonstrates the recommended directory structure of a typical website. We have the public directory containing files publicly accessible by site users, includingthe index.php entry point file, CSS files, JavaScript files and images. All other directories ofthe application are inaccessible by site users and contain application configuration, data andmodules.

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 itfrom certain IP addresses only.

The Composer dependency manager is a powerful tool for installing the dependencies of yourweb site. For example, Zend Framework 2 itself can be considered as a dependency. All packagesinstallable by Composer are registered in a centralized catalog on the Packagist.org site.

Page 61: Using Zend Framework 2

3. Web Site OperationIn this chapter we will provide some theory on how a typical Zend Framework 2 basedapplication 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 stagespresent in an application’s life-cycle. You will also become familiar with such an important ZF2components as Zend\EventManager, Zend\ModuleManager and Zend\ServiceManager. If insteadof learning the theory, you want to have some practical examples, skip this chapter and referdirectly to Chapter 4.

ZF2 components covered in this chapter:

Component Description

Zend\Mvc Support of Model-View-Controller pattern. Separation of businesslogic from presentation.

Zend\Loader Implements the PHP class autoloading support.

Zend\ModuleManager This component is responsible for loading and initializing modules of theweb application.

Zend\EventManager This component implements functionality for triggering events and eventhandling.

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 ofa single library) in your program, the class names may conflict. This means you can encountertwo classes having the same name, resulting in PHP interpreter error. If you’ve ever programmedweb 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 betweendifferent 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 callednamespaces. The namespaces allow to solve the name collisions between code components, andprovide 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 (forexample, PHP classes Exception and DateTime belong to global namespace).

A real-world example of a namespace definition (taken from ZendMvc component) is presentedbelow:

Page 62: Using Zend Framework 2

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 thePHP engine that the text after the tag is a PHP code. In example above, when the filecontains only the PHP code (without mixing PHP and HTML tags), you don’t need toinsert the closing ?> tag after the end of the code. Moreover, this is not recommendedand may cause undesired effects, if you occasionally add some character after theclosing ?> tag.

In Zend Framework 2, all classes belong to top-level Zend namespace. The line 2 definesthe 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 theglobal 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;

Page 63: Using Zend Framework 2

Web Site Operation 44

Although the alias allows to use a short class name instead of the full name, its usageis optional. You are not required to always use aliases, and can refer the class by its fullname.

Every PHP file of your application typically defines the namespace (except index.php entryscript and config files, which typically do not). For example, the main module of your site, theApplication 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 providingthe implementation of such a behavior. This is also called a contract : by implementing aninterface, a class agrees to the contract terms.

In Zend Framework 2, interfaces arewidely used. For example, the Application class implementsthe 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();

}

Page 64: Using Zend Framework 2

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 definesmethods. However, the interface does not provide any implementation of its methods. In theApplicationInterface 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() forrunning the application.

In Zend Framework 2, by convention, interface classes should be named withInterface suffix, like ApplicationInterface.

A class implementing an interface is called a concrete class. The concrete Application classimplements the ApplicationInterface, which means it provides the implementation of themethods 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 animplementation of all methods of ApplicationInterface interface. The Application class canalso have additional methods, which are not part of the interface.

Page 65: Using Zend Framework 2

Web Site Operation 46

Graphically, the class relations are displayed using inheritance diagrams. In figure 3.1, thediagram for Application class is presented. The arrow points from the child class to the parentclass.

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 separatefile. This introduces the need of including the files.

For example, let’s assume we have the file named Application.php which contains the definitionfor the \Zend\Mvc\Application class from the previous section. Before you can create aninstance of the Application class somewhere in your code, you have to include the contentsof Application.php file (you can do this with the help of require_once statement, passing it thefull 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 Framework2 itself consists of hundreds of files, and it can be very difficult to load the entire library and allits dependencies this way. Moreover, when executing the resulting code, PHP interpreter willtake 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 PHPfunction spl_autoload_register() allows you to register an autoloader function. For complexweb 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 definedyet, it calls all the registered autoloader functions in turn, until either the autoloader functionincludes the class or “not found” error is raised. This allows for “lazy” loading, when PHPinterpreter processes the class definition only at the moment of class invocation, when it is reallyneeded.

To give you an idea of how an autoloader function looks like, below we provide a simplifiedimplementation of an autoloader function:

Page 66: Using Zend Framework 2

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 furtherrefer to as the class map autoloader.

The class map autoloader uses the class map for mapping between class name and absolute pathto PHP file containing that class. The class map is just a usual PHP array containing keys andvalues. To determine the file path by class name, the class map autoloader just needs to fetchthe 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 eachtime 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, youwill have to register a different custom autoloader function per each dependent library, whichis rather annoying (and actually this is an unneeded work). To resolve this problem, the PSR-0standard was introduced.

The PSR-0 standard¹ (PSR stands for PHP Standards Recommendation) defines the recommendedcode structure that an application or librarymust 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

Page 67: Using Zend Framework 2

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 bethe top-level namespace.

• Namespaces shouldmap to directory structure. Each namespace separator (‘\’) is convertedto 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 directorystructure:

/path/to/zend/lib

/Zend

/Mvc

Application.php

For the code conforming to the PSR-0 standard, we can write and register an autoloader, whichwe 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 tothe directory structure one-by-one, the function calculates the path to PHP file by transformingback-slashes (namespace separators) to forward slashes (path separators) and concatenating theresulting path with the absolute path to the directory where the library is located. Then thefunction checks if such a PHP file really exists, and if so, includes it with the require statement.

Page 68: Using Zend Framework 2

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 whenyou develop new code and add new classes to your application.

Zend Framework 2 conforms to PSR-0 standard, making it possible to use standardautoloading 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 requestmessage and sends it using HTTP protocol to the web server. The web server directs this HTTPrequest to your web application.

HTTP² (stands for Hyper Text Transfer Protocol) – a protocol for transferring data inthe form of hyper text documents (web pages). HTTP is based on the client-servertechnology: the client initiates a connection and sends a request to web server, and theserver waits for a connection, performs the necessary actions and returns a responsemessage back.

Thus, the main underlying goal of any web application is handling the HTTP request andproducing 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 webpage 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

Page 69: Using Zend Framework 2

Web Site Operation 50

• The starting line (line 1) specifies the method of the request (e.g GET or POST), the URLstring and HTTP protocol version.

• Optional headers (lines 2-8) characterize the message, the transmission parameters andprovide other meta information. In the example above, each row represents a single headerin the form of name:value.

• Optional message body contains message data. It is separated from the headers with ablank line.

The headers and the message body may be absent, but the starting line is always present in therequest, 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 therequest:

• The starting line (line 1) represents the HTTP protocol version, response status code andmessage (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 anempty line. The message body typically contains the HTML code of the requested webpage.

3.6 Site Entry Script

When the Apache web server receives an HTTP request from a client browser, it executes theAPP_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 serverdirects all HTTP requests to this script (remember the .htaccess file?). Having this singleentry script makes the web site more secure (comparing with the situation when youallow everyone to access all PHP files of your application).

Page 70: Using Zend Framework 2

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 definerelative file paths in your application:

Next, in line 10, PHP class autoloading is initialized. This allows to easily load any class eitherlocated in Zend Framework library or in your application without the need for require_oncestatement.

And finally, in line 13, an instance of Zend\Mvc\Application class is created. The application isinitialized 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\Applicationobject is created. The application’s “life” consists of several stages.

Zend Framework 2 uses the concept of event. One class can trigger an event, andother classes may listen to events. Technically, triggering an event means just callinganother class’ “callback” method. The event management is implemented inside of theZend\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 andreact 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 registeritself as a listener of further application events in its onBootstrap() callback method.

Page 71: Using Zend Framework 2

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 aroute 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 methodand 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 forrendering to Zend\View\Renderer\PhpRenderer class. The renderer class uses a view templatefile 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 customparameters specific to your application.

You can define the configuration parameters in two levels: either at the application level, or atthe module level. At the application level you typically define parameters which control thewhole app and are common to all modules of your application. At the module level, you defineparameters which affect only this module.

Page 72: Using Zend Framework 2

Web Site Operation 53

Some PHP frameworks prefer conventions over configuration concept, where most ofyour parameters are hard-coded and do not require configuration. This makes it fasterto develop the application, but makes it less customizable. In Zend Framework 2, theconfiguration over conventions concept is used, so you can customize any aspect ofyour application, but have to spend some time for learning how to do that.

3.8.1 Application-Level Config Files

TheAPP_DIR/config subdirectory contains application-wide configuration files. Let’s look at thissubdirectory 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 theapplication on start up for determining which services should be created in the service managerand 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 specifickey in that array. You can provide inline comments for the array keys to make it easier for othersto understand what each key means.

By convention, key names should be in lower case, and if the key name consists ofseveral words, the words should be separated by the underscore symbol (‘_’).

Page 73: Using Zend Framework 2

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 forsource files belonging to modules. Application modules that you develop are located underAPP_DIR/module directory, and third-party modules may be located inside the APP_DIR/vendordirectory.

And in line 26 we have the config_glob_paths key, which tells ZF2 where to look for extraconfig files. You see that files from APP_DIR/config/autoload which have global.php or local.phpsuffix, are automatically loaded.

Summing up, you typically use the main application.config.php file for storing the informationabout which modules should be loaded into your app and where they are located and how they

Page 74: Using Zend Framework 2

Web Site Operation 55

are loaded (for example, you can control caching options here). In this file you can also tune theservice manager. It is not recommended to add more keys in this file. For that purpose it is betterto use autoload/global.php file.

3.8.2 Application-Level Extra Config Files

“Extra” config files,APP_DIR/config/autoload/global.php andAPP_DIR/config/autoload/local.phpfiles define application-wide environment-agnostic and environment-dependent parameters,respectively. These config files are automatically loaded and recursively merged with themodule-provided config files, that’s why their directory is named autoload.

Having different config files in APP_DIR/config/autoload directory, you might have beenconfused 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 onthe concrete machine environment. For example, here you can store parameters whichoverride 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 usuallyhas a local database when developing and testing the web site. The developer thus willedit the local.php file and enter his own database credentials here. When you install yoursite to the production server, you will edit the local.php file and enter the credentials forthe “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 developerin your team then renames the local.php.dist file into local.php and enters his ownparameters. This local.php file should not be stored under version control, becauseit may contain confidential information like database credentials (username andpassword), 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 themodule.config.php file, in which you put your module-specific parameters.

Page 75: Using Zend Framework 2

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 themodule’s controllers, put information about routing rules for mappingURLs to your controllers, register controller plugins, and also register view templates and viewhelpers (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 configurationfiles from APP_DIR/config/autoload directory are merged into one big nested array, so everyconfiguration parameter becomes available to any piece of the web site. So, potentially, you areable to override some parameters specified by the modules.

You might also have seen the “combined” config file when installing PHP, where thereis the main php.ini file and several extra config files, which are included into the mainone. 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 youneed 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 servicemanager and load application modules. The data loaded from this config is stored aloneand not merged with other config files.

Page 76: Using Zend Framework 2

Web Site Operation 57

• Configuration files for each application module are loaded and merged. Modules areloaded in the same order as they are listed in the application.config.php file. If twomodulesstore (either intentionally, or by mistake) parameters in the similar-named keys, theseparameters may be overwritten.

• Extra config files from the APP_DIR/config/autoload folder are loaded and merged intoa single array. Then this array is merged with the module config array produced on theprevious stage, when loading the module configuration. Application-wide configurationhas 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 pointfor the module. This file provides the Module class with several “callback” methods. Below, thecontents 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 ),

Page 77: Using Zend Framework 2

Web Site Operation 58

29 );

30 }

31 }

The class Module belongs to the module’s namespace (for the main module it belongs to theApplication namespace).

The onBootstrap() method (lines 9-14) is called by the application on start up, during theBootstrap event, and allows to register event listeners for the module.

The getConfig() method (lines 16-19) tells the Zend Framework where the module.config.phpfile is located.

The getAutoloaderConfig() method (lines 21-30) tells the Zend Framework where to find thesource files for the module. This information is used for initializing the autoloader function forthe 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 2based application.

3.10.1 Composer-provided Autoloader

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

Recalling the index.php site entry script from a previous section, we see that it “includes” theautoloader initialization script file named init_autoloader.php. The init_autoloader.php scriptdoes one simple thing. It checks if Composer’s autoloader file is present, and just “includes” thatfile:

<?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 withComposer (including Zend Framework 2 classes).

Page 78: Using Zend Framework 2

Web Site Operation 59

By default, Composer uses the PSR-0 standard autoloader. To improve autoloadingperformance, especially for production environment, you may want to enable classmap 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 autoloadany PHP class in your modules. This is made with the getAutoloaderConfig() method of theModule class.

ZF2 has a special component named Zend\Loader, which contains implementations of the twocommonly-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 StandardAutoloaderand 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 registeringthe 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 discoveryalgorithm.

Page 79: Using Zend Framework 2

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 usethe standard autoloader.

In the skeleton application’s Module class, the getAutoloaderConfig() method provides theconfiguration for registering the source PHP files belonging to the module’s namespace withinthe standard autoloader:

<?php

namespace Application;

class Module {

public function getAutoloaderConfig()

{

return array(

'Zend\Loader\StandardAutoloader' => array(

'namespaces' => array(

__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,

),

),

);

}

}

In the abovemethod, we see that themodule tells ZF2 to use the Zend\Loader\StandardAutoloaderfor autoloading the module’s source files. The namespaces key contains key⇒value pairs, wherekey is the namespace, and the value is the absolute path to the directory containing the PHP filesimplementing the classes from that namespace. The __NAMESPACE__ constant expands into themodule’s namespace string (for example, to Application for themainmodule of the application),and the __DIR__ constant expands to directory path where the Module.php file is located.

Page 80: Using Zend Framework 2

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 areregistered with the standard autoloader are stored under the APP_DIR/module/Application/srcdirectory (“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 thisPHP class, we have to put it under the Application/Controller directory under the module’ssource directory.

At a first sight, the duplication of Application directory name in directory structurewould look strange and unneeded if you do not keep in mind the PSR-0 standardrequirements.

³IndexController class is the default controller for the skeleton web site. We will talk about controllers later in Chapter 4 when learningabout Model-View-Controller pattern.

Page 81: Using Zend Framework 2

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. Thisautoloader 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) containingkey⇒value pairs, where the key is the full class name, and value is the absolute path to PHPfile where the class is implemented. Or we can create the class map in an external PHP file andprovide 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 specialtool for this. The tool is a PHP script located in APP_DIR/vendor/bin/classmap_generator.phpfile. 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 anauthentication service responsible for logging in the site users, entitymanager service responsible

Page 82: Using Zend Framework 2

Web Site Operation 63

for accessing the database, event manager service responsible for triggering events and deliveringthem to event listeners, etc.

In Zend Framework 2, the ServiceManager class is a centralized repository for all applicationservices. The service manager is implemented in Zend\ServiceManager component, as theServiceManager class, which implements the ServiceLocatorInterface interface. The classdiagram 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 ofZend\Mvc\Application class). The standard services available through service manager arepresented in table 3.1. This table is incomplete, because the actual number of services registeredin 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 filesmerged with autoload/global.php and autoload/local.php.

EventManager Allows to retrieve the singleton of Zend\Mvc\EventManager class. Theevent manager allows to send (trigger) events and attach event listeners.

ModuleManager Allows to retrieve the singleton of Zend\Mvc\ModuleManager class. Themodule manager is responsible for loading application modules.

Request The singleton of Zend\Http\Request class. Represents HTTP requestreceived from client.

Response The singleton of Zend\Http\Response class. Represents HTTP response thatwill be sent to client.

Router The singleton of Zend\Mvc\Router\Http\TreeRouteStack. Performs URLrouting.

ServiceManager Service manager itself.

ViewManager The singleton of Zend\Mvc\View\Http\ViewManager class. Responsible forpreparing the view layer for page rendering.

A service is typically an arbitrary PHP class, but not always. For example, when ZF2 loads theconfiguration files and merges the data into nested arrays, it saves the arrays in the service

Page 83: Using Zend Framework 2

Web Site Operation 64

manager as a couple of services (!): ApplicationConfig and Config. The first one is the arrayloaded from application-level configuration file application.config.php, and the latter one is themerged array frommodule-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 servicemanager is itself registered as a service. Moreover, the Application class is also registered as aservice.

An important thing you should note about the services is that they are typically storedin a single instance only (this is also called the singleton pattern). Obviously, you don’tneed the second instance of the Application class or, say, the event manager (in thatcase 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 ServiceLocatorInterfaceinterface. As its name suggests, this interface defines the minimum needed methods for locatingand 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 theservice 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 isnot registered.

You can retrieve a service by its name at any place of your application with the help of theservice locator’s get() method. This method takes a single parameter representing the servicename. Look at the following example:

Page 84: Using Zend Framework 2

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 fromsomewhere. Looking ahead, let’s say that typically, you will do that in your controllerclasses with the help of getServiceLocator() method. We will learn about controllerclasses later in Chapter 4.

3.11.2 Canonical Service Names

Different services can use different naming styles: the same currency converter service may beregistered under the different names: CurrencyConverter, currency_converter and so on. Tointroduce some uniform naming convention, the service manager internally uses the canonicalservice 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, theservice manager uses its canonicalizeName()method, which takes the usual service name stringand produces the canonical name string.

To get the list of all canonical service names registered within the service manager, you can useits getCanonicalNames()method, which returns an array of string pairs. The first element of thepair is the canonical service name. The second one is the usual name of the service (the nameunder 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 theservicemanager. To register a service, you use the setService()method. Let’s create and registerthe currency converter service class, which will be used, for example, on a shopping cart page toconvert EUR currency to USD:

Page 85: Using Zend Framework 2

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 theservice manager using the setService()method (we assume that the $serviceManager variableis of type Zend\ServiceManager\ServiceManager class, and that it was declared somewhereelse).

The setService() method takes two parameters: the service name string, and the serviceinstance. The service name should be unique within all other possible services. If you are tryingto register the service name which is already present, the setService() method throws anexception. But sometimes you want to override the service with the same name (to replace itby the new one). For this purpose, you can use the setAllowOverride() method of the servicemanager:

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 whetherto 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 yourapplication with the help of the service manager’s get()method. Look at the following example:

Page 86: Using Zend Framework 2

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 instancebefore you really need it. If you never use the service, the service instantiation will onlywaste the time and memory. To resolve this issue, the service manager provides you with thesetInvokableClass() 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 insteadof passing its instance. With this technique, the service will be instantiated by the servicemanager only when someone calls the get('CurrencyConverter') method. This is also calledlazy loading.

3.11.5 Registering Factories

Sometimes, service instantiation is more complex than just creating the service instance withnew operator. You may need to pass some parameters to the service’s constructor or invoke someservice methods just after construction. This complex instantiation logics can be encapsulatedinside of a factory. A factory is a PHP class responsible for creating new objects. The factoryclass 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 thecreateService()method returning the instance of a single service. The service locator is passedto the createService() method as a parameter; it can be used during the construction of theservice for accessing other services.

As an example, let’s write a factory for our currency converter service (see the code below). Wedon’t use complex construction logics for our CurrencyConverter service, but for more complexservices, you may need to use one.

Page 87: Using Zend Framework 2

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 servicesshould 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() andcreateServiceWithName(). The first one is needed to test if the factory can create the servicewith the certain name, and the latter one allows to actually create the service. The methodstake three parameters: service locator, canonical service name and usual service name (as it wasrequested from service locator by the client).

Comparing to usual factory class, the difference is that the usual factory class can create only asingle 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’ssetAlias() method:

Page 88: Using Zend Framework 2

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’sget() method.

3.11.7 Service Manager Configuration

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

If you are putting this key in a module-level configuration file, be careful about thedanger of name overwriting during the configs merge. Do not register the same servicename 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 ),

Page 89: Using Zend Framework 2

Web Site Operation 70

27 ),

28

29 //...

30 );

In the example above, you can see that the service_manager key may contain several subkeysfor 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 servicewill be instantiated using lazy loading;

• the factories subkey (line 15) allows for registering a factory, which is able to createinstances of a single service;

• the abstract_factories (line 19) can be used for registering abstract factories, which areable 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 theApplication 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 andlogger service) and an alias is registered for the translator service. We will become familiar withthese 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 ofapplications which use many third-party components. The namespaces allow to solve the name

Page 90: Using Zend Framework 2

Web Site Operation 71

collisions between code components, and provide you with the ability to make the long namesshorter.

The class autoloading makes it possible to use any PHP class in any library installed withComposer without the use of require_once statement.

Most of Zend Framework 2 components require configuration. You can define the configurationparameters 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 HTTPresponse typically containing the HTML code of the requested web page. When Apache webserver receives an HTTP request from a client browser, it executes the index.php file, which isalso called the site’s entry script. On every HTTP request, the Zend\Mvc\Application object iscreated, 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 servicemanager is a centralized repository for all the application services. A service is typically a PHPclass, but in general it can be a variable or an array, if needed. In your code, you retrieve theservices from the service manager with the help of the service locator interface.

Page 91: Using Zend Framework 2

4. Model-View-ControllerIn this chapter, you will learn about the models, views and controllers (MVC). The webapplication uses the MVC pattern to separate business logic from presentation. The goal of thisis 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 andcommon 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 ZendFramework.

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 toreproduce yourself. It may be difficult for a novice to write code without mistakes. If you arestuck or can not understand why your code does not work, you can download the completeHelloWorld web application from GitHub code hosting. The examples from this chapter are mostlythe part of this sample application.

To download the Hello World application, visit this page¹ and click the Download ZIP button todownload the code as a ZIP archive (see figure 4.1). When download is complete, unpack thearchive to some directory.

Then navigate to the helloworld directory containing the complete source code of the HelloWorld example:

/using-zend-framework-2-book

/helloworld

...

The Hello World is a complete web site which can be installed on your machine. To install theexample, you can either edit your default Apache virtual host file or create a new one. Afterediting 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

Page 92: Using Zend Framework 2

Model-View-Controller 73

4.2 Separating Business Logic from Presentation

A typical web site has three kinds of functionality: code implementing business logic, codeimplementing user interaction and code rendering HTML pages (presentation). Before PHPframeworks, programmers usually merged these three types of code in a single big PHP scriptfile, which made it a pain to test and maintain such a code, especially when you write a largeweb 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 organizeyour classes in a better manner, to make them easy to maintain.

In MVC, classes implementing your business logic are called models, code snippets renderingHTML pages are called views, and the classes responsible for interacting with user are calledcontrollers.

Views are implemented as code snippets, not as classes. This is because views aretypically very simple and contain only the mixture of HTML and inline PHP code.

Page 93: Using Zend Framework 2

Model-View-Controller 74

The main objective of the MVC concept is to separate the business logic (models) from itsvisualization (views). This is also called the separation of concerns, when each layer does itsspecific 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 visualrepresentations for the same models.

To better understand how this works, lets remember that any web site is just a PHP programreceiving an HTTP request from the web server, and producing an HTTP response. Figure 4.2shows how an HTTP request is processed by the MVC application and how the response isgenerated:

• First, the site visitor enters an URL in his web browser, for example http://localhost, andthe 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 scriptdoes is creating the Zend\Mvc\Application class instance.

• The application uses its router component for parsing the URL and determining to whichcontroller to pass the request. If the route match is found, the controller is instantiated andits 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 andcalls their methods.

• Model classes use business logic algorithms to process the input data and return theoutput data. The business logic algorithms are application-specific, and typically includeretrieving data from database, managing files, interacting with external systems and soon.

• The result of calling the models are passed to the corresponding view script for therendering 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 HTMLoutput. In the next sections, we describe them in more details.

Page 94: Using Zend Framework 2

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 inputfrom HTTP request and uses the model(s) and the corresponding view to produce the necessaryHTTP response.

Controllers belonging to module typically reside in the Controller subdirectory of module’ssource directory (shown in figure 4.3).

Page 95: Using Zend Framework 2

Model-View-Controller 76

Figure 4.3. Controller directory

Zend Skeleton Application provides you with the default implementation of IndexControllerclass. The IndexController is typically the main controller class of the web site. Its code ispresented 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 inApplication\Controller namespace.

A controller is a usual PHP class derived from the AbstractActionController base class (line8).

By default, the controller class contains the single action method called indexAction() (see lines11-14). Typically, you will create other action methods in your controller classes.

ZF2 automatically recognizes the action methods by the Action suffix. If a controllermethod’s name does not have that suffix, it is considered as a usual method, not anaction.

Page 96: Using Zend Framework 2

Model-View-Controller 77

As its name assumes, an action method performs some site action, which typically results indisplaying a single web page. Index controller usually contains action methods for site-wideweb 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 yoursite.

IndexController::aboutAction() The “about” action displays the About page ofthe site. The About page contains contact andcopyright information.

IndexController::contactUsAction() The “contactUs” action displays the Contact Uspage of the site. The Contact Us page displaysthe 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 yourcontroller 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 therepresentation of HTTP request data.

getResponse() Retrieves the Zend\Http\PhpEnvironment\Response objectallowing to set data of HTTP response.

getServiceLocator() This method returns the Zend\ServiceManager\ServiceLocatorinterface, allowing to access all services registered in theweb application.

Page 97: Using Zend Framework 2

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 representsthe 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 pluginwith 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 HTTPrequest and response data, and provides you with the access to the service manager and to theevent manager. It also gives you an ability to register and call controller plugins (we will learnabout 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 (thedata like GET and POST variables, cookies, HTTP headers and so on). For this purpose, ZendFramework 2 provides you with Zend\Http\Request and Zend\Http\PhpEnvironment\Responseclasses, 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 HTTPrequest data. In table 4.3, you can find themost widely usedmethods of the Request class togetherwith 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.

Page 98: Using Zend Framework 2

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 postparameters or a single post parameter.

getCookie() Returns the Cookie header.

getFiles($name, $default) Returns the parameter container responsible for fileparameters or a single file.

getHeaders($name, $default) Returns the header container responsible for headersor 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) forthis HTTP request.

fromString($string) A static method that produces a Request object from awell-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 convenientmethods 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” fromGET. If such a variable does not present, the default value “default_val” is returned. The defaultvalue is very convenient, because you don’t have to use the isset() PHP function to test if thevariable exists.

In line 5 we use the fromPost()method to retrieve the variable from POST (line 5). The meaningof 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 requestdata.

Page 99: Using Zend Framework 2

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 withthe help of getResponse()method provided by AbstractActionController base class. Table 4.4contains 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, fromdatabase), you would do something with that data (typically you will process the data with yourmodel layer) and return the data from the action method.

You can see that the indexAction() method of the Index controller returns an instance of theViewModel class. The ViewModel class is some kind of a variable container. All variables passedto 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 IndexControllerclass, which we will call the aboutAction(). The “about” action will display the About page ofour site. In the action method, we will get the current version of the Zend Framework with theZend\Version component, and return the resulting variables for rendering in a view with thehelp of ViewModel object:

Page 100: Using Zend Framework 2

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 youneed 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 literalrepresentation of the version of Zend Framework currently installed in our machine (at themoment 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 fromthe Internet (note, that this requires the Internet connection and may take a few seconds tocomplete).

In line 13, we use the compareVersion() static method to compare our current version with thelatest one (the comparison result will be -1 if our current version is newer, 0 if both equal, or 1 ifours is older).

In lines 17-21, we pass the variables we’ve created to the constructor of the ViewModel object asan associative array. The array keys define the names of the variables which on return will beaccessible to view script.

The ViewModel class provides several methods that you can additionally use to set variables toViewModel and and retrieve variables from it. The table 4.5 provides the methods summary:

Page 101: Using Zend Framework 2

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 thevariabledoes not exist).

setVariable($name, $value) Sets a variable.

setVariables($variables, $overwrite) Sets a group of variables, optionally overwriting theexisting 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.phpconfiguration 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 shouldregister your controllers here. To register a controller class, you add the line in form of key⇒valuepair. The key should be the unique ID of the controller, like Application\Controller\Index, andvalue should be the fully qualified class name of the controller, likeApplication\Controller\IndexController.

By registering your controller under the invokables subkey, you tell Zend Frameworkthat it can invoke the controller by instantiating it with the new operator. This is themost simple way of instantiating the controller. As an alternative, you can register afactory to create the controller instance, in that case you would register your controllerunder the factories subkey.

Page 102: Using Zend Framework 2

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 allactions to IndexController. The Index controller is used for defining the actions which workfor your entire site. It is recommended to create new controller class for each model (or for mostimportant ones) of your business logic domain.

For example, you can create UserController to manage users of your site. This controller wouldhave the default “index” action for displaying the page with all users, “add” action for adding anew 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 purchasesof your products and implementing the shopping cart, DownloadController and its actions tomanage 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 tocreate a custom base class, say BaseController, and derive other controllers from thatbase class. This way is used in some PHP frameworks, but not in Zend Framework2. From ZF2 creators’ point of view, plugins are better solution, because they useclass composition ², which provides better flexibility comparing to class inheritance.You register your plugin controller and it automatically becomes accessible from allcontrollers 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’vealready 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 addressesfrom inside controllers.

Layout Gives access to layout view model for passing data tolayout template.

Identity Returns the identity of the user who has logged into theweb site.

²Composition is a relationship between two classes that is best described as a “has-a” and “whole/part” relationship. The owner class containsa reference to another class (plugin). The owner is responsible for the lifetime of the object it holds.

Page 103: Using Zend Framework 2

Model-View-Controller 84

Table 4.6. Standard Controller Plugins

Standard Plugin Class Description

FlashMessenger Allows to define “flash” messages which are stored insession and can be displayed on a different web page.

Redirect Allows to redirect the request to another controller’saction method.

PostRedirectGet Redirects the POST request, converting all POST variablesto 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 allowedto 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 intheir 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){

// ...

}

}

Page 104: Using Zend Framework 2

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 inthis 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 produceHTML 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 theyusually 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):

Page 105: Using Zend Framework 2

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, becausethey are not usual PHP classes and do not need to be resolved by a PHP classautoloading feature. View templates are resolved by the special ZF2 class called viewresolver, 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 thecontroller’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, andthe information about the current Zend Framework version.

To create the view template file, in your NetBeans window, navigate to view/application/indexdirectory (see figure 4.6), and right click on the “index” directory name. From the context menuthat 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 theFinish button.

The about.phtml view template file will be created and displayed in the right pane of NetBeanswindow. In that file, enter the following:

Page 106: Using Zend Framework 2

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 pagewith several PHP code fragments. A viewscript just renders the data you pass to it with a ViewModel variable container. For example, in

Page 107: Using Zend Framework 2

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

Page 108: Using Zend Framework 2

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. Viewstypically do not modify the data you pass from controller. For example, a view can usethe model you pass to it to walk through database table rows and render the items toan 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 aview. You can invoke view helpers from any view template. With view helpers, you can createreusable 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” thefunctionality of view templates.

ZF2 provides many standard view helpers out of the box. In the table 4.7, some of them arepresented with a brief description:

Page 109: Using Zend Framework 2

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 addressesfrom inside view templates.

ServerUrl Retrieves the current request’s URL.

Doctype Helper for setting and retrieving the doctype HTML elementof the web page.

PageTitle Helper for setting the title HTML elementof 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 forinclusion in HTML body section.

Identity View helper to retrive the authenticated user’s identity.

FlashMessenger Allows to retrieve the “flash” messages stored insession.

To demonstrate the usage of a view helper, below we will show how to set a title for a webpage. Typically, it is required to give a different title per each web page. You can do this withthe HeadTitle view helper. For example, you can set the title for the About page by adding thefollowing 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”) asthe argument. The HeadTitle view helper internally sets the text for the <title>HTML elementof your web page. Then, if you open the About page in your web browser, the page title will looklike “About - Zend Skeleton Application” (see the figure below for an example):

Page 110: Using Zend Framework 2

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

4.10 View Template Names & View Resolver

When you return data with the ViewModel variable container from your controller’s actionmethod, Zend Framework somehow knows the name of the corresponding view template file andits location. For example, for your IndexController’s aboutAction()method, ZF2 automaticallyuses 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 allowsto specify which view template should be used for page rendering. The summary of methodsprovided 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()methodreturns the view template name currently set for the view model.

Page 111: Using Zend Framework 2

Model-View-Controller 92

The following code example shows how you can call the setTemplate() method from yourIndexController class’ indexAction() method to force ZF2 to use the about.phtml viewtemplate 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 thename of the view template name as its argument. The view template name is actually a relativepath 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’tdo that, ZF2 will determine the view template name automatically by concatenating the currentmodule name, controller name and action method name.

When Zend Framework has the template name, it only remains to determine the absolute pathto the corresponding .phtml file. This is also called the view template resolving. View templatesare 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 asoutput. 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 templatefile by its name. This way is fast, but you have to maintain some template map array andupdate it each time you add a new view script.

• The template path stack resolver assumes that the view template name can be mappedto directory structure. For example, “index/about” template name maps to APP_DIR/-module/Application/view/application/index/about.phtml. This way is simpler, because youdon’t have to maintain any maps.

View resolver settings are stored inside of your module.php.config file under the view_managerkey:

Page 112: Using Zend Framework 2

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. Bydefault, there are several “standard” view templates, which are resolved this way: the index pagetemplate, the layout template (we will talk about it in Chapter 6) and error templates (we will talkabout them a little bit later). These standard pages are served with this type of resolver, becauseit is fast.

The template path stack resolver’s settings are stored under the template_path_stack key. Youcan 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 automaticallyfind the template.

The template map resolver and template path stack resolver work in pair. First, the fast templatemap resolver tries to find the template view in its array map, and if the page is not found, thetemplate 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 theResponse object from the the controller’s action.

For example, let’s create a DownloadController class, and add the “file” action, which wouldallow site users to download files from your web site. This action does not need a correspondingfile.phtml view template, because it dumps file contents to PHP standard output stream and exits.

Add the DownloadController.php file to Controller directory of Applicationmodule, then put thefollowing code into the file:

Page 113: Using Zend Framework 2

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

Page 114: Using Zend Framework 2

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 slashesfrom 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.phpfile:

<?php

return array(

// ...

'controllers' => array(

'invokables' => array(

// ...

'Application\Controller\Download' =>

'Application\Controller\DownloadController'

),

),

// ...

);

To see how the file download works, createAPP_DIR/data/download directory and put some textfile named sample.txt in it. Then open yourweb browser and type the URL “http://localhost/application/download/file?name=sample.txt”in your browser’s navigation bar and press the Enter key. The browser will download thesample.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 errortemplates. There are two error templates: error/404 which is used for “404 Page Not Found”

Page 115: Using Zend Framework 2

Model-View-Controller 96

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

Figure 4.10. 404 Error Page

Themodule.config.php file contains several parameters under the view_manager key, which youcan 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',

Page 116: Using Zend Framework 2

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 anunhandled 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 tofalse in production systems, because you don’t want site visitors see the details abouterrors in your site. However, you will still be able to retrieve the detailed informationfrom Apache’s error.log file.

4.13 Models

Amodel is a PHP class which contains the business logic of your application. The business logicis the “core” of your web site which implements the goal of site operation. For example, if youimplement an E-shop web site, you will have models implementing the product catalog and theshopping cart.

In general, the termmodel 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 systemand ignore all others. For example, the most important properties of the site visitor (from website 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, thecurrency 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 youcould 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):

Page 117: Using Zend Framework 2

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 logicdomain. This is also called the “Domain Driven Design” (or shortly, DDD). The personwho 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. Forexample, a User entity always has a unique login property, and you can identify the user bythat 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 systemor 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) {

Page 118: Using Zend Framework 2

Model-View-Controller 99

20 $this->login = $login;

21 }

22

23 //...

24 }

In lines 5-10, we define Usermodel’s properties. The best practice is to define the properties usingthe private access type, and make them available to the caller through getter and setter publicmethods (like getLogin() and setLogin(), etc).

Model’s behavior methods are not limited by getters and setters. You can createother methods which manipulate with model’s data. For example, you can define thegetFullName() conveniencemethod, whichwould 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. Avalue object is usually a small class identified by all of its attributes. It does not have an identifierattribute. Value objects typically have getter methods, but do not have setters (value objects areimmutable).

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 }

Page 119: Using Zend Framework 2

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 (themodel is immutable).

In lines 24-33 we have the static method convert(), which is designed to convert money amountto another currency using some simple currency conversion algorithm. Instead of modifying themoney 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 somesituations you will have a value object which is “mutable” (have setter methods), andsometimes, you will have only getter methods for some of entity properties. Do notdwell on the idealistic recommendations provided in this chapter, instead adapt yourmodels as your intuition advices to you.

4.14.3 Services

Service models usually encapsulate some business logic functionality. Services typically do nothave state (internal properties), instead they manipulate other entity models and value objects.Services usually have easily recognizable names ending with “er” suffix, like FileUploader orUserManager.

Below, an example of Mailer service is presented. It has the sendMail()method which takes anEmailMessage value object and sends an E-mail message using standard PHP mail() function:

Page 120: Using Zend Framework 2

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;

}

}

Page 121: Using Zend Framework 2

Model-View-Controller 102

4.14.4 Factories

Factories are usually being designed to instantiate other models. In the simplest cases youcan create an instance of a class without any factory, just by using the new operator, butsometimes class creation logic might be very complex, and you encapsulate it inside of a factoryclass. 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 imageis 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’screate a fictitious CaptchaFactory class, whose only goal is to create different CAPTCHA imagesbased 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, aUserRepositorymay represent a database table and provide methods for retrieving User entities.You typically use repositories when storing entities in a database. With repositories, you canencapsulate 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 aboutDoctrine library.

³http://www.google.com/recaptcha

Page 122: Using Zend Framework 2

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 modeltype, but as soon you improve your skills, you will be able to do that intuitively. Justremember that model types improve the structure of your domain models.

When writing your own application having specific model domain, you may be confused whentrying 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 todetermine 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 serviceor 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, youwill notice that it utilizes DDD principles as well, so using DDDmakes your applicationwell-organized.

Page 123: Using Zend Framework 2

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 hugeand models small, which in turn makes it difficult to test and support your application. Thissection’s goal is to give you a general understanding of what code may be placed in a controllerclass, what code may be placed in a view template, and what code may be placed in a modelclass.

4.16.1 Skinny Controllers

The idea behind the term “skinny controller” is that typically, in your controller classes, you putonly 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 inview 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 ofmoney 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

Page 124: Using Zend Framework 2

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 andcan 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 (orinvalid), 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. Morecomplex parameter checks should be performed inside of the model class.

• Creates the CurrencyConvertermodel (line 14) and passes themoney amount to themodelby calling its convertEURtoUSD()method. Themethod then returns the converted amount.

• Constructs the ViewModel variable container and passes the resuting data to it (line17). This variable container can be further accessed in the corresponding view templateresponsible 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 yourapplication should be put into model classes. In a properly designed Model-View-Controllerapplication, models look “huge”. A model class may contain the code which:

• Performs complex data filtering and validation. Because the data that you retrieved incontroller is passed to your application from an outside world, in your model, you have totake a lot of effort to verify the data and ensure the data will not break your system. Thisresults in a secure web site resistent to hacker attacks.

• Performs data manipulation. Your models should manipulate the data: e.g. load the datafrom database, save it to database and transform the data. Models are the right place forstoring 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. Thecontroller’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 varydepending on the user request, and it is better to put it in a view template.

Page 125: Using Zend Framework 2

Model-View-Controller 106

If you follow this principles, you will encounter that your models are easy to test, because theyhave clearly identified input and output. You can write a unit test which passes some test datato 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 thisan important logic that needs to be carefully tested? If the answer is yes, you should put the codein 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 possibleto 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. Thisallows 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 substitutedwithout modifying the business logic of your application. For example, you can easily changethe 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 fromthe 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 forcode reusability and separation of concerns.

A controller is a mediator between the application, models and views: it gets input from HTTPrequest and uses the model(s) and the corresponding view to produce the necessary HTTPresponse. A controller is a usual PHP class containing action methods.

Views are simple HTML+PHP code snippets producing HTML output returned by the web serverto 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 logicis the “core” of your web site which implement the goal of site operation. Models can accessdatabase, manipulate disk files, connect to external systems, manipulate other models and so on.

Page 126: Using Zend Framework 2

5. URL RoutingWhen a site user enters a URL in a web browser, the request is finally dispatched to controller’saction. In this chapter, we will learn about how ZF2-based application maps page URLs tocontrollers and their actions. This mapping is accomplished with the help of routing. Routingis 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 anHTTP 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 (likesite1.yourserver.com). Optional path segments follow the host name. So if you have the pathpart “/path/to/page” then “path”, “to”, and “page” would each be a URL segment. Next, afterthe 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. Thisencoding ensures that the URL contains only “safe” characters from the ASCII ¹ table. If a URLcontains unsafe characters, they are replaced with a percentage character (‘%’) followed by twohexadecimal 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 Englishalphabet. It encodes 128 characters: digits, letters, punctuation marks and several control codes inherited from Teletype machines.

Page 127: Using Zend Framework 2

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. Forexample, 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 identifythe rule. The type defines the name of the PHP class which implements the algorithm used forcomparing the URL string. The options is an array that includes the route string which shouldbe 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 thencompares the URL with the route, and if the URL string matches the route, returns severalparameters, 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 URLstring. 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). Theseroute 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 specificsegment (or several segments) of a URL. The Method route type is matched against the HTTPmethod (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 queryparameters from your URL with the Params controller plugin (see the Retrieving Datafrom HTTP Request section in Chapter 4).

Page 128: Using Zend Framework 2

URL Routing 109

5.3 Combining Route Types

Routes may be combined with the help of “aggregate” route types (shown in table 5.2). Thecompound 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. TheSimpleRouteStack allows to organize different routing rules in a priority list. The TreeRouteStackallows 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 (wewill 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 behaviorof the simple route stack (allows to organize routes in priority list) and extends it (allows toorganize routes in subtrees). The Part and Chain classes are derived from TreeRouteStack classand are used internally by the TreeRouteStack for building subtrees and chains of child routes.

Page 129: Using Zend Framework 2

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. Thisis because routing is also used for mapping shell commands to controllers in consoleapplications. Thus, console route classes will live in Zend\Mvc\Router\Console, whilethe SimpleRouteStack compound route type will be used for both HTTP routing andconsole routing.

5.3.1 Simple Route Stack

The SimpleRouteStack allows to combine different route types in a priority list. For an exampleof such a list, look at the route stack in the left part of figure 5.3. The example list contains severalLiteral routes and several Segment routes.

When matching against the HTTP request, the SimpleRouteStack walks through the list ofroutes and tries to match each route in turn. Each route in the list has a priority; the routeswith the higher priority are visited first. The lookup is finished once some route matches theHTTP 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 organizethe routes in a priority list, plus it provides an ability to nest routes in subtrees and chains. Anexample tree route stack is presented in the right part of figure 5.3. The list contains of oneLiteral route, a chain of Literal and Segment routes, and a subtree consisting of two branches:

Page 130: Using Zend Framework 2

URL Routing 111

a branch containing a single Segment route, and a branch consisting of Scheme, Hostname andSegment routes.

The tree route stack performs request matching in the following way. It walks through its prioritylist items (denoted by dashed lines in figure 5.3), starting from high-priority routes. If a certainitem is a Chain route or a Part route, it processes such a nested route from its parent route tochildren. 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 ismatched against the first segment (or several segments) of the URL, its child is matched againthe 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 instructionsfor ZF2 on how to do that. The routing configuration for a module is stored inmodule.config.phpconfiguration 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 )

Page 131: Using Zend Framework 2

URL Routing 112

12 ),

13 );

Above, in line 4we have the router key, under which there is the routes subkey (line 6), containingthe 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 TreeRouteStackis used by default.

You can use the optional default_params key (line 9) to define the default parameters for allroutes at once. However, you typically do not use this key and define the defaults on a per-routebasis.

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 inlower case, like “home” or “about”. The type key specifies the route class name. It can be eitherthe 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 thepriority key, the routes will be visited in the LIFO ² order.

Routes having equal priority will be visited in the LIFO order. Thus, for the bestperformance, 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 followingsections 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 addyour 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.

Page 132: Using Zend Framework 2

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_routeskey 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 Chainroute types, because (for your convenience) they are used by the ZF2 automaticallywhen it encounters the child_routes and chain_routes keys in your routing config-uration.

5.4.3 Default Routing Configuration in Zend SkeletonApplication

Now that you know how to configure routes and organize them in a compound structures, let’slook at the real life example. In a fresh Zend Skeleton Application, the routing configurationlooks like below:

Page 133: Using Zend Framework 2

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

Page 134: Using Zend Framework 2

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 hasthe child rule named “default”. This compound route also has a name: it is the concatenation ofall names of routes it consists of (“application/default”).

The may_terminate parameter (line 29) of the parent route defines whether the child route mustalways match or the child route is optional. If the may_terminate parameter is true and theend of the URL is reached, the child route is ignored and the route is treated as matching. Ifthe may_terminate parameter is false and the last segment of the URL is reached, the route istreated as non-matching.

In the next sections, we will provide some examples on how to use the route types in your website.

5.5 Literal Route Type

With Literal route type, the route match is achieved only when you have the exact literal matchof the route string against the URL path. You typically use the Literal type for URLs which shouldbe short and memorable, like ‘/about’ or ‘/news’.

Below, the definition of the route named “home” is presented. The “home” route is usuallymapped to the “index” action of the IndexController and points to the Home page of yoursite:

Page 135: Using Zend Framework 2

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 algorithmis implemented in the Zend\Mvc\Router\Http\Literal class. You can either specify the full classname or its short alias (Literal).

Line 4 defines the route string to match against the URL path (the forward slash ‘/’ means theempty URL part). Because we have the literal route type, the route match is achieved only whenyou 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 routematches. The controller and action parameters define the controller and controller’s actionmethod 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 pagewe’ve created earlier in the Views section of Chapter 4. To create the route, add the followinglines 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 theAboutpage.

5.6 Segment Route Type

The Segment route type allows for matching the route string against one or several URL pathsegments.

Page 136: Using Zend Framework 2

URL Routing 117

If you look at the module.php.config file, you can see the Segment route type is usedinside 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, andit 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 actionwhich will generate a simple barcode image. Barcodes are widely used in supermarkets foroptically recognizing goods in your shopping cart. The barcodes may be of different types andhave 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 variablesegments by using “wildcards”. We have three segments: barcode, :type and :label. Thebarcode segment is constant, while the latter two are wildcards (wildcard’s name should startwith a colon).

You specify how a wildcard should look like inside of the constraints subkey (lines 5-8). Wedefine the regular expression [a-zA-Z][a-zA-Z0-9_-]* which constraints our :type wildcardto 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 anyallowed character (either letter, digit, underscore or minus sign character).

Optional segments can be enclosed in square brackets. In our example, we have both the :typeand :label segments as optional.

In lines 9-12, we define the defaults, the parameters that will be returned by the router. Thecontroller and action defaults specify which controller and action method to execute on routematch.

Next, we add the barcodeAction() method into the IndexController class:

Page 137: Using Zend Framework 2

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 labelwildcards from route. We do that with thehelp of Params controller plugin’s fromRoute() method. Analogous to fromQuery() method, ittakes 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 thelabel 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 barcodeimages. 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):

Page 138: Using Zend Framework 2

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 ofthe PHP engine installed and enabled. Please refer to Appendix A for instructions onhow to do that.

Because we have the wildcards in the route, you can pass the type and label parameters of thebarcode image in the URL. Below, several URL examples are provided (corresponding barcodesare presented in figure 5.7):

a. http://localhost/barcode/code39/HELLO-WORLDb. http://localhost/barcode/leitcode/12345c. http://localhost/barcode/identcode/98765453212d. http://localhost/barcode/postnet/123456e. http://localhost/barcode/planet/1234567890123f. http://localhost/barcode/upca/12345678901g. http://localhost/barcode/code128/ABCDEFh. 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.)

Page 139: Using Zend Framework 2

URL Routing 120

5.7 Regex Route Type

The regular expression route type (Regex) is useful if you have URLs which can be matchedagainst a regular expression.

For example, assume you want to create a simple documentation system for your web site. Thedocumentation 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 codeplus several PHP inline fragments. For such simple pages you do not need to createseparate controller actions. All “static” pages can be served by the single controlleraction.

Let’s implement the route which will serve the “static” pages of the site. Because “static” pagesare simple, you typically won’t need to add per-page action methods to the controller. All pageswill 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 theRegex 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 soon. The expression contains the named capture ⁴ “page”, which will be returned by the router onmatch together with the default parameters.

Line 9 contains spec option, which is used for generating URLs by route (we will discussgenerating 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 beindexed in the matches array by its name.

Page 140: Using Zend Framework 2

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” namedcapture 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 ViewModelvariable 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: theTable 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 thelist 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, seefurther sections in this chapter).

Then add the introduction.phtml page into the same doc directory:

Page 141: Using Zend Framework 2

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 seea 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 addother pages to the doc directory to make them automatically available for site users through ourRegex route.

One disadvantage of such a documentation system is that it does not work well ifyou place nested pages in subdirectories under the doc directory. The reason of thislimitation lies in the way the Regex route assembles URLs. You can’t generate URLscontaining slash characters, as these “unsafe” characters will be automatically URL-encoded. We will work-around this problem with our custom route type that we willcreate 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 …]”. TheURL may contain variable count of name/value pairs. Because the Wildcard route type mayconsume all segments up to the end of the URL, it should not have child routes.

Page 142: Using Zend Framework 2

URL Routing 123

To demonstrate the usage of the Wildcard route type, let’s assume we need to create an actionthat would display blog post(s) published during some period of time. For this action, we wouldlike 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 whichparameters are present. If the user provided the name of the blog post to show, we display itsname, 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 displayblog posts for the year. If the user didn’t provide neither year nor month, we display all posts forthe period.

Next, define the routing configuration for this action. Add the following lines under your routerkey:

Page 143: Using Zend Framework 2

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_delimiteroption defines the character which separates the key and value within the pair. Typically, this isalso 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 seethe 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 seethe 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 routetypes mentioned previously.

TheHostname 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 ofother 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 dailytasks, like content publishing.

Page 144: Using Zend Framework 2

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, whichcan take different sub-domain values. The constraints key defines the regular expression thissub-domain parameter must match. TheHostname route will differentiate your domains, so eachsite 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 differentways.

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 requestdata is tunnelled through Secure Socket Layer (SSL) channel and not available to third parties.

Page 145: Using Zend Framework 2

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 thescheme to match against (like http or https). If the scheme in HTTP request’s URL is exactlythe same as the scheme option, the route is considered matching. You can use the defaults keyto return some parameters on route match. In the example above, the https boolean parameterwill be returned.

The Method route type can be used if you need to direct GET and POST requests into differentcontroller’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 bethe 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 parametersextracted from URL string.

In your controller, you will often need to retrieve these parameters. We already did this in theexamples above. In this section, we will give some summary.

Page 146: Using Zend Framework 2

URL Routing 127

To retrieve a parameter from the route in your controller’s action method, you typically use theParams controller plugin and its fromRoute() method, which takes two arguments: the name ofthe 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 dothat, 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\RouteMatchclass, providing the methods for extracting the matched route name and parameters extractedfrom 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 alternativelyyou 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 followingcode:

Page 147: Using Zend Framework 2

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 lifecycle consists of events). We then use the the getRouteMatch() method of the MvcEvent class toretrieve the RouteMatch object.

In line 8, we use the getMatchedRouteName() method to retrieve the name of the route thatmatched 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 cando 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 onmatch return the set of parameters by which a controller and action can be determined. Anopposite task a route class allows to do is generating a URL by parameters. This feature can beused in your controller action methods for generating URLs, for example, for redirecting a userto 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 apage internal to your site or to a page on another site. A hyperlink is represented by <a> HTML

Page 148: Using Zend Framework 2

URL Routing 129

tag having href attribute specifying the URL of the destination page. Below, an example of ahyperlink 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 andpass the “home” route name as its parameter. In line 5, we pass the “about” route name as anargument for the Url view helper.

In the example above, the Url view helper internally uses the RouteMatch object andcalls 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 willbe 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 thesecond argument:

Page 149: Using Zend Framework 2

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 andparameters. We pass the “application/default” compound route name as the first argument, andan array of parameters as the second argument.

In line 3, we pass the “controller” and “action” parameters to tell the Segment route class that itshould 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 willbe 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 specifythe third parameter for the Url view helper. The third parameter should be an array containingone or several options. For assembling the absolute URL, pass the force_canonical option, as inthe example below:

Page 150: Using Zend Framework 2

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, emptyarray as the second argument, and an array containing force_canonical option as the thirdargument. In lines 6-8, we also pass the force_canonical option as the third argument forgenerating 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 thirdargument of the Url view helper. For example, assume you have the “search” action in somecontroller (and a routing rule mapped to this action), and you want to pass it search querystring 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 followingcode:

<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⇒valuepairs 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 examplebelow:

Page 151: Using Zend Framework 2

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 viewhelper’s ones. So, you can generate absolute or relative URLs the same way you did inyour view templates.

5.11.3 URL Encoding

When generating URLs either with the Url view helper or with the Url controller plugin, youshould 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 withthe 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” parameterwith 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 theslash (‘/’) 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.

Page 152: Using Zend Framework 2

URL Routing 133

5.12 Writing Own Route Type

Although ZF2 provides you with many route types, in some situations, you will need to writeyour own route type.

One example of the need for such a custom route type is when you have to define the URLmapping rules dynamically. Usually, you store the routing rules configuration in module’s configfile, but in some CMS systems you will have documents stored in the database. For such a system,youwould need to develop a custom route typewhichwould connect to the database and performroute matching against the data stored in the database. You cannot store this information inconfig 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 URLgeneration.

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 thefactory() method.

The match() method is used to perform the matching of the HTTP request (or, particularly itsURL) 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 nullon failure.

The assemble()method is used for generating URL string by route parameters and options. ThegetAssembledParams() helper method’s purpose is to return the array of parameters which wereused on URL generation.

5.12.2 Custom Route Class

To demonstrate the creation of a custom route type, let’s improve our previous approach tobuilding the simple documentation systemwith Regex route type. The disadvantage of the Regexroute type is that you cannot organize the static pages in a hierarchy by creating subdirectoriesunder the doc directory (when generating an URL for such a page, the slash directory separator

Page 153: Using Zend Framework 2

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 URLsstarting 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 toTreeRouteStack) 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, ifthe URL is “/chapter1/introduction”, then the route should check if the correspondingview template file <base_dir>/chapter1/introduction.phtml exists and is readable, and ifso, 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” isnot. 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 theStaticRoute.php file inside of it (figure 5.9). Inside that file, paste the stub code presented below:

Figure 5.9. StaticRoute.php file

Page 154: Using Zend Framework 2

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 thatwe placed the StaticRoute class inside the Application\Servicenamespace (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 implementsthe 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:

Page 155: Using Zend Framework 2

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 ofthe 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 initializingthe protected properties.

Next, let’s implement the factory() method for our StaticRoute custom route class. Thefactory() method will be called by the router for instantiating the route class:

Page 156: Using Zend Framework 2

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. TheStaticRoute class will accept the following options:

Page 157: Using Zend Framework 2

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 instantiateand 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';

Page 158: Using Zend Framework 2

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 usedfor 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 acceptablefile (directory) name (lines 27-321). If the segment is not a valid file name, we return null as afailure status.

In line 35, we calculate the path to the view template, and in lines 27-29 we check if such afile really exists and accessible for reading. This way we match the URL against the directorystructure.

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

Page 159: Using Zend Framework 2

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: theparameters array and the options array (line 10). The method constructs the URL by encodingthe segments with URL encoding and concatenating them (line 20-26).

The method getAssembledParams() just returns the names of the parameters we used for URLgeneration (page 36).

Now we’ve finished the StaticRoute route class. To use our custom route type, we add thefollowing 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 ),

Page 160: Using Zend Framework 2

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, wedefine the base directory where the “static” pages will be placed (line 4), the template prefix (line5), the filename pattern (line 6), and the defaults array, containing the name of the controllerand 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, weretrieve 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 templatename 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:

Page 161: Using Zend Framework 2

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 shouldappear (see figure 5.11 for example). If you type the http://localhost/chapter1/intro URL in yourbrowser, you should see the Introduction page (figure 5.12).

Figure 5.11. Help page

Page 162: Using Zend Framework 2

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 willautomatically become available to site users.

If you are stuck, you can find this complete working example inside the Hello Worldapplication.

5.13 Summary

In this chapter, we’ve learned about routing. Routing is used for mapping HTTP request tocontroller’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 HTTPrequest’s data) to compare the URL with the specified route template. We also learned how towrite 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, bywhich a controller and action can be determined. An opposite task a route class allows to do isgenerating a URL by parameters. This feature is widely used in view layer of the application forgenerating hyperlinks.

Route types can be combined in a nested tree with the help of TreeRouteStack router, ororganized in a chain with SimpleRouteStack router. These two routers allow to define arbitrarilycomplex rules.

Routing configuration is stored in module’s configuration file under the router key. Eachmodule exposes its own routing rules, which are merged with other modules’ configurationupon application start up.

Page 163: Using Zend Framework 2

6. Page Appearance and LayoutIn this chapter you will learn how to make your web pages attractive and professionally lookingwith the help of Twitter Bootstrap CSS Framework and how to position elements on a pageusing ZF2 layout mechanism. You’ll also become familiar with common view helpers allowingfor composing web pages of reusable parts. If you are new to Twitter Bootstrap, it is alsorecommended 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 andcommon 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, CSSstylesheets 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 skillsof 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² (orshortly, the Bootstrap). Originally designed at Twitter to unify the appearance of their ownweb tools, Bootstrap has became a popular CSS framework, allowing to make your web siteprofessionally looking and visually appealing, even if you don’t have advanced designer skillsand without the need of creating basic CSS rules (but, of course you can define your own customCSS rules on top of Bootstrap to customise your site’s appearance). Bootstrap is freely distributedunder the Apache License v.2.0³.

Twitter Bootstrap is shipped with Zend Skeleton Application, so you can use it out ofthe box. Alternatively, you can download the newest version of Bootstrap from theproject’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 164: Using Zend Framework 2

Page Appearance and Layout 145

• It provides theCSS 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 pagein a grid-like structure. For example, look at the Skeleton Application’s main page (figure6.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 isthe navigation bar component at the top, and the header (also called the Hero Unit orJumbotron) component below the navbar. These components are very handy on any website.

• In includes the JavaScript extensions that allow to make Bootstrap-provided interfacecomponents more interactive. For example, JavaScript is used to animate dropdownmenusand 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 AppendixC, where you can find more information about using Twitter Bootstrap and itscomponents.

Page 165: Using Zend Framework 2

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, andthe <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 includedstylesheets 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 calledthe layout. The layout “decorates” other view templates.

The layout template typically has a placeholder in which ZF2 puts the content specific to aparticular 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 locatedinside of the view/layout directory inApplicationmodule’s directory (see figure 6.3 for example).

Page 166: Using Zend Framework 2

Page Appearance and Layout 147

Let’s look at the layout.phtml template file in more details. Below, the complete contents of thefile is presented (because some lines of the file are too long for a book page, line breaks areinserted 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 167: Using Zend Framework 2

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 168: Using Zend Framework 2

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 tagsmixed with PHP code fragments. When the template is rendered, ZF2 evaluates the inline PHPfragments and generates resulting HTML page visible to site users.

Line 1 above generates the <!DOCTYPE> ⁵ declaration of the HTML page with the Doctype viewhelper.

Line 3 defines the <html> element representing the root of the HTML document. The <html> tagis followed by the <head> tag (line 4), which typically contains a title for the document, and caninclude 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 usingUTF-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. ThesetSeparator()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 pagetitle. The Translate view helper is used for localizing your web site’s strings into differentlanguages.

In line 12, the HeadMeta view helper allows to define the <meta name="viewport"> tag containingmeta information for the web browser to control layout on different display devices, includingmobile browsers. The width property controls the size of the viewport, while the initial-scaleproperty 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, youtypically 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 stylesheetfiles: 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 todefine 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 executedby 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 inAPP_DIR/public/js directory.

⁵The <!DOCTYPE> declaration goes first in the HTML document, before the <html> tag. The declaration provides an instruction to the webbrowser 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 theAbout page of your site you will have the “About - Zend SkeletonApplication”, and for the Documentation page you will have something like “Documentation - Zend Skeleton Application”.

Page 169: Using Zend Framework 2

Page Appearance and Layout 150

Line 38 defines the <body> tag, the document’s body which contains all the contents of thedocument, 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 applicationuses 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 whichdenotes the container element for the grid system. So, you can use the Bootstrap grid system toarrange the contents of your pages.

Line 64 is very important, because this line defines the inline PHP code that represents the pagecontent placeholder we talked about in the beginning of this section.When the ZF2 page rendererevaluates 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 “2013by Zend Technologies Ltd. All rights reserved.” You can replace this information with you owncompany name.

Line 73 is the placeholder for JavaScript scripts loaded by the concrete page. The InlineScriptview 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 ofthe 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, pagecontent placeholder in the middle of the page, and the footer with the copyright information atthe 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 asfollows:

<?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 170: Using Zend Framework 2

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 171: Using Zend Framework 2

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 fourgrid columns. For styling the text, we use our custom CSS class app-caption (we willdefine 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, itwill echo the value of the $content variable, so the actual page content will be substitutedhere.

• 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, ifyou 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 172: Using Zend Framework 2

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 alsoused 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 Routesection 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 defineour 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 lookingcolor. To do this, open the style.css file, and append the following lines to the end:

Page 173: Using Zend Framework 2

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> elementand defining the 25 pixels vertical padding, large font size, bold text style and the hexadecimalrepresentation of the RGB text color.

By default, in skeleton application, the navbar is pinned to page top, and the CSS rule for the pagebody defines the 20 pixels top padding to leave space for it. Since in our Hello World examplewe’ve unpinned the navigation bar from top of the page and placed it in page flow, we need toremove the padding from page body top. To do that, edit the following CSS rule in the style.cssfile 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 sitein your browser, you should see the page as in figure 6.4. You can click the links in navigationbar to visit the pages like About or Documentation, etc. The content of a particular page is putinto the content placeholder of our layout.

The result can be seen in action in the Hello World sample application that is part ofthis 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,youwill 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 partwould consist of web pages publicly visible to all users and would utilize the default layout forall of these pages. The back-end part would consist of pages visible to the administrator user onlyand utilize another layout template containing an administrative menu.

First, prepare another layout template file. For example, call it layout2.phtml. To simplify the filepreparation, 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 particularcontroller’s action by using the following code:

Page 174: Using Zend Framework 2

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 allowsto access the instance of the ViewModel class associated with the layout template. To change thelayout template for this particular action method, we called the setTemplate()method providedby the ViewModel class.

In addition to the Layout controller plugin, there is the Layout view helper whichprovides 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 actionmethods of a controller class need to use the same alternative layout, you can overridethe 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 175: Using Zend Framework 2

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 logicacross different view templates.

For a simple example of partial view usage, let’s imagine that we need to render a table of someproducts. Each product has the ID, the name and the price. We can use partial view template torender 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 176: Using Zend Framework 2

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 theview 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 line18, we walk through the items of the products array and render each row with the Partial viewhelper.

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 tothe view template. They will be accessible the same way as if you would pass them with theViewModel variable container.

Finally, create the table-row.phtml view template, whichwill be used as the partial view template:

Page 177: Using Zend Framework 2

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 yourbrowser’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 ofseveral pieces.

For example, you can use the Placeholder view helper in pair with the Partial view helperto “decorate” the content of a view template with another view template. A useful practicalapplication for this is layout “inheritance”.

Imagine the situation, when you need to create an alternative layout which has exactly the samehead 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 layouttemplate, 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, whichwill 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 onall (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 thenrender/use it on another one.

Page 178: Using Zend Framework 2

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 179: Using Zend Framework 2

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 (line15) of the Placeholder view helper to delimit theHTMLmarkup thatwill be captured by the viewhelper 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-cellgrid. The first cell of the grid (spanning 8 columns) will contain the actual content of a certainpage, 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 improvesthe code reusability.

Now, if you set the layout2.phtml for all actions of, say Index controller, you should be able tosee 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 beinserted to an HTML file between <script> and </script> tags. Below, an example JavaScriptcode is presented:

Page 180: Using Zend Framework 2

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 functionin it. The jQuery binds a function to be executed when the DOM has finished loading. Whenthe function is executed, a simple alert window with the “Hello World!” text and OK button willappear.

Figure 6.6. Inherited layout

Since you put this JavaScript code inside the HTML file, we will refer to it as inline script. Analternative way of storing JavaScript code is putting it in an external .js file. External filestypically contain code that is designed to be used by several web pages. Typically, externalJavaScript files are stored in APP_DIR/public/js/ directory. To link an external JS file to yourHTML 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 executesthe code.

Generally, there are two places inside an HTML file where you can put the script:

Page 181: Using Zend Framework 2

Page Appearance and Layout 162

• JavaScript code can be put in the <head> section of an HTML page. This method isrecommended to use when you need JavaScript to be loaded before the content of thepage. We used this method for loading the Twitter Bootstrap JavaScript extensions andjQuery library.

• Script can be placed at the bottom of the <body> section of an HTML page, just before theclosing </body> tag. This way is acceptable when you need the entire DOM⁹ to be loadedbefore 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 toplace it in layout view template. But when a script needs to be used on a single page only, puttingit in the layout template is not the best idea. If you put such a script to layout template, the scriptwill be loaded to all pages, which can produce an unneeded traffic and increase page load timefor 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 usethe 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 codein 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. Thismethod takes two arguments. The first one is the path to external JS file (if the file is storedinside of APP_DIR/public/js directory, or an URL of a JS file if the file is located on another webserver). 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 scriptwill be inserted.

⁹The DOM (Document Object Model) is a convenient representation of an HTML document structure as a tree of elements.

Page 182: Using Zend Framework 2

Page Appearance and Layout 163

The methods prependScript(), appendScript(), offsetSetScript() and setScript() aredesigned to insert an inline JavaScript code. They are rarely used, because you typically insertexternal 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 InlineScriptview 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 ofdocument 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 withthe inline JavaScript code. Then we call the appendScript() function on the InlineScript viewhelper 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 notrecognize the JavaScript code. To fix this, you can simply put the <script> element at the bottomof 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 betterscript readability and automatic syntax checking.

For HeadScript and InlineScript view helpers to work, you should ensure theircontent 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 theweb 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 183: Using Zend Framework 2

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 userwants to type in by several first letters, without the user actually entering the text completely. Wecan 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 distributedfreely.

Download typeahead.min.js file (a minified version of the Typeahead library) from the officialproject page¹². When the download is finished, place the file in yourAPP_DIR/public/js directory.

Then add the typeahead.phtml file in your application/index/static subdirectory under themodule’s view directory. This directory is served by the StaticRoute route type that we’vecreated and configured earlier in Chapter 5, and all “static” pages placed here will automaticallybecome 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 fileto the <head> section of the page with the HeadScript view helper (line 4).

¹²http://twitter.github.io/typeahead.js/

Page 184: Using Zend Framework 2

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 theinput field with the typeahead CSS class.

Lines 11-25 contain inline JavaScript code placed at the bottom of the view template (we don’tuse InlineScript view helper for better code readability).

In line 12, we have the jQuery event handler bound to the “document is ready” event. This eventis fired when the complete DOM tree has been loaded.

In line 13, we have the jQuery selector (“input.typeahead”) which selects all input fields markedwith 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 userenters a character in the field, the handler is executed and checks the letters entered. It thendisplays the dropdown menu with suggested auto-completion variants.

The typeahead() function takes two arguments: the name argument identifies the dataset, andthe 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, addthe 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 185: Using Zend Framework 2

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 yourbrowser and press Enter. The Typeahead page will appear with the prompt to enter a continentname. For example, type a letter to see how Typeahead suggests you available variants (figure6.7).

Figure 6.7. Auto-complete feature

Page 186: Using Zend Framework 2

Page Appearance and Layout 167

You can see this example working in the Hello World sample bundled with this bookby 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 alink 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 rulesprovided 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 needthis CSS stylesheets for most of your pages, it is better to link them in the head section of thelayout template. But, if a certain CSS stylesheet needs to be loaded for a single page only, youplace 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 appendStylesheetmethod of the HeadLink view helperto add an external CSS stylesheet to the head section of the document. The method accepts apath 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 187: Using Zend Framework 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 usethe 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 textinput 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 jQueryUI 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 analogousto Twitter Bootstrap in the sense that both provide reusable user interface components.

¹⁴http://jqueryui.com/

Page 188: Using Zend Framework 2

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 addthe 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 tojquery-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 textinput field. When the user clicks the text input field, the datepicker widget will appear allowingto 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 stillhave a couple of things to do to make the layout fully functional. If you remember, the layouttemplate contains the navigation bar and breadcrumbs. But both navigation bar and breadcrumbsinterface components provided by Twitter Bootstrap are currently “static”, while they need to bemore interactive.

For example, the active item of the navigation bar should depend on the controller’s action thatis being executed at the moment. And the breadcrumbs should display the path to the currentlyviewed page. In this section we will make these widgets completely ready for the web site withthe help of our own view helpers.

Page 189: Using Zend Framework 2

Page Appearance and Layout 170

Figure 6.8. Datepicker

A typical view helper is a PHP class deriving from Zend\View\Helper\AbstractHelper baseclass, 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 190: Using Zend Framework 2

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 navigationbar. 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 (forexample, 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 dropdownmenus as navigation items. For example, in case ofthe Support dropdownmenu having theDocumentation andHelp sub-items, the item descriptionwill 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 191: Using Zend Framework 2

Page Appearance and Layout 172

Wewant to put the Menu class in Application\View\Helper namespace. Thus, start from creatingthe 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 classcalled view resolver, and for this reason, view templates are stored under the module’sview/ 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 192: Using Zend Framework 2

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 ofitems for initializing the menu. An alternative method of menu initialization is through thesetItems() method (lines 20-23). And the setActiveItemId() method (lines 25-28) sets the IDof the currently active menu item.

Next, let’s add the render() method, which will generate HTML code for the whole navigationbar 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 193: Using Zend Framework 2

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. Thenavbar 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 menuitems and render each one with the renderItem()method. Finally, the render()method returnsthe resulting HTML code as a text string.

To finish with creating the Menu class, let’s implement the renderItem() method. This methodwill 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 194: Using Zend Framework 2

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 theitem is a dropdown menu or a simple item (line 10). If the item is a dropdown menu, we walkthrough the dropdownmenu items, and render each one in turn (lines 15-20). Lines 25-30 containthe 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 inconfiguration. 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 ableto access it from any view template.

Since we plan to use the Menu view helper in the layout view template, replace the navigationmenu markup in layout.phtml file with the following code:

Page 195: Using Zend Framework 2

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 baritems with the help of setItems() method (line 1). As a parameter for the method, we pass thearray 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 fromany view template. For example, add the following code to the beginning of the view templatefor the About page (application/index/about.phtml) as follows:

Page 196: Using Zend Framework 2

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 thenavigation menu is highlighted with a different color. To display the active item properly, youneed 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 forrendering the breadcrumbs. It is completely analogous to the Menu view helper, so below we justprovide 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 197: Using Zend Framework 2

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 themodule.config.php file as follows:

Page 198: Using Zend Framework 2

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 thebreadcrumbs 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 thefollowing 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 userswill 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 classas a variable container for passing the variables from controller to view template, and we alsoused the ViewModel for overriding the default view template name.

Page 199: Using Zend Framework 2

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 viewmodels in a tree-like structure. Each view model in the tree has the associated view templatename and data variables that can be passed to the view template to control the process ofrendering.

This feature is internally used by Zend Framework 2 when “combining” the layout view templateand the view template associated with the controller’s action method. ZF2 internally creates theview 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 achild 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 theresulting HTML markup is saved in a temporary storage;

• The output HTML markup of the child view model is passed to the layout view model asthe $content variable. This way the layout view template can render the content specificto the certain page.

Table 6.5 gives the summary of the methods provided by the ViewModel class for the purpose ofpage composition:

Page 200: Using Zend Framework 2

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 modelsattached, 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 toinject the HTML markup code produced by a child view template. If two child view models usethe same variable, the second one will overwrite the first one. The setAppend() method can beused when you need to inject the results of two or more view templates into a single placeholdervariable. 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 viewmodel is considered as terminal (top-levelparent) and the renderer returns the output of the view template to the application, otherwise itsparents are rendered as well. The method terminate() tests whether the view model is terminalor not.

The setTerminal()method is very useful in some situations, because with its help youcan disable the rendering of the layout view template. If you return from controller theview model marked as terminal, the layout will not be applied. This can be used, forexample, when you want to load part of a page asynchronously by an AJAX ¹⁵ requestand 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 forcreating 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, andretrieve data from, a server asynchronously (in background) without interfering with the display and behavior of the existing page.

Page 201: Using Zend Framework 2

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 havea navigation bar at the top, the body with page content, and the footer with the copyrightinformation at the bottom). In Zend Framework 2, you define this common structure with aview template file called the layout. The layout template may have placeholder(s) in which ZF2puts 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.

Page 202: Using Zend Framework 2

7. Collecting User Input with FormsIn this chapter, you will become familiar with using web forms for gathering data entered by siteusers. In Zend Framework 2, functionality for working with forms is mainly spread across fourcomponents: the Zend\Form component, which allows you to build forms and contains the viewhelpers 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 thebook. 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 todownload the code as a ZIP archive. When the download is complete, unpack the archive to adirectory of your choosing.

Then navigate to the formdemo directory which contains the complete source code of the FormDemo web application:

/using-zend-framework-2-book

/formdemo

...

To install the example, you can either edit your default virtual host file or create a newone. After editing the file, restart the Apache HTTP Server and open the web site inyour web browser. For additional information on Apache virtual hosts, you can referto Appendix A.

¹https://github.com/olegkrivtsov/using-zend-framework-2-book

Page 203: Using Zend Framework 2

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. HTMLprovides several tags intended for defining form fields:

• <input> - specifies an input field where the user can enter some data (field appearanceand 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 adropdown 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 iconon the button.

Page 204: Using Zend Framework 2

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 containscorresponding 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 thatthe user entered data is in a correct format, and if not the browser will prevent form submissionand 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" />

Page 205: Using Zend Framework 2

Collecting User Input with Forms 186

At themoment of writing this chapter, not all modernweb browsers completely supportHTML5 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 examplebelow. 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

Page 206: Using Zend Framework 2

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-mailaddress, message subject, text, and then submit them to the server. The form definition beginswith 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 forprocessing the submitted form (“/contactus”).

• the method attribute defines the method (either GET or POST) to use for delivering formdata. 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 attributespecifies 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 heightof 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 attributeallows you to set the title text for the button (“Submit”). By clicking this button, the user willsend 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 openthe file in your browser. You will see the form visualization as in figure 7.4.

Page 207: Using Zend Framework 2

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 willsend an HTTP request to the URL you specified in the action attribute of the form. The HTTPrequest 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 methodshave important technical differences.

When using POSTmethod (the default) for submitting the form, the data is sent in HTTP requestbody. For example, when you press the Submit button on the feedback form, an HTTP requestwill 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 areconcatenated in a single string and then URL-encoded to replace unsafe characters with allowedcharacters from the ASCII table.

In comparison, when you set the GET method for the form, an HTTP request will look like theexample below (the back-slash (‘’) character indicate a line break inserted where the line is toolong for a book page):

Page 208: Using Zend Framework 2

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 aspart of the HTTP request’s URL (line 1), which makes the URL long and harder to read. Sincethe form data is sent inside the URL, this makes it easily visible to site visitors.

Inmost cases, youwill use the POSTmethod for delivering form data in the request body, becausethe user doesn’t need to see the data in the browser’s navigation bar (especially when submittingpasswords or other sensitive data).

Please note that submitting form data using the POST method does not protect yoursensitive data (like passwords, credit card numbers, etc.) from being stolen. To protectsuch data, you’ll have to direct your HTTP traffic to a SSL³ tunnel (SSL stands forSecure 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 needto obtain an SSL certificate from a trusted provider (like VeriSign⁴) and install it onyour server.

7.3 Styling HTML Forms with Twitter Bootstrap

In ZF2-based web sites, we use the Twitter Bootstrap CSS Framework that provides default CSSrules 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 usinglabels 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 classlike .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/

Page 209: Using Zend Framework 2

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="[email protected]">

</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 makesthe form fields as wide as the size of the screen. This may make your form too wide and hard tounderstand. To limit form width, you can use the Bootstrap-provided grid, like in the examplebelow:

<div class="row">

<div class="col-md-6">

<form>

...

</form>

</div>

</div>

Page 210: Using Zend Framework 2

Collecting User Input with Forms 191

In the HTML markup above, we put a form inside of the 6-column-width grid cell, which makesthe 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 formprompting the site user for input. Once the user fills the form fields, they click the Submitbutton, 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 thefeedback form and retrieving the data submitted by the user. To start, add the contact-us.phtmlview template in the application/index/ directory under the module’s view/ directory (see figure7.6 for example).

Put the HTML markup code of the feedback form from the previous section into the viewtemplate file.

Then, add the contactUsAction() action method to the IndexController class. In the actionmethod, we want to extract raw data from the feedback form submitted by the site user:

Page 211: Using Zend Framework 2

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 ofthe HTTP request). Typically, the form uses the POST method for submitting the data. For this

Page 212: Using Zend Framework 2

Collecting User Input with Forms 193

reason, we can detect if the form is submitted or not by checking the starting line of the HTTPrequest.

In line 15 we retrieve the raw data submitted by the user. We extract all the POST variables withthe help of the Params controller plugin. The data is returned in the form of an array and savedinto the $data variable.

Finally, we have to add a literal route to make a short and memorable URL for the Contact Uspage. Add the following contactus key to the routing configuration in the module.config.phpfile:

<?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 theSubmit button on the form. The data will be sent to the server, and finally extracted in theIndexController::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.

Page 213: Using Zend Framework 2

Collecting User Input with Forms 194

array (size=4)

'email' => string '[email protected]' (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 viewtemplate with form HTML markup and a controller action responsible for displaying the formand dumping raw user input to the screen. However, using raw user input in real-life applications

Page 214: Using Zend Framework 2

Collecting User Input with Forms 195

has a disadvantage in that we do not check user-submitted data for possible errors and/ormalicious 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 isusually separated into form models responsible for field definition, filtering and validation; andform 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, isschematically shown in figure 7.8. As you can see from the figure, the standard HTML formsfunctionality 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 forrendering 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 fewerplaces in your code where you need to check user input, thus you improve your sitesecurity.

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 forvalidation. Form view helpers are used in a view template for generating HTML markup of theform. 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 siteuser from GET, POST (and possibly other) PHP variables. Then you create an instance of

Page 215: Using Zend Framework 2

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) forany invalid form field.

2. Secondly, you pass the form model to the .phtml view template for rendering (with thehelp of the ViewModel variable container). The view template then will be able to accessthe form model and call its methods.

3. And finally, the view template uses the formmodel and the view helpers provided by ZendFramework 2 to render the form fields (and to display possible validation error messagesproduced 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 formmodels 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 itselfcan be considered as a fieldset. This fact is reflected in form class inheritance (figure 7.9). As you

Page 216: Using Zend Framework 2

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 logicalif you remember that the Form class inherits methods for adding form fields from theFieldset class, and that it inherits methods for setting form attributes from the Elementclass.

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

Page 217: Using Zend Framework 2

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 toApplication\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 modelfrom 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() methodprovided by the base class (line 16). The setAttribute() takes two parameters: the first oneis 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, settingthe form’s “action” attribute is optional.

Setting the “action” attribute for the form is optional, because empty form action forcesthe browser to submit form data to the URL of the current page. This is sufficient inmost scenarios, because usually you use the single controller action for both displayingthe form and processing its data.

Form fields are typically created inside of the form model’s constructor (look at line 21). In thenext section, we will learn which form fields are available and how to add them to the formmodel.

7.7 Form Elements

In a form model, an input field is typically paired with the text label (<label> and <input> tagsare 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; theoptions 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 theZend\Form component. The Element base class implements the ElementInterface interface. Theclass inheritance diagram is shown in figure 7.10.

Page 218: Using Zend Framework 2

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

Page 219: Using Zend Framework 2

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 onHTML4 and HTML5 input fields (discussed in the beginning of this chapter).

For your convenience, ZF2 also provides several “compound” fields. The MultiCheckbox fieldis a field which is composed of a group of typical checkboxes related to each other. TheDateTimeSelect, DateSelect, and MonthSelect elements are analogous to correspondingHTML5elements, but simulate them with the usual select fields. These input fields have an advantage inthat they are supported by all web browsers, unlike the corresponding HTML5 fields. The visualrepresentation 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 formfor enhancing the security. The Captcha element is a graphical element (image) that is placedon a form for checking if the site user is a human or a robot. The Csrf element has no visualrepresentation 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 elementsdynamically 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 userthat the website trusts.

Page 220: Using Zend Framework 2

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 elementto insert, and the second one (named $flags) is the optional flags.

The $elementOrFieldset parameter may either be an instance of an Element-derived class (orthe 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 followingkeys: 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 aform.

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

Page 221: Using Zend Framework 2

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). Theclass 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 baseclass. For example, in line 20, we set the “id” attribute with the setAttribute() method. Foryour reference, the (most important) methods of the Element base class which can be used forconfiguring 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.

Page 222: Using Zend Framework 2

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 specificationto 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 willautomatically be instantiated and configured by the base class. Internally, this isaccomplished with the help of the Zend\Form\Factory factory class (illustrated byfigure 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 }

Page 223: Using Zend Framework 2

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 theelement specification to the add() method in the form of an array. The array has the followingtypical keys:

• the type key (line 15) defines the class name to use for instantiation of the element. Hereyou 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 addfields (elements) to the form, let’s create the complete model class for the feedback form that weused in our previous examples.

As we know, form model classes for the Application module live inside the Application\Formnamespace. So, we have to create the ContactForm.php file inside of the Form directory underthe 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 theZend\Form\FormElementManager class.

Page 224: Using Zend Framework 2

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 initializethe form by adding its elements;

• addElements() protected method will contain the actual code for adding form elementsand will be called by the constructor.

We put the field creation logic into the addElements() protected method to betterstructure 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

Page 225: Using Zend Framework 2

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

Page 226: Using Zend Framework 2

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) andpasses 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 addingelements to the form. The code of the addElements() is located in lines 27-70. To add elementsto the form, we call the add()method provided by the base class. This method accepts the singleargument – an array containing configuration for an element. We add four fields: the email, thesubject, the body and the submit field.

In figure 7.14, you can see schematic graphical representation of the formmodel we have created.

Figure 7.14. The feedback form model and its elements

Page 227: Using Zend Framework 2

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 duringthe form submission. For example, for our feedback form, we want to perform the followingchecks:

• We want to test that the E-mail address, message subject, and body fields are alwayspresent (because these fields are required).

• We want to ensure that the user entered a valid E-mail address like [email protected].• Users may add white space characters to the beginning and/or the end of the E-mailaddress, so we would like to filter such characters out (perform the string trimmingoperation).

• It would be useful to check for minimum and maximum allowed length of the messagesubject and body text.

• For the message subject, we would like to filter out (strip) the new line characters andHTML 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 twocategories: filters and validators.

The filters transform the user-entered data to fix possible errors or to ensure the data conformsto 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 consideredvalid and the data can be safely used by the business logic layer. If a certain field is invalid, avalidator raises an error flag. In that case, the form is typically shown to the user again, and theuser 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 bechecked, leaving a hole in your site’s security. It is recommended to always add avalidation rule per each form field entered by user and add as many checks per eachfield 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 InputFilterclass is defined in the Zend\InputFilter component. The input filter is a container for so calledinputs. 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 undesiredcontent. To avoid this, we need to replace HTML tags in message subject and text.

Page 228: Using Zend Framework 2

Collecting User Input with Forms 209

An input may consist of filters and/or validators and some additional information. Forexample, an input may contain the flag telling if the field is required or if its value maybe missing from HTTP request.

Analogous to adding a form model’s fields, there are two possible ways of adding inputs to theinput 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 lattermethod (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 thename of the form model’s field. If the name of the input doesn’t match the name of thecorresponding 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 inputclasses are shown in figure 7.15. In figure 7.15, the Input class is designed to be usedwith regular scalar values, ArrayInput is used for filtering/validating array values, andFileInput is used for checking uploaded files.

• The required key (line 4) tells whether the form field is required or optional. If the fieldis 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.

Page 229: Using Zend Framework 2

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 mustbe an integer number. The filters with the highest priority will be applied first. By default, theFilterChain::DEFAULT_PRIORITY constant (value 1000) is assigned.

The options array (line 4) is specific to a certain filter and may contain parameters forconfiguring the filter.

7.9.2.2 Validator Configuration

A typical validator configuration is presented below:

Page 230: Using Zend Framework 2

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 validatorcheck fails. If this equals to true, subsequent validators in the list will not be executed; otherwiseevery 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 forconfiguring 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 formmodel. 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 itwith 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/validationrules, 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

Page 231: Using Zend Framework 2

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

Page 232: Using Zend Framework 2

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\InputFilterclass (line 3).

In the formmodel’s constructor (line 10), we call the addInputFilter()method which we definein lines 16-76.

The addInputFilter() method’s goal is to create the InputFilter container (line 18), attach itto form model (line 19) and add filtering/ validation rules (lines 21-75). For attaching the inputfilter 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() methodprovided 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 addressfor correctness. We configure the EmailAddress validator to allow domain names as E-mail addresses (the \Zend\Validator\Hostname::ALLOW_DNS flag) and disable MX recordchecking (set useMxCheck option to false).

• For the subject field, by analogy, we make it required, and use the StringTrim filterto remove white spaces from the beginning and the end. Additionally, we use theStripNewLines 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 inlength by using the StringLength validator.

Page 233: Using Zend Framework 2

Collecting User Input with Forms 214

• For the body field, we require it to be mandatory, and we use the StripTags filter to stripHTML tags from E-mail text. We also use the StringLength validator to constrain E-mailtext 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’vecreated.

Figure 7.16. The input filter for ContactForm

Above, we briefly described how to create an input filter for the form model. Fordetailed information about the above mentioned (and other) filters and validators andtheir usage examples, please refer to Chapter 8.

7.10 Using the Form in a Controller’s Action

When the formmodel 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 iterativeprocess (schematically illustrated by figure 7.17):

Page 234: Using Zend Framework 2

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

Page 235: Using Zend Framework 2

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

• 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 resultsin executing the input filter attached to the form). If a certain field(s) is/are invalid, displaythe 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 modelusing 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’saction 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();

Page 236: Using Zend Framework 2

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

In line 19 we retrieve the raw data submitted by the user. We extract all the POST variables withthe help of the Params controller plugin. The data is returned in the form of an array and savedinto the $data variable.

The data submitted by the user may contain mistakes and should be filtered and validated beforefurther 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 (line23), which returns true upon successful validation. If the validation succeeds, we retrieve thevalidated data using the getData() method (line 26) and then can pass the data to our businesslogic 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 pagewhere you redirect the user.

We will prepare the controller’s action and view template for the Thank You page alittle bit later.

Page 237: Using Zend Framework 2

Collecting User Input with Forms 218

In line 37, we pass the form model through the $form variable to the view template. The viewtemplate will access this variable and will use it for rendering the form (and possible validationerrors).

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, inthis section we will create a simple MailSender model ⁹ class which can be used for sending anE-mail message to an E-mail address. When the user submits the form, we will validate the formdata and pass the validated data to the MailSender model and ask it to send the E-mail messageto the recipient.

Reading this section is optional and intended mostly for beginners. You may skip it andrefer 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 severalclasses implementing available transports for sending mail (in this example, we will use theMail\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/Unixoperating systems. It accepts messages that a PHP script passes to it, deciding basedupon the message header which delivery method it should use, and then passes themessage through the SMTP protocol to the appropriate mail server (like Google Mail)for delivery to the recipient.

Start with creating theMailSender.php file under the Service directory under the module’s sourcedirectory (see figure 7.18 for example).

Figure 7.18. Creating the MailSender.php File

The following is the code that should be put into theMailSender.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/

Page 238: Using Zend Framework 2

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 theMailSender class can be related to service models (its goal is to manipulate data, not to storeit).

In lines 4-6, we declare the aliases for the Mail, Message and Transport\Sendmail classesprovided by the Zend\Mail component.

In lines 9-35, we define the MailSender class. The class has the single method sendMail() (line12), which takes four arguments: sender’s E-mail address, recipient’s E-mail address, messagesubject and, finally, message body text.

Page 239: Using Zend Framework 2

Collecting User Input with Forms 220

In line 18, we create an instance of the Message class. We use the methods provided by this classfor 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 passthe message to the appropriate mail server (see lines 25-26). Since the classes provided by theZend\Mail component may throw an exception on failure, we enclose the block of code with thetry-catch exception handler.

The sendMail() method will return true if the E-mail message sent successfully; otherwise itwill return false (line 33).

Configuring mail system for your web server is a rather complex task. It typicallyrequires installing sendmail and configuring the server’s MXDNS record to use certainmail 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 findadditional information on configuring mail for your particular system online.

Finally, you can instantiate the MailSendermodel 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/

Page 240: Using Zend Framework 2

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 '[email protected]', $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 allowyou 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.

Page 241: Using Zend Framework 2

Collecting User Input with Forms 222

• In line 31, we instantiate the MailSender class with the new operator; in line 32, we call itssendMail() method and pass it four parameters: the sender’s address (here we use “[email protected]”, but you can replace this with the address of your sendmail); therecipient’s E-mail address, the E-mail subject and body.

• If mail has been sent successfully (if the sendMail() method returned true), we redirectthe 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 SendingEmail 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 templatefile to display your form on a web page. In the view template, you need to define the markupusing <form>, <label>, <input>, and possibly other HTML tags.

Additionally, you will have to display error messages if the form validation failed. Because thiswork is rather boring, Zend Framework 2 provides you with special view helpers intended forrendering the form.

For simple forms (which do not show error messages), you can use raw HTML tags forrendering the form and ignore ZF2-provided form view helpers. But, form view helpersare really unavoidable when rendering complex forms that may display validationerrors 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 errormessages 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.

Page 242: Using Zend Framework 2

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 theZend\Form\View\Helper namespace. As you can see from the table, the view helpers can bedivided into the following categories:

• Generic form view helpers. These classes are designed to render the whole form (Formhelper) or its single element (FormElement helper) and possible validation errors (FormElementErrorshelper).

• View helpers for rendering HTML fields of certain types. These allow you to generateHTML 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 theview 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 forrendering 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.

Page 243: Using Zend Framework 2

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 helpersand 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 aspossible and recognize as many field types as possible. So, with this view helper you are able toproduce 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 singleargument – the instance of the element to render. You can retrieve the form element withthe form model’s get() method (see example below).

• The __invoke() method is a convenience wrapper which results in less code to write.

Page 244: Using Zend Framework 2

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 viewhelper which determines the field type automatically and produces the needed HTMLcode.

7.12.2 Rendering an Element’s Validation Errors

The FormElementErrors view helper class allows you to produce HTML markup for fieldvalidation errors (if present). If there are no validation errors for certain element, this view helperdoes 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. Anexample 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:

Page 245: Using Zend Framework 2

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, andvalidation errors. With this class, you are able to render these in a single step. This helper isflexibly configurable, so you can apply a different decoration to the form row. The methods ofthis 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,

$renderErrors, $partial)

Renders the form row (convenience wrapper).

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

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:

Page 246: Using Zend Framework 2

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 theclosing </form> tag. But its major purpose is to render the entire form and all of its fields witha 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 itselements (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 theContact Form

Now we are ready to define the presentation for our feedback form. If you remember, earlier weadded the contact-us.phtml view template in application/index/ directory under the module’sview/ directory. Replace the code in that file with the following:

Page 247: Using Zend Framework 2

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. Pleasenote that calling this method is very important. If you forget to do that, there may be someundesired rendering problems.

• In line 6, we call the openTag()method of the Form view helper. Its purpose is to render theopening <form> tag and its attributes. The method takes a single argument – an instanceof the form model. Paired closing </form> tag is rendered in line 23 with the help of thecloseTag() method of the Form view helper.

• In lines 8-10, we render the E-mail field’s label, the text field itself and (possible) validationerrors 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 getan 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 textarea field.

• In line 20, we render the Submit button.

When the view template renderer evaluates this code, it will produce the HTML output likebelow:

Page 248: Using Zend Framework 2

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 andFormLabel view helpers. You may use the generic FormRow or Form view helpers if youwant to reduce the amount of code to write, but this may result in less control of formdecoration.

If certain fields have validation errors, those errors will be outputted below the field in the formof the <ul> unordered HTML list. For example, if you enter the “123@hostname” into E-mailform 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 TwitterBootstrap CSS classes to give the form a nice, professional-looking appearance. To add Bootstrapstyling to the form, you have to modify the code in the .phtml file to make it look like below:

Page 249: Using Zend Framework 2

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'=>'[email protected]'

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

Page 250: Using Zend Framework 2

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-controlCSS class to every input field in the form.We didthat with the setAttribute() method (see lines 5, 10 and 15). With that method, we also addedthe “placeholder” attribute to define the nice-looking placeholder text when a field is empty. Forthe “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 ofthe 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 givethem a nice visual appearance, we add a couple of CSS rules to the style.css file inAPP_DIR/publicdirectory:

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

Page 251: Using Zend Framework 2

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 “ErrorSending Email” pages.

Add the thank-you.phtml view template in application/index/ directory under the module’sview/ 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 SendingEmail 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 thevalidation errors (figure 7.20).

Entering the correct E-mail, subject and message text and submitting the form results in sendingthe 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 theContact Us form in action in the FormDemo sample application bundledwith this book.

Page 252: Using Zend Framework 2

Collecting User Input with Forms 233

Figure 7.19. Contact Form

Figure 7.20. Form validation errors

Page 253: Using Zend Framework 2

Collecting User Input with Forms 234

Figure 7.21. Thank You page

Figure 7.22. Error Sending Email page

Page 254: Using Zend Framework 2

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 ofelements (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 forelement definition and validation, and form presentation implemented with the help of specialview helpers.

To create a form model, you write a class deriving from the Form base class. The form model isinitialized 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 aspart of a HTTP request. Once the user submits the form, you can extract the form data in yourcontroller and ask the form model to validate it.

For checking and filtering the user-entered data, filters and validators are utilized. You use theInputFilter 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.

Page 255: Using Zend Framework 2

8. Transforming Input Data withFilters

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. Forexample, filters may be used in a controller action to transform the data passed as GETand/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. ZendFramework 2 provides a lot of standard filters that can be used for creating filtering rules of yourforms (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 toZend\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), whichtakes the single parameter $value. The method transforms the input data and finally returns theresulting (filtered) value.

Page 256: Using Zend Framework 2

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 configurationof 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 thatbase concrete class for most standard filters is the AbstractFilter class, which implements theFilterInterface interface ².

Youmay notice that there is a strange filter called StaticFilterwhich does not inheritfrom 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 instantiationof 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 followinggroups:

• 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 directoryname, etc.);

• filters performing compression and encryption of input data;• filters manipulating string data (case conversion, trimming, character replacement andremoval, 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 alsobe considered standard. For example, the Zend\Filter\File namespace contains several filters applicable to processing file uploads (those filterswill 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 StringToUpperand StringToLower filters, because it provides the string conversion functionality common to both of them. And, the Decompress filter inheritsfrom the Compress filter, because these filters are in fact very similar. By analogy, the Decrypt filter inherits from the Encrypt filter, becausethey are the “mirror reflection” of each other as well.

Page 257: Using Zend Framework 2

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 & timestring in a given format.

BaseName Given a string containing the path to a file or directory, this filter will returnthe trailing name component.

Dir Given a string containing the path of a file or directory, this filter will returnthe parent directory’s path.

RealPath Returns canonicalized absolute pathname.

Compress Compresses the input data with the specified algorithm (GZ by default).

Page 258: Using Zend Framework 2

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 inverseto the Compress filter).

Encrypt Encrypts the input data with the specified cryptographic algorithm.

Decrypt Decrypts the input data previously encrypted with the specified cryptographicalgorithm.

Inflector Performs the modification of a word to express different grammaticalcategories 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 thestring.

StripNewlines Removes new line characters from string (ASCII codes #13, #10).

HtmlEntities Returns the string, converting characters to theircorresponding 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 classwithout 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 mostfrequently 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.

Page 259: Using Zend Framework 2

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 filteringan arbitrary data. To do that, you simply create an instance of the filter class, configure the filterby 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 spacecharacters 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 anE-mail invalid. With the StringTrim filter, you will easily cope with such input errorsand 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, providesthe constructor method which you can (optionally) pass with the complete list of options toinitialize the filter, and the setCharList() and getCharList() methods which can be used forsetting specific filter options.

All standard filters have the constructor method (optionally) accepting an array ofoptions for configuring the filter when instantiating it manually.

Below, we provide two code examples showing equivalent methods of manually creating aninstance of the StringTrim filter, setting its options, and filtering a value.

Example 1. Passing options to the constructor method.

Page 260: Using Zend Framework 2

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(' [email protected] ');

10

11 // The expected output of the filter is the '[email protected]' 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 spacecharacter). Actually, passing the array of options to this filter can be omitted because the filteralready has some default character list to strip off.

In line 9, we call the filter()method and pass it the string value “ [email protected] “ to betrimmed. The expected output of this call is the “[email protected]” 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(' [email protected] ');

13

14 // The expected output of the filter is the '[email protected]' string

In the code above, we create the StringTrim filter object with the help of the new operator (line6).

In line 9, we (optionally) call the StringTrim filter’s setCharList() method to set the list ofcharacters the filter will remove (here, we tell the filter to remove the new line characters, thetabulation character, and the space character). This call is optional because the filter already hassome default character list for stripping off.

And, in line 12, we call the filter() method and pass it the string value “ [email protected]“ to be trimmed. The expected output of this call is the “[email protected]” string.

Page 261: Using Zend Framework 2

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. TheStaticFilter 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(' [email protected] ',

4 'StringTrim', array('charlist' => "\r\n\t "));

5

6 // The expected output of the filter is the '[email protected]' string.

The StaticFilter class provides the execute() static method, which takes three arguments: theinput 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 itssetCharList() method, and pass the input value to its filter() method. This is very usefulbecause 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 objectexplicitly as we did in the previous section; instead, you pass an array configuration to the factoryclass, which automatically constructs the filter for you and (optionally) configures it. We alreadysaw 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 );

Page 262: Using Zend Framework 2

Transforming Input Data with Filters 243

In the code above, we call the add() method provided by the InputFilter container class (line5). The add()method takes an array which has the filters key. You typically register the filtersunder that key (line 7). Filters registered under that key are inserted in a filter chain in the orderthey appear in the list.

A filter configuration typically consists of the name (line 9) and options (line 10). The name is afully 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 instantiatesthe filter, it passes the list of options to the filter’s constructor method, and the constructorinitializes 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 nameor its short alias when instantiating the filter from the array. The short aliases for the standardfilters 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 classZend\Filter\StringTrim has the short alias StringTrim.

The filter plugin manager is internally used by the InputFilter container class for instantiatingthe 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 suchfilter, it will return the array as is.

Some filters can work with data in certain format only (e.g., with dates only). If filtering ofinput data is impossible (for example, when you pass the filter some wrong data that it isunable to process), the filter()methodmay throw a Zend\Form\Exception\RuntimeExceptionexception. 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 inincorrect format and cannot be filtered.

It is recommended to read filter’s documentation carefully to know what to expect ofthe filter you plan to use in your form.

Page 263: Using Zend Framework 2

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 themethods (and options) a filter has and provide code examples showing how to instantiate thefilter and apply it to input data. If you need to use a filter not covered in this section, please referto 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 inputdata 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 integernumeric 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 asis.

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

In line 7, we pass an array to the filter. Because the Int filter works with scalar values only, itreturns 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 orfalse). This filter can be used for filtering check box form fields.

Its public methods are listed in table 8.3.

Page 264: Using Zend Framework 2

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 theflag is set to false), it will cast only from types defined by the setType() method, and all othervalues will be returned as is.

The setType() filter’s method allows to define from which types to cast. This method acceptsthe single argument $type, which can be either an OR combination of TYPE_-prefixed constantsor an array containing the literal equivalents of the constants. Possible constants accepted by thesetType() 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. (Thisis the default behavior.)

TYPE_FALSE_STRING 128 “false” Converts a string containing theword “false” to a boolean false.

TYPE_LOCALIZED 256 “localized” Converts a localized string whichcontains 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:

Page 265: Using Zend Framework 2

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 andfalse values. This method accepts a single parameter, which must be an array in the form ofkey⇒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.

Page 266: Using Zend Framework 2

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 ofany 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 returnsa 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. Thismethod takes a single parameter, which can either be a combination of TYPE_-prefixed constantslisted 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 tonull.

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:

Page 267: Using Zend Framework 2

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

This filter can accept a string (e.g., ‘2014-03-22 15:36’), an integertimestamp (like the time() PHP function returns) or an instance ofthe DateTime PHP class. The DateTimeFormatter filter may throw aZend\Filter\Exception\InvalidArgumentException exception if you pass it adate 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 itto 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'.

Page 268: Using Zend Framework 2

Transforming Input Data with Filters 249

Internally, the DateTimeFormatter filter uses the DateTime class from the PHP standardlibrary for converting and formatting dates. For available date formats, please refer tothe 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 tomanipulating file paths.

8.6.2.1 BaseName Filter

The BaseName filter class is just a wrapper on the basename() PHP function. It takes a stringcontaining 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 willreturn 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 containingthe 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 returnthe array as is.

Below, a code example demonstrating the usage of the Dir filter is provided.

Page 269: Using Zend Framework 2

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 expandsall symbolic links and resolves references to ‘/./’, ‘/../’ and extra ‘/’ characters in the input pathand 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 canbe 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 anonexisting 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'.

Page 270: Using Zend Framework 2

Transforming Input Data with Filters 251

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

8.6.3 Filters Performing Compression and Encryption of InputData

In this section, we will consider several filters from the group of filters related to compressingand encrypting the input data. These filters are not very usable for filtering form data but canbe 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. Forexample, 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. Theadapter class must implement the CompressionAlgorithmInterface interface. You attach anadapter 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). Thoseclasses 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/

Page 271: Using Zend Framework 2

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 forarchiving while preserving file system information such as user and grouppermissions, 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 speedcost.

Snappy Snappy⁶ is a fast data compression and decompression library developed by Googlebased on ideas from LZ77.

Rar RAR is an archive file format that supports data compression, error recovery, and filespanning.

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/

Page 272: Using Zend Framework 2

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 thetestfile.txt file.

The Decompress filter is a “mirror reflection” of the Compress filter and can be used byanalogy. 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’spublic 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 adapterto the Encrypt filter with the setAdapter() method, and the adapter performs the concreteencryption. An adapter class must implement the EncryptionAlgorithmInterface interface.

There are several standard adapter classes available (see figure 8.3 below). Those classes live inthe Zend\Filter\Encrypt namespace.

• BlockCipher – implements symmetric block cipher algorithm.• Openssl – uses an encryption algorithm from the OpenSSL library.

Page 273: Using Zend Framework 2

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 byanalogy. 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 tomanipulating string data.

8.6.4.1 StringToLower Filter

The StringToLower filter class is designed for converting the input string data to lowercaseletters. 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.

Page 274: Using Zend Framework 2

Transforming Input Data with Filters 255

By default, the filter behaves like the strtolower() PHP function. Given a string, it returnsthe 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 filterto 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 notaffected 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 donot 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 astring data. This filter is a wrapper over the preg_replace() PHP function. The public methodsof the filter are provided in table 8.13 below.

Page 275: Using Zend Framework 2

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 inputstring. It allows to explicitly define the tags which should not be stripped out. Additionally, itprovides an ability to specify which attributes are allowed across all allowed tags and/or specifictags 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.

Page 276: Using Zend Framework 2

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 isnon-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 anynewline 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 filteris 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 ascheme part to it. The public methods of the filter are provided in table 8.15 below.

Page 277: Using Zend Framework 2

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 defaultscheme if set.

setDefaultScheme($defaultScheme) Set the default scheme to use when parsing schemelessURIs.

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

5. The query part is sanitized, URL-decoding everything that doesn’t need to be encoded andURL-encoding everything else.

The URL normalization procedure rules may be different for different protocols (schemes). Ifthe URL doesn’t contain the scheme part, the http scheme is assumed by default. You mayuse the UriNormalize filter’s setDefaultScheme() method to set the default scheme for URLnormalization. It accepts any of the following schemes: http, https, file, mailto, urn, and tag.

Additionally, the UriNormalize filter’s setEnforcedScheme() allows to override the defaultscheme part by the so-called “enforced scheme”, if the original URL doesn’t contain schemepart.

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

Page 278: Using Zend Framework 2

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. Whensuch a compound filter is run, the value filtered by the first filter is passed as an input for thesecond one, and then the value filtered by the second filter will be passed to the third one, andso on.

The FilterChain class is internally used by the InputFilter container class for storingthe 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 thechain. Filters are run in the order in which theywere 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 bythe 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:

Page 279: Using Zend Framework 2

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(" [email protected]<html>\n ");

// The expected output is '[email protected]'.

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” keywhere you register the filters youwant to insert into the chain.For each attached filter, you provide the following subkeys: * “name” is the fully qualified classname 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 thepriority of the filter in the chain. Filters with higher priority are visited first. The default valuefor the priority is DEFAULT_PRIORITY.

Finally, in line 20, we call the filter() method, which walks through the chain and passes thefiltered 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 mayoften need to trim a string or convert it to lowercase. However, sometimes there are cases whereyou cannot use a standard filter. Here, the Callback filter will be handy.

Page 280: Using Zend Framework 2

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 filteringalgorithm to the data.

You implement your custom filtering algorithm as a callback function or a callbackclass method. A callback is a function or a public method of a class which is called bythe Callback filter and is passed the value to be filtered and, optionally, user-definedargument(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 ourContactForm 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 countrycode followed by the three-digit area code enclosed into braces. The rest of the phone consistsof 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 thisphone format as the “international” format.

The international phone format is required for making telephone calls between differentcountries (or areas). If the calls are made within the same area, the telephone number may simplylook like “456-7890” (we just omit the country code and area code). We will refer to this phoneformat as the “local” phone format.

To make our filter as generic as possible, we assume that the user is required to enter the phonein international format for some forms and in local format for other forms. Because some sitevisitors may enter their phone number in a format different from what is required, we want toapply 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.

Page 281: Using Zend Framework 2

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

Page 282: Using Zend Framework 2

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. Thefield is a usual text input field, and we already had some experience of working with such fieldsearlier.

Then, in lines 24-39, we add a validation rule for the “phone” field of our form. Under the “filters”

Page 283: Using Zend Framework 2

Transforming Input Data with Filters 264

key (line 27), we register the Callback filter (here, we use the short alias Callback, but you canalternatively 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 themethod to call, respectively. In this example, the callback is the filterPhone() method of theContactForm 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 valueis 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 emptyphone 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 thedash 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 webbrowser. If you enter some phone number in an incorrect format, the filter will fix the phonenumber 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 theFilterInterface interface. Then, this filter may be used in forms of your web application (or, ifyou wish, outside a form).

To demonstrate how to create your own filter, we will write the PhoneFilter class encapsulatingthe phone filtering algorithm we used with the Callback filter example.

As you may remember, the base concrete class for all standard filters is theAbstractFilter class. By analogy, we will also derive our custom PhoneFilter filterfrom that base class.

We plan to have the following methods in our PhoneFilter filter class (see table 8.18):

Page 284: Using Zend Framework 2

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 toset 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’ssource 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.

Page 285: Using Zend Framework 2

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

Page 286: Using Zend Framework 2

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 AbstractFilterbase class to reuse the functionality it provides. Line 4 contains the short alias for the AbstractFilterclass.

In lines 11-12, for convenience, we define the phone format constants (PHONE_FORMAT_INTL forinternational 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 keynamed “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 filteris constructed by the factory class, the factory will pass filter options to the filter’s constructorthrough this argument.

In lines 31-37 and 43-46, we have the setFormat() and getFormat() methods that allow to setand retrieve the current phone format, respectively.

In lines 49-86, we have the filter() method. This method encapsulates the phone numberfiltering algorithm. It takes the $value parameter, transforms it by taking the selected phoneformat 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 theContactForm::addInputFilter() method:

Page 287: Using Zend Framework 2

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 bundledwith this book. Open the “http://localhost/contactus” page in your web browser. If you entersome 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. ZendFramework 2 provides a lot of standard filters that can be used for creating filtering rules of yourforms (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;

Page 288: Using Zend Framework 2

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

Page 289: Using Zend Framework 2

9. Checking Input Data withValidators

In this chapter, we will provide an overview of standard validators that can be used with yourforms. A validator is a class designed to take some input data, check it for correctness, andreturn a boolean result telling whether the data is correct (and error messages if the data hassome errors).

In general, you even can use validators outside forms to process an arbitrary data. Forexample, validators may be used in a controller action to ensure that data passed asGET 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 booleanresult telling whether the data is correct. If the data is incorrect, the validator generates the listof 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 (itbelongs 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

Page 290: Using Zend Framework 2

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) andgetMessages() method (line 11).

The first one, isValid()method, is intended to perform the check of the input value (the $valueparameter). 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 haveadditional methods. For example, many validator classes have methods allowing toconfigure the validator (set validation options).

9.2 Standard Validators Overview

Standard ZF2 validators are provided by the Zend\Validator component ¹. Standard validatorclasses inheritance is shown in figure 9.1. As you can see from the figure, most of them arederived from AbstractValidator base class.

Standard validators together with their brief description are listed in table 9.1. As you may noticefrom the table, they can be roughly divided into several groups:

• validators for checking value conformance to certain format (IP address, host name, E-mailaddress, 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, StaticValidatorand Callback).

¹Here, we only consider the standard validator classes belonging to the Zend\Validator namespace. But, actually there are more validatorsthat can be considered as standard. We will cover them in further chapters.

Page 291: Using Zend Framework 2

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

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 cardnumber (Luhn algorithm, mod-10 checksum).

Iban Returns true if the value is a valid International Bank Account Number(IBAN); otherwise returns false.

Page 292: Using Zend Framework 2

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 StandardBook 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; otherwisereturns 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 previouslygenerated 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 explicitlyinstantiating it.

Callback This validator allows to execute a custom validation algorithm through theuser-provided callback function.

ValidatorChain Wrapper validator allowing to organize several validators in a chain. Attachedvalidators are run in the order in which they were added to the chain (FIFO).

9.3 Validator Behaviour in Case of Invalid orUnacceptable Data

If you pass a validator some data that doesn’t pass the check, the validator internally creates thelist of error messages that can be retrieved with the getMessages() method. For example, look

Page 293: Using Zend Framework 2

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 thevalidation failed. The array keys are validation failure message identifiers, and the array valuesare the corresponding human-readable message strings.

If isValid() method was never called or if the most recent isValid() call returned true, thenthe 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 mayrequire that the input data be a string, but not an array). If you pass it data in unacceptableformat, the validator may throw an Zend\Validator\Exception\RuntimeException exceptionor raise a PHP warning.

It is recommended to check certain validator’s documentation to be aware of its actualbehaviour 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 themost 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.

Page 294: Using Zend Framework 2

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 arbitrarydata. In order to do that, you simply create an instance of the validator class, configure thevalidator 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-mailaddress for conformance to RFC-2822² standard. An E-mail address typically consists of thelocal part (user name) followed by the “at” character (@), which is in turn followed by the hostname. For example, in the “[email protected]” 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 addresseson your forms for correctness. The validator will check for the correctness of the localpart and the host name, for presence of the “at” character (@) and, optionally, willconnect 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 optionsallowing to configure it.

isValid($value) Returns true if the value is a valid E-mail addressaccording to RFC-2822; otherwise returns false.

getMessages() If validation failed, this method will return an arrayof error messages.

useDomainCheck($domain) Tells the validator to check the host name part forcorrectness.

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; otherwisereturns false.²https://tools.ietf.org/html/rfc2822

³AnMX record is a type of record used in the Domain Name System (DNS). MX records define one or several mail server addresses assignedto recipient’s domain.

Page 295: Using Zend Framework 2

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

As you can see from table, the EmailAddress validator, additionally to the isValid() andgetMessages() methods, provides the constructor method to which you can (optionally) passthe complete list of options for initializing the validator.

All standard validators have the constructor method (optionally) accepting an array ofoptions for configuring the validator when instantiating it manually.

The EmailAddress class also provides a number of methods that can be used for setting specificvalidator options.

The useDomainCheck() method tells whether to check the host name for correctness, or not. Bydefault, this check is enabled. The setAllow()method provides an ability to specify which typesof 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 thehost name part of an E-mail address. Optionally, you can attach a custom host namevalidator by using the setHostnameValidator() method, however it is unlikely youwill need to do such.

The useMxCheck()method tells whether the validator should connect to the recipient’s host andquery the DNS server for the MX record(s). If the server has no MX records, than the validationfails. You can additionally use the useDeepMxCheck()method to tell the validator to compare themail server addresses extracted from the MX records against the black list of reserved domainnames, and perform additional checks per each detected address.

It is not recommended to perform MX check (and deep MX check), because that maytake a lot of time and increase the web page load time. By default, these checks aredisabled.

Below, we provide code examples showing two equivalent methods of manual creating of aninstance 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.

Page 296: Using Zend Framework 2

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('[email protected]'); // 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-mailaddress to be a domain name, an IP address or local network address. Also, we use the mxCheckand 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 “[email protected]” tobe 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 tofail (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);

Page 297: Using Zend Framework 2

Checking Input Data with Validators 278

13 $validator->useDeepMxCheck(true);

14

15 // Validate an E-mail address.

16 $isValid = $validator->isValid('[email protected]'); // 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 theuseMxCheck() 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 “[email protected]” tobe 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 expectedto 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. TheStaticValidator 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 EmailAddressvalidator, 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('[email protected]',

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.

Page 298: Using Zend Framework 2

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, callits setAllowDns(), useMxCheck() and useDeepMxCheck() methods, and pass the input value toits 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-readablevalidation errors. However, since the StaticValidator is designed to be used outsideforms, and not intended for displaying results to a human, this seems to be not a bigdisadvantage.

9.4.3 Method 3. Using an Array Configuration

When using validators with form’s validation rules, you typically do not construct a validatorobject explicitly as we did in the previous section, instead you pass an array configuration to thefactory 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 Chapter7.

For example, let’s show how to construct the same EmailAddress filter with the help of thefactory:

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 (line5). The add() method takes an array which has the validators key. You typically register thevalidators under that key (line 7). Validators registered under that key are inserted into validatorchain in the order they appear in the list.

Page 299: Using Zend Framework 2

Checking Input Data with Validators 280

A validator configuration typically consists of the name (line 9) and options (line 10). The nameis 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 thefactory 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 validatorclass name or its short alias. The short aliases for the standard validators are defined by theValidatorPluginManager class.

The ValidatorPluginManager class defines validator aliases.

A standard validator’s alias is typically the same as class name. For example, the classZend\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 themethods (and options) a validator has, and provide a code example showing how to instantiateand apply the validator to input data.

9.6.1 Validators for Checking Value Conformance to CertainFormat

In this section, we will consider usage examples of the validators from the group of validatorsdesigned 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 valueis an IPv4 ⁵ address, IPv6 ⁶ address, IPvFuture ⁷ address, or IPv6 literal ⁸ address, the validatorreturns boolean true; otherwise it returns false. On failure, error messages can be extractedwith 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.)

Page 300: Using Zend Framework 2

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 ofallowed host name types. The types are:

Page 301: Using Zend Framework 2

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 acombination 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 validatoroptions:

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

Page 302: Using Zend Framework 2

Checking Input Data with Validators 283

4. Each domain part is checked based on the rules for acceptable domain names. If a domainname is an IDN ⁹, it is checked against the rules for valid IDNs. (You can disable IDN checkwith 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 usualURL.

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 UniformResource Locator (URL) is a type of URI. But, that doesn’t mean all URIs are URLs.

Page 303: Using Zend Framework 2

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 anURI string. By default, Zend\Uri\Uri class is used as the URI handler. (You can set your customURI handler with the setUriHandler() method, if you wish.)

AnURI 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 considerabsolute and/or relative URIs acceptable. For that, you use the setAllowAbsolute() andsetAllowRelative()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:

Page 304: Using Zend Framework 2

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 standardlibrary for converting and formatting dates. For available date formats, please refer tothe 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 returnstrue if the string matches the regular expression, otherwise it returns false. On failure, errormessages 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.

Page 305: Using Zend Framework 2

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 thePCRE Patterns section of the PHP documentation.

Below, a code example demonstrating the usage of the Regex validator is provided. It uses theregular expression to check if the input string is a valid IPv4 address (such address typicallyconsists 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 GivenRange

In this section, we will consider usage examples of the validators from the group of validatorsdesigned 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 whenworkingwith form elements or other user input, where you can use it to ensure required elementshave values associated with them.

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

Page 306: Using Zend Framework 2

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 methodaccepts the single argument $typewhich can be either an OR combination of the constants listedin 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 emptyvalue.

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

OBJECT_STRING 512 “objectstring” Returns false when an object is givenand it’s __toString() method returns anempty string.

OBJECT_COUNT 1024 “objectcount” Returns false when an object is given, ithas an Countable interface and it’s countis 0.

ALL 2047 “all” Consider all above types as empty values.

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

Page 307: Using Zend Framework 2

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), eitherinclusively (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 thegiven range, it compares if the value is greater or equal to its lower bound, and if the value islower or equal to its upper bound). You can change this with the setInclusive()method. It tellsthe validator whether to perform inclusive comparisons (pass true as the argument) or exclusivecomparisons (pass false as the argument).

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

Page 308: Using Zend Framework 2

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. Thepublic 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() methodwill 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 usesetRecursive() method. This method takes the single boolean flag. If the flag is true, than thesearch 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 valueand 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_VULNERABILITYDonot perform strict checkof variable type, but prevent false positive comparisons of string to integer (e.g. "asdf" ==

0). This is the default option.

Page 309: Using Zend Framework 2

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 andno 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 arethree possible ways you can do that:

• Use only the setMin() method to allow strings with a lower-bound minimum length, butunbound upper length;

• Use only the setMax() method to allow strings with zero minimum length and an upper-bound maximum length;

Page 310: Using Zend Framework 2

Checking Input Data with Validators 291

• Use both the setMin() and setMax() methods to allow strings with a length layingbetween the lower and upper bounds.

By default, the PHP engine uses the UTF-8 encoding for strings. If your input string uses adifferent 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. TheValidatorChain validator’s isValid() method returns true if all validators in the chain returntrue; otherwise it returns false.

The ValidatorChain class is internally used by the InputFilter container class forstoring 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 returntrue.

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,

$breakChainOnFailure)

Adds a validator to the beginning of the chain.

Page 311: Using Zend Framework 2

Checking Input Data with Validators 292

Table 9.13. Public methods of the ValidatorChain validator

Method name Description

attachByName($name, $options,

$breakChainOnFailure)

Use the plugin manager to add a validator byname.

prependByName($name, $options,

$breakChainOnFailure)

Use the plugin manager to prepend a validator byname.

merge($validatorChain) Merge the validator chain with the one given inparameter.

An example validator chain is shown in figure 9.2. It consists of the NotEmpty validator followedby the StringLength validator, which in turn is followed by the Date validator. When this chainis 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 torange (1, 16), inclusively, and finally, the Date validator is run checking that the input value is adate 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 ownchecking algorithm to the data. The public methods provided by the Callback validator are listedin table 9.14.

Page 312: Using Zend Framework 2

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() andsetCallbackOptions() methods that can be used to set the callback function or class methodand (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 ourContactForm form model class. The validator would check a telephone number entered by sitevisitor.

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 filteringoperation, we will use the Callback wrapper validator. To do that, we will make the followingchanges 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(

Page 313: Using Zend Framework 2

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

Page 314: Using Zend Framework 2

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 threearguments: the $value parameter is the phone number to validate, the $context parameterreceives the values of every field of the form (it may be needed for some validators to referto the values of other form fields, too); and the $format parameter is the expected format of thephone 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 theselected phone number format.

2. Match the phone number against the regular expression pattern for the selected phoneformat.

9.7 Writing Own Validator

An alternative of using the Callback validator is writing your own validator class implementingthe ValidatorInterface interface. Then, this validator may be used in forms of your webapplication.

To demonstrate how to create your own validator, we will write the PhoneValidator classencapsulating the phone validation algorithm we used with the Callback validator example.

As you might remember, the base concrete class for all standard validators is theAbstractValidator 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 toset 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.

Page 315: Using Zend Framework 2

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 “Thephone number must be a scalar value”;

• If the international phone format is selected, and the phone number entered doesn’tmatch this format, the validator will generate the message “The phone number must be ininternational format”;

• If the local phone format is selected and the phone number entered doesn’t match theformat, the validator will generate the message “The phone number must be in localformat”.

To start, create the PhoneValidator.php file in the Application/Service directory under themodule’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.

Page 316: Using Zend Framework 2

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

Page 317: Using Zend Framework 2

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 AbstractValidatorbase class to reuse the functionality it provides. Line 4 contains the short alias for the AbstractValidatorclass.

In lines 11-12, for convenience, we define the phone format constants (PHONE_FORMAT_INTL forinternational 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 singlekey 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 threedifferent 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 beforeerror message identifiers and their textual representations. The textual messages are intendedfor displaying to a human.

In lines 33-44, we have the constructor method which takes the single argument $options. Whenconstructing the validator manually, you may omit this parameter. But, when the validator is

Page 318: Using Zend Framework 2

Checking Input Data with Validators 299

constructed by the factory class, the factory will pass validation options to validator’s constructorthrough this argument.

In lines 47-56 and 59-64, we have the setFormat() and getFormat() methods that allow to setand retrieve the current phone format, respectively.

In lines 59-99, we have the isValid() method. This method encapsulates the phone numberchecking algorithm. It takes the $value parameter, performs the regular expression match, andreturns true on success.

On failure, the isValid() method it returns the boolean false, and the list of errors can beretrieved by the getMessages() method.

You might notice that we didn’t define the getMessages() method in ourPhoneValidator class. This is because that we inherited this method from theAbstractValidator base class. Inside of our isValid() method, for generating errormessages, we also used the error() protected method provided by the base class (lines62, 92, 94).

9.7.1 Using the PhoneValidator Class

When the PhoneValidator validator class is ready, you can easily start using it in the feedbackform (or in another form) as follows. It is assumed that you call the following code inside of theContactForm::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 applicationbundled with this book. Open the “http://localhost/contactus” page in your web browser. If you

Page 319: Using Zend Framework 2

Checking Input Data with Validators 300

enter some phone number in an incorrect format, the validator will display an error (see figure9.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 yourcontroller to transform and check the data extracted from GET and/or POST variables.

Page 320: Using Zend Framework 2

Checking Input Data with Validators 301

Let’s assume we implement a payment gateway system and need to create a web page displayinga payment history for the given credit card on given date. This page can be handled by somepaymentHistoryAction() action of a controller class, and the credit card number and date will beextracted from GET variables. For the paymentHistoryAction()method, we need to implementsome 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 }

Page 321: Using Zend Framework 2

Checking Input Data with Validators 302

Inside the action method, we use the params() controller plugin (lines 16-17) to retrieve twovariables 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 thecard 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 booleanresult 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 checkingalgorithm to the input data. In such case, you may use either the Callback validator or writeyour own custom validator class.

Page 322: Using Zend Framework 2

10. Uploading Files with FormsIn this chapter, you will learn about uploading files with forms. First, we will review the basictheory like HTTP file upload capability and binary content transfer encoding, and then providea 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 typicallytransmitted through HTTP POST method ².

By default, HTTP uses the URL encoding for transfers of form data, and you could see howthat encoding looks like when reading the GET and POST Methods section of the previouschapter. However, this is inefficient for uploading large files, since URL-encoding binary datadramatically increases the length of the HTTP request. For the purpose of uploading files, itis instead recommended to use the so called “binary transfer encoding” described in the nextsection.

10.1.1 HTTP Binary Transfer Encoding

A simple HTML form capable of file uploads is shown in the code example below. The binaryencoding 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 greatlyincreases the URL length.

Page 323: Using Zend Framework 2

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 utilizeeffective 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 allowsite 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 userpicks some file and clicks the Submit button on the form, the web browser will send an HTTPrequest to the web server, and the request will contain the data of the file being uploaded. Theexample 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--

Page 324: Using Zend Framework 2

Uploading Files with Forms 305

As you can see from the example above, the HTTP request with “multipart/form-data” encodingtype looks analogous to a usual HTTP request (has the status line, the headers, and the contentarea), however it has the following important differences:

• Line 5 sets the “Content-Type” header with “multipart/form-data” value; The form isassembled of the fieldsmarked by the “boundary” – a unique randomly generated sequenceof 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 transmittedin 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 fileand modify the post_max_size and upload_max_filesize parameters (please refer toAppendix A for information on how to do that). Setting these with 100M allows toupload files up to 100 Mb in size, and this would typically be sufficient. If you planto upload very large files up to 1 GB in size, than better set these with 1024M. Do notforget 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 toa temporary location (usually to system temporary directory which is /tmp in Linux andC:\Windows\Temp inWindows). 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 twoare used to store the GET and POST variables, respectively, while the first one is usedto store information about uploaded files.

For example, for the above mentioned simple upload form, the $_FILES super-global array willlook 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 uploadedfile. For each uploaded file, it contains the following information:

Page 325: Using Zend Framework 2

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 meansthe 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 thePHP script execution ends. So, if you want to save the uploaded files to some directory for lateruse, 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 oneis the destination file name.

You might be confused why you cannot use the usual rename() PHP function formoving the temporary uploaded file to its destination path. PHP has special functionfor moving uploaded files for security reasons. The move_uploaded_file() function isanalogous to rename() function, but it takes some additional checks to ensure the filewas really transferred through HTTP POST request, and that the upload process hasfinished without errors.

The following code example shows how to move the file uploaded with the simple form we haveconsidered 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 uploadedfile.

In line 2, we call the move_uploaded_file() function and pass it two arguments: the path to thetemporary 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 theuploaded file under another name than its original name, you can specify the full filepath instead of the directory name.

In line 3, we check the returned value of the function. If the operation is successful, the functionwill return true. If some error occurs (for example, if directory permissions are insufficient tosave 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. Forexample the “text/plain” MIME type is assigned to a text file, while the “application/octet-stream” MIME type is assigned to a binary file.

Page 326: Using Zend Framework 2

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

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 accessingthe Request object, and the getFiles()method of the request object to retrieve the informationabout 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 samefromFiles() method and pass it the name of the file field to retrieve. This retrieves the singlefile 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 theZend\Form\Element\File class as follows:

Page 327: Using Zend Framework 2

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 theconfiguration array describing the element. The type key of the array (line 5) must be eitherZend\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 mayneed to check that:

• the file(s) were really uploaded through HTTP POST request, and were not just copiedfrom 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 toobig);

• 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 table10.1). Those validator classes belong to Zend\Validator component and live in Zend\Validator\Filenamespace.

Page 328: Using Zend Framework 2

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 checkswhether it lies in a given range.

Upload FileUpload Performs security checks ensuring that all given fileswere really uploaded through HTTP POST and therewere no upload errors.

UploadFile FileUploadFile Performs security checks ensuring that a file reallywas uploaded through HTTP POST and there wereno 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 ina given range.

Extension FileExtension Checks that the extension of a file belongs to a set ofallowed extensions.

ExcludeExtension FileExcludeExtension Checks that the extension of a file DOES NOT belongto a set of extensions.

MimeType FileMimeType Checks that the MIME type of a file belongs to the listof allowed MIME types.

ExcludeMimeType FileExcludeMimeType Checks that the MIME type of a file DOES NOTbelong 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 givenrange.

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 givenhash(es).

Crc32 FileCrc32 Checks that the file content has the given CRC32check 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 followinggroups:

Page 329: Using Zend Framework 2

Uploading Files with Forms 310

• validators checking whether the file(s) were really uploaded through HTTP POST andupload 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, theirshort 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 laterin this chapter.

10.5 Filtering Uploaded Files

Zend Framework 2 provides several filters intended for “transforming” file fields. Those filterclasses (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 shortaliases (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 algorithmsavailable (MD5, SHA-1, CRC32, etc.)

Page 330: Using Zend Framework 2

Uploading Files with Forms 311

The Encrypt and Decrypt filters allow to apply various encryption/decryption algorithms to theuploaded file (concrete algorithm is attached by specifying the certain adapter). The LowerCaseand 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 usesthe rename() PHP function internally, and that’s why it is in general not recommended to usethis filter with uploaded files because of security reasons.

The RenameUpload filter seems to be much more useful than other filters, because it allows toencapsulate the call of the move_uploaded_file() function and move/rename the uploaded filefrom a temporary location to its persistent directory. We will show how to use the RenameUploadfilter 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 storedin an InputFilter container which consists of inputs (an input is typically represented by theInput class belonging to the Zend\InputFilter namespace. For usual form fields, the filters areexecuted 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 beutilized 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 theusual Input class.

In your form model’s addInputFilter() private method, you add the validation rules for thefile 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 needto encrypt an uploaded file or convert it to lower case letters.

Page 331: Using Zend Framework 2

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

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 entrywith 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 filtersinserted 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 areexecuted after filters. However, for file uploads, this sequence is opposite.

For file uploads, validators are executed before filters. This behaviour is inverse to theusual 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 astorage directory, renaming it, etc.) Because of that, file validators need to be run first turn, andfilters 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 toform.

• On successful validation, call the getData() to extract the filtered and validated data fromthe 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 understandwhat 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 andvalidated data from the input filter attached to form.

• On failure, call the getMessages() to retrieve the validation error messages.

Page 332: Using Zend Framework 2

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 commoncase), the filters are still executed first for usual inputs, but validators are executed first for fileinputs.

10.7 Controller Action & File Uploads

In this section, we will provide a short code example showing how to handle file uploads in acontroller 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 fileuploads. 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.

Page 333: Using Zend Framework 2

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 actionimplementing 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 datafrom $_POST and $_FILES super-global PHP arrays and merge them into the singlearray (lines 15-19). This is required to correctly handle uploaded files, if any. Thenwe 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 filterattached to the form model. For FileInput inputs, this will execute attached validatorsonly.

• If the data is valid, we call the getData() method (line 28). For the FileInput inputs, thiswill run the attached file filters. The file filters, for example, could move the uploadedfiles 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 Gallerythat will consist of two web pages: the image upload page allowing to upload an image (figure10.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 applicationbundled with this book.

Page 334: Using Zend Framework 2

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

Page 335: Using Zend Framework 2

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 willcall that form model class the ImageForm. This class will allow us to upload an image file to theserver. 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.phpfile 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 ),

Page 336: Using Zend Framework 2

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 anyproblems 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 formuse 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 callform’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 willadd 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 theUploadFile 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 withthe 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:

Page 337: Using Zend Framework 2

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

Page 338: Using Zend Framework 2

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 usingthe 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 allowedrange. In the code above, we check that the image is between 128 pixels and 4096 pixels inwidth, 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 tothe APP_DIR/data/upload directory. The filter will use the same file name for the destination fileas 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. Thisextension is already enabled in Linux Ubuntu, but in Windows, you have to manuallyopen 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 createa service model class encapsulating the functionality for image management. We will call this

Page 339: Using Zend Framework 2

Uploading Files with Forms 320

class ImageManager and put it to Application\Service namespace. We will also register thisservice 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 imagefiles.

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 serviceclass, we improve the separation of concerns and code reusability.

Add the ImageManager.php file to the Application/Service directory under the module’s sourcedirectory. 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 theprivate $uploadDir property ⁶ which contains the path to the directory containing our uploadedfiles (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.

Page 340: Using Zend Framework 2

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 willscan the upload directory and return an array containing the names of the uploaded files. To addthe 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), andif not, we try to create it (line 17). Then, we get the list of files in the directory (lines 24-32) andreturn it to the caller.

Page 341: Using Zend Framework 2

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 uploaddirectory to that file name;

• the getImageFileInfo() method will retrieve MIME information about the file and itssize 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);

Page 342: Using Zend Framework 2

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

Page 343: Using Zend Framework 2

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 anappropriate thumbnail image height (lines 11-16) preserving its aspect ratio. Then, we resize theoriginal 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 theservice manager component of the web application by adding the following lines to themodule.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 yourweb 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. Thecontroller 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 singleimage.

ImageController:indexAction() Displays the image gallery page with the list of uploadedimages.

ImageController:fileAction() Provides an ability to download a full-size image or a smallthumbnail for an image.

Page 344: Using Zend Framework 2

Uploading Files with Forms 325

To start, create the ImageController.php file in the Application/Controller directory under themodule’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\Controllernamespace and added three action method stubs into the class: indexAction() (line 13),uploadAction() (line 19) and fileAction() (line 25). Next, we will populate those actionmethods 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 willhandle the Upload a New Image web page containing the upload form. The form will provide anability to upload an image file to the gallery.

Change the ImageController.php file as follows:

Page 345: Using Zend Framework 2

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

Page 346: Using Zend Framework 2

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 formmodel with the setData() method (line 22).

In line 25, we call the formmodel’s isValid()method. This method runs the input filter attachedto the form model. Since we have only one file input in the input filter, this will only run ourthree 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 theRenameUpload 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 populatethat action method a little bit later.

Now, its time to add the view template for the “upload” action. Add the upload.html viewtemplate 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 applynice-looking Twitter Bootstrap styles to the form’s Submit button.

Page 347: Using Zend Framework 2

Uploading Files with Forms 328

Then, we render the form with the common view helpers that we discussed in Chapter 7. Forrendering 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 actualrendering.

10.8.4.2 Adding Index Action & Corresponding View Template

The second action method we will complete is the indexAction(). This action will handle theImage Gallery page containing the list of uploaded files and their small thumbnails. For eachimage, there will be a button “Show In Natural Size” for opening the image in another tab of theweb 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). Wedo that with the getServiceLocator() method provided by the controller’s base class. ThegetServiceLocator() method returns the ServiceLocatorInterface interface, which providesan ability to retrieve any service registered in the service manager.

Next, in line 12, we use the getSavedFiles() method of the ImageManager class for retrievingthe 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 movingthe image management functionality to the ImageManager service model.

Page 348: Using Zend Framework 2

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>

Page 349: Using Zend Framework 2

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 andoutput the thumbnails of each uploaded images.

For rendering a thumbnail, we use the <img> tag. We set its src attribute with the URL pointingto the “file” action of our ImageController controller. We pass two parameters to the action viathe 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 theBootstrap official documentation.

Below each thumbnail, we put the “Show in Natural Size” link, which points to the “file” actionof our ImageController controller. When site visitor clicks the link, he will be shown with theimage in natural size, and the imagewill 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 methodwill allow to preview an uploaded image or generate a small thumbnail of the image. The actionmethod 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 itssmall copy.

Change the ImageController.php file as follows:

Page 350: Using Zend Framework 2

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

Page 351: Using Zend Framework 2

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-globalarray (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 previouslyregistered in the service manager.

In line 18, we use the getImagePathByName() method provided by the ImageManager service toget the absolute path to the image by its name.

If a thumbnail is requested, we resize the image with the resizeImage() method of theImageManager (line 23). That method returns path to a temporary file containing the thumbnailimage.

Then, we get the information about the image file (its MIME type and file size) with thegetImageFileInfo() method of the ImageManager (line 27).

Finally, we create a Response object, fill its headers with image information, set its content withdata 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 viewtemplate for this action method. By this reason, we do not create the file.phtml viewtemplate file.

10.8.4.4 Registering the ImageController

Finally, register the ImageController in the module.config.php configuration file:

Page 352: Using Zend Framework 2

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 theApache Web Server. In Linux Ubuntu, this is typically accomplished by the following shellcommands (replace the APP_DIR placeholder with the actual directory name of your webapplication):

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 andallow the web server to write to the directory, respectively.

If you now enter the URL http://localhost/application/image/index into your web browser’snavigation bar, you will see the image gallery page like shown in figure 10.4.

Figure 10.4. Image Gallery Page

Page 353: Using Zend Framework 2

Uploading Files with Forms 334

Clicking the Upload More button will open the Upload a New Image page where you can peekan image file for upload. If you pick an unacceptable file (not an image, or too big image), youwill 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 pageand see the uploaded image in the list of thumbnails. Clicking the View Full Size button will openthe image in a new browser tab (see the figure 10.6 below for example).

Page 354: Using Zend Framework 2

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 webapplication bundled with this book.

10.9 Summary

File uploads is a standard HTML form feature. Uploading files is accomplished by setting formcontent encoding to binary encoding type. Zend Framework 2 provides convenient functionalityfor doing file uploads and validating the uploaded files.

Page 355: Using Zend Framework 2

11. Advanced Usage of FormsIn previous chapters, you’ve learned about form usage basics: what HTML forms are and howyou define form models and form presentation in Zend Framework 2. In this chapter, you willlearn 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 thoseelements to your form model (and rendering them in a view template), you will make yourform resistant to hacker attacks.

11.1.1 CAPTCHA

A CAPTCHA (stands for “Completely Automated Public Turing test to tell Computers andHumans Apart”) is a challenge-response test used in web sites for determining whether the useris a human or a robot.

There are several types of CAPTCHA. The most widely used one requires that the user type theletters 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:

Page 356: Using Zend Framework 2

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 anautomated process (so called robot). Usually, such robots send spam messages to forums, hackpasswords on site login forms, or perform some other malicious actions.

The CAPTCHA test allows to reliably distinguish humans from robots, because humansare easily able to recognise and reproduce characters from the distorted image, whilerobots 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 theZend\Captcha component):

• Dumb. This is a very simple CAPTCHA algorithm which requires that site user enter theword letters in reverse order. We will not consider this type in details here, because itprovides too low protection level.

• Image. A CAPTCHA algorithm distorting an image with addition of some noise in formof 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 distortedimages and using them for CAPTCHA test.

• Figlet. An unusual CAPTCHA type using FIGlet program instead of an image distortionalgorithm. The FIGlet is an open-source program which generates the CAPTCHA imageof many small ASCII letters (figure 11.1, b).

The Zend\Captcha component provides a unified interface for all CAPTCHA types (the AdapterInterfaceinterface). The AbstractAdapter base class implements that interface, and all other CAPTCHAalgorithms are derived from the abstract adapter class ². The class inheritance diagram is shownin figure 11.2 below.

As you can see from the figure 11.2, there is another base class for all CAPTCHA types that utilizesome secret word of characters: the AbastractWord class. This base class provides methods forgenerating 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 AbstractAdapterinterface, the caller may use any CAPTCHA algorithm in the same common manner (by calling the methods provided by the base interface).

Page 357: Using Zend Framework 2

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 useCAPTCHA 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 previoussection) from Zend\Captcha component. For this purpose, the element class has the setCaptcha()methodwhich takes either an instance of a class implementing Zend\Captcha\AdapterInterfaceinterface, or an array containing CAPTCHA configuration ³. By the setCaptcha() method, youcan 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 theZend\Form\Form base class. As usual, you can pass it either an instance of the Zend\Form\Element\Captchaclass or provide an array of configuration options specific to certain CAPTCHA algorithm (inthat case, the element and its associated CAPTCHA algorithm will automatically be instantiatedand 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 codeinside 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 classZend\Captcha\Factory.

Page 358: Using Zend Framework 2

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 anarray 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 classkey (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 keyas 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 helperclass (belonging to Zend\Form\View\Helper namespace). But, as you might learn from theprevious chapter, typically you use the generic FormElement view helper instead, like shownin 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 providedby ZF2: the Image, Figlet and ReCaptcha. We will show how to add a CAPTCHA field to thefeedback 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 PNGsupport and FT fonts. For information on how to install the extension, please referto Appendix A.

To add the Image CAPTCHA to your form model, call the form’s add() method as follows:

Page 359: Using Zend Framework 2

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 parametersfor 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 thegenerated distorted images (in this example, we will save the images to the APP_DIR/pub-lic/img/captcha directory).

Page 360: Using Zend Framework 2

Advanced Usage of Forms 341

• the suffix parameter (line 23) defines the extension for a generated image file (“.png” inthis example).

• the imgUrl parameter (line 24) defines the base part of the URL for opening generatedCAPTCHA images in a web browser. In this example, site visitors will be able to accessCAPTCHA images using URLs like “http://localhost/img/captcha/<ID>”, where ID is aunique ID of certain image.

• the imgAlt parameter (line 25) is an (optional) alternative text to show if CAPTCHA imagecan’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 TTFfont, for example, from here⁴. In this example, we use Thorne Shaded font, which wedownloaded 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 theCAPTCHA images. Once an image expires, it is removed from disk.

• the dotNoiseLevel parameter (line 31) and lineNoiseLevel parameter (line 32) define theimage 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 templatefile:

<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 CAPTCHAimages. Adjust directory permissions to make the directory writeable by the ApacheWeb Server.In Linux Ubuntu, this is typically accomplished by the following shell commands (replace theAPP_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 theApache 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 CAPTCHAimage will be generated based on a random sequence of letters and digits saved in session. Youshould see something like in the figure 11.3 below.

⁴http://www.1001freefonts.com/

Page 361: Using Zend Framework 2

Advanced Usage of Forms 342

When you fill the form fields in and press the Submit button, the letters entered into the Humancheck field will be transferred to server as part of HTTP request. Then, on form validation, theZend\Form\Element\Captcha class will compare the submitted letters to those stored in PHPsession. 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 theCAPTCHA 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 fromthe previous example with the following code:

Page 362: Using Zend Framework 2

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 parametersfor 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, youshould see a page like in the figure 11.4 below.

Once the PHP renderer processes the view template, it generates HTML markup for theCAPTCHA 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>

Page 363: Using Zend Framework 2

Advanced Usage of Forms 344

Figure 11.4. FIGlet CAPTCHA

11.1.4 Example 3: Adding reCaptcha CAPTCHA to theContactForm

It may look surprising, but there are two purposes of the reCAPTCHA service: the main one is touse wide community of users to help Google to recognize text on images, and the second one is togenerate CAPTCHA images. How does this work? Well, probably you’ve heard of Google BooksSearch that is an automated service for digitizing books. But, sometimes the text recognitionalgorithms become stuck on certain word or phrase. In this situation, a human attention wouldbe required to recognize the word and type it in. People in Google are smart enough to use otherpeople to do that work for free.

Look at figure 11.1, c for example of reCAPTCHA image. It usually consists of two words. Thefirst word is known to machine, and the second one is the word on which text recognitionalgorithm has stuck. The user enters both words, and only the first one is used for CAPTCHAtest; 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 requiresadditional installation.

Page 364: Using Zend Framework 2

Advanced Usage of Forms 345

The commands above make your site’s directory to be the current working directory and runComposer dependency manager to install the component.

Next, you have to register on recaptcha.net⁶ web-site (figure 10.5) and get the public and privatekeys from there.

Figure 11.5. ReCaptcha Registration Page

To add the reCAPTCHA element to your form model, replace the form element definition fromthe 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

Page 365: Using Zend Framework 2

Advanced Usage of Forms 346

15 ),

16 ),

17 ));

Above, the captcha key of the configuration array (see line 9) contains the following parametersfor 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 valueof the public key acquired from recaptcha.net, and the pubKey parameter with the valueof 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 seesomething 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 totransmit an HTTP-request to an arbitrary site. Through the CSRF attack, the malicious script is

Page 366: Using Zend Framework 2

Advanced Usage of Forms 347

able to send unauthorized commands from a user that the web-site trusts. This attack is typicallyperformed on pages containing forms for submission of some sensitive data (e.g. money transferforms, 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 notethat the SSL-protected connection (HTTPS) is used here, but it doesn’t protect from suchkind of attacks.

2. Typically, you set check on the “Remember Me” check box of the login form to avoidentering user name and password too often. Once you logged in to your account, yourweb 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.phpto buy some goods. Please note that this payment form will later be used as a vulnerabilityallowing to perform the CSRF attack.

4. Next you use the same web browser to visit some web site you like. Assume the web sitecontains cool pictures http://coolpictures.com. Unfortunately, this web site is infected by amalicious script, masqueraded by an <img src="image.php"> HTML tag. Once you openthe HTML page in your web browser, it loads all its images, thus executing the maliciousimage.php script.

5. The malicious script checks the cookie variable, and if it presents, it performs the “sessionriding” and can act on behalf of the logged in user. It is now able to submit the paymentform to the payment gateway site.

Page 367: Using Zend Framework 2

Advanced Usage of Forms 348

The above described CSRF attack is possible it the web form on the payment gatewaysite does not check the source of the HTTP request. The people who maintain thepayment 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 inPHP 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 ableto 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 formmodel, you use the Zend\Form\Element\Csrfform 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 arraydescribing the CSRF element. The element will be automatically instantiated and initialized bythe 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”).

Page 368: Using Zend Framework 2

Advanced Usage of Forms 349

In line 4, we set the “name” attribute for the element. In this example, we use “csrf” name, butyou 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\Csrfclass. We set the timeout option to 600 (look at line 7), which means the CSRF check expires in600 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 theCSRF 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 hiddenfield with a randomly generated token. Since the attacker script doesn’t know thistoken, it won’t be able to submit its correct value, thus the CSRF attack becomesprevented.

What happens if CSRF element validation fails?

If during the form validation the CSRF check fails, the form is considered invalid andthe user will see it again to fix input errors, but he won’t see the error message for theCSRF 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 andCsrf. A CAPTCHA is a type of challenge-response test used to determine whether or not the useris a human. CAPTCHA elements are used on form to prevent form submission by a maliciousautomated process (a robot). The latter element, Csrf, is used for Cross-Site Request Forgery(abbreviated as CSRF) hacker attack prevention.

Page 369: Using Zend Framework 2

12. Database Management withDoctrine ORM

Doctrine is an open-source PHP library providing convenient methods for managing yourdatabase in object-oriented way. For working with relational databases, Doctrine provides acomponent named Object Relational Mapper (shortly, ORM). With Doctrine ORM you map yourdatabase 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 theDoctrine library architecture.

Doctrine is a third-party library, it is not part of Zend Framework 2. We cover it in thisbook because it provides an easy way of adding database support to your ZF2-basedweb application.

12.1 Get Blog Example from GitHub

For demonstration of Doctrine ORM usage, in this chapter, we will create a real-life Blog website that does the following:

• Stores blog posts in a database and provides user interface for accessing and managingthose posts.

• It is assumed that the blog has the single author of its posts, while comments can be addedby multiple blog readers.

• The web site has two pages: Home page and Admin page. The first one displays the list ofrecently 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:

Page 370: Using Zend Framework 2

Database Management with Doctrine ORM 351

Figure 12.1. Blog home page

To download the Blog application, visit this page¹ and click theDownload ZIP button to downloadthe 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 thefile, 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 onhow to do that are provided in the next section.

¹https://github.com/olegkrivtsov/using-zend-framework-2-book

Page 371: Using Zend Framework 2

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 databasemanagement system, which is very simple in installation and administration.

For OS-specific instructions on how to install MySQL server and client, please refer toAppendix A.

Once you install MySQL, type the following command from your command shell to log intoMySQL client console:

mysql -u root -p

When asked for, type the password of the root user (the password of the root user is the oneyou’ve specified during MySQL server installation). On successful login, you should see thefollowing welcome message:

Page 372: Using Zend Framework 2

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.) orSQL 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 followingMySQL statementand 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 acommon convention.

Next, we create the user named blog and grant it all privileges for accessing and modifying theblog 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 bloguser. This password should be different than the password of the root user.

Page 373: Using Zend Framework 2

Database Management with Doctrine ORM 354

Here, we create the second user blog, because it is not recommended to give the webapplication to log into database under the root user. The root user has unlimited rightsand it would be just insecure to give the application an ability to do any actions itwants. The blog user will have permissions to modify the blog database only, whichis sufficient in our case.

You can check that the database has been created by typing the following command and pressingEnter:

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 (atag 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 youwant to move the caret to the next line. The command is considered to be fully enteredwhen the semicolon (;) character is encountered.

Page 374: Using Zend Framework 2

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

Page 375: Using Zend Framework 2

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 bytyping the following command from MySQL prompt:

DROP SCHEMA blog;

Figure 12.3 graphically illustrates what entities we have in the schema and what relationsbetween 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, becausea 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 manytags, and a single tag may belong to many posts as well. Many-to-many relation is typicallyimplemented 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 usedin the Blog sample web application. In the real life, you typically do not type all those SQLstatements in MySQL prompt. Instead, you could type the CREATE TABLE statements to a file andsave it to disk. Then you could just import that file and have ready schema.

For your convenience, the ready schema forBlog sample can be found inAPP_DIR/data/schema.mysql.sqlfile. The file is a plain text file containing SQL statements. To import the file, go to the

Page 376: Using Zend Framework 2

Database Management with Doctrine ORM 357

APP_DIR/data/ directory and type the following command from your command shell (but notfrom 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 ZendFramework 2

For easy integration with Zend Framework 2, Doctrine project provides the following twocomponents (that are actually ZF2 modules):

• DoctrineModule² is a ZF2 module that provides Doctrine basic functionality required bythe 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 isregistered in Packagist.org⁴ catalogue. This is very similar to the way that Zend Framework 2uses for installing its components.

Since Composer packages may depend on each other, it is enough to declare dependency onlyon DoctrineORMModule. This package depends on DoctrineModule and on some other Doctrinecomponents (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/

Page 377: Using Zend Framework 2

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.jsonfile located in the root directory of the web application (in this book, we typically denote thatdirectory as APP_DIR).

To add the dependency, type the following commands from your command shell (replace theAPP_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-moduleas a dependency to your web application, and to download and install that dependency. Theasterisk (*) parameter means that any version of the package is acceptable.

Specifying the asterisk as a version, will result in installing the latest available versionof Doctrine, which typically is the desired behavior.

Once you run the commands above, Composer will first modify the composer.json file and createthe 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 machineand 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%

Page 378: Using Zend Framework 2

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

Page 379: Using Zend Framework 2

Database Management with Doctrine ORM 360

As a bonus, at the end of installation, Composer “suggests” you to installsome additional packages that might be useful for you (doctrine/migrations,doctrine/data-fixtures, etc.) If you strongly wish, you may add those dependencieswith the Composer’s require command as well.

When the installation has been finished, you can find the Doctrine files in yourAPP_DIR/vendordirectory (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 installDoctrine. Once the composer.json (and composer.lock) files have been modified byComposer, you are able to install (or update) all dependencies as usual by typing thephp composer.phar install or php composer.phar update commands, respectively,from your command shell.

12.3.2 Loading Doctrine Integration Modules on ApplicationStart Up

Once you have installed the DoctrineORMModule and all its dependencies, you need to add thefollowing 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 );

Page 380: Using Zend Framework 2

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

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 (whatdatabase driver, host, user name and password to use), where to locate entity classes and howto extract their annotations (metadata), how to store cached data (in the file system or to use acaching extension like APC), and so on. This section’s goal is to give you a general idea of howDoctrine configuration looks like.

The default Doctrine configuration is located in the module.config.php config file of theDoctrineORMModule. 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 DoctrineORMModulefor 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 toconnect 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 moredatabase connections if required.

• the configuration key contains ORM settings like caching configuration and locations ofauto-generated entity proxy classes for each available connection.

• the driver key contains the information about where to locate entity classes for eachavailable 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.

Page 381: Using Zend Framework 2

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

• the eventmanager key contains settings for Doctrine event manager for each availableconnection.

Page 382: Using Zend Framework 2

Database Management with Doctrine ORM 363

Doctrine uses its own implementation of event manager. If you want, you can createan event listener class and hooks some events. However, this advanced topic and wedo not cover it in this book.

• the migrations_configuration key contains settings for database migrations. Databasemigrations are used for initializing and updating database schema in a standard andconsistent way.

12.3.4 Overriding the Default Doctrine Configuration

As you already know from Chapter 3, in a ZF2-based web application configuration is typicallydivided 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 suitswell for storing settings not depending on particular environment, while the latter one suitswell for storing environment-dependent settings (like database connection parameters).

• For storing Doctrine settings specific to certain module, you use the module.config.phpconfig 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 configfile contains the application-wide database connection settings for the blog MySQL databasethat we created earlier in this chapter:

This connection is shared between all modules of the web application. If you want tocreate module-specific connection, consider adding the key to the module.config.phpfile instead.

Page 383: Using Zend Framework 2

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 theorm_default subkey which is the default connection.

• The driverClass key provides the class name to use as a driver to the database. Sincewe use MySQL database, we specify the Doctrine\DBAL\Driver\PDOMySql\Driver classname.

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’scode (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.

Page 384: Using Zend Framework 2

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 developerin your team then renames the local.php.dist file into local.php and enters his ownpassword instead of the placeholder. The local.php file should not be stored underversion control, because youmight want that other people in your team (or other peoplehaving 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 fortesting 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 severaloftenly used examples of entities:

• User entity is designed to store information about a web-site visitor. It may containproperties like username, password, first name, last name, etc.

• License entity is designed to store information about a software license. It may containdata like unique license key, reference to user who purchased the license, license creationdate, etc.

• Payment entity may contain properties related to a purchase of some goods. The propertiesare: transaction ID, money amount, money currency, etc.

In terms of Model-View-Controller pattern, entities are a kind of models designed forstoring data. For additional examples of entities and other types of models, please referto Chapter 4.

In Doctrine ORM, an entity class is mapped on a certain database table. For example, the Userentity 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 thesame that we used when defining the post table in blog database schema. The entity classwill 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.

Page 385: Using Zend Framework 2

Database Management with Doctrine ORM 366

12.5.1 Annotations

An annotation is a special kind of a PHP comment that is preprocessed byDoctrine ORM. In otherwords, annotations is metadata attached to an entity class that can be read by the Doctrine ORMat run-time. Annotations provide verbose information about an entity. Annotations describe anentity 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. Anexample 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 areserving the same goal: to describe a PHP class and its properties and methods. Then thetool goes through your code and builds an HTML documentation automatically basedentirely on code and annotations analysis.

For example, below, we provide the basic example of a Doctrine entity class. You can see thatthe class and its properties are marked with Docblock annotations with special tags (a tag startswith ‘@’ 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/

Page 386: Using Zend Framework 2

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 theApplication module live.

In line 4, you may notice that we use the Doctrine\ORM\Mapping class and its short ORM alias forDoctrine annotations ⁸.

In lines 6-9, you can see a Dockblock annotation for the Post class. Each annotation tag beginswith 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 mappedon 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 avoidannotation naming collisions (assume the case when some other component has an annotation named Entity or Table, the collision wouldhappen).

Page 387: Using Zend Framework 2

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 thecorresponding table column uses AUTO_INCREMENT initializer.

• @ORM\Column(name="<column_name>") is used to tell Doctrine ORM on which tablecolumn 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 thefollowing link⁹.

12.6 Creating Entities

For the Application module, entities are (by convention) stored inside the Application/Entitydirectory under the module’s source directory. Entity classes live inside the Application\Entitynamespace.

12.6.1 Adding Post Entity

We start with creating the Post entity. Create the Post.php file under module’sApplication/Entitydirectory. (If you haven’t created the Application/Entity directory yet, its the right time to dothat.) 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

Page 388: Using Zend Framework 2

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 }

Page 389: Using Zend Framework 2

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 valuesthe $status property may receive (1 for Draft, 2 for Published).

• Entity class has protected properties ($title, $content, $dateCreated, etc.). These aredata that a typical blog post has (see table 12.4 below for reference of properties togetherwith their brief descriptions).

Page 390: Using Zend Framework 2

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 DoctrineORM at run-time allowing it to know how to map this entity and its properties on thedatabase table and its columns.

• Entity class has getter and setter methods (lines 45-102) allowing to access/modifythe protected properties (see the table 12.5 for reference of methods and their briefdescriptions).

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 isjust no need to do that. However, you may mark methods with usual comments andnon-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 theApplication/Entity directory. To do that, first, create Comment.php file and put the following

Page 391: Using Zend Framework 2

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;

Page 392: Using Zend Framework 2

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:

Page 393: Using Zend Framework 2

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;

Page 394: Using Zend Framework 2

Database Management with Doctrine ORM 375

47 }

48 }

Since the Comment and Tag entities are analogous to the Post entity, we don’t provide detaileddescription of the code above.

Please note that we do not create an entity for the fourth auxiliary table post_tag. Thattable will be indirectly used further in this chapter when defining relations betweenentities.

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

For detailed information about relations between entities in Doctrine, please read thispage¹⁰ of Doctrine documentation.

12.6.3.1 OneToMany/ManyToOne

First, let’s define one-to-many relation between the Post and Comment entities. Modify thePost.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

Page 395: Using Zend Framework 2

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 propertywill be the collection of comments related to certain post.

We initialize the $comments property in class constructor (lines 25-29). By assigning it with anew instance of Doctrine\Common\Collections\ArrayCollection class.

A Doctrine ArrayCollection is an array of objects, like usual PHP array, but withadditional features required by Doctrine. It is implemented in DoctrineCommoncomponent.

Page 396: Using Zend Framework 2

Database Management with Doctrine ORM 377

In lines 15-19, we add Doctrine annotations to the $comments property, so Doctrine knows howto 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 associatedwith 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 cannotice 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 }

Page 397: Using Zend Framework 2

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. Theannotation tags @ORM\ManyToOne and @ORM\JoinColumn are analogous to thosewe covered before.

12.6.3.2 ManyToMany

Let’s now express themany-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.

Page 398: Using Zend Framework 2

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 propertywithDocblock annotationswith @ORM\ManyToMany and @ORM\JoinTableannotation tags

• initialize the property in constructor;• add three methods getTags(), addTag() and removeTag() allowing to get/modify theproperty’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 {

Page 399: Using Zend Framework 2

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 methodsfor retrieving the collection of posts associated with the tag, and adding posts associated withthe given tag.

12.6.4 Specifying Entity Locations

To let Doctrine know where to find entities for your Application module (or for another moduleyou 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 currentmodule.

Note that usually we do not specify namespace in config files, but in this particularcase it is convenient to do. When we have namespace defined, we can use the __-

NAMESPACE__ placeholder which expands into that namespace.

Page 400: Using Zend Framework 2

Database Management with Doctrine ORM 381

In line 6, we have doctrine key, under which we have the driver subkey. In line 11, we tellDoctrine ORM that our entities are stored inside of Application/Entity directory under themodule’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 toretrieve entities from their repositories using search criteria and save entities back torepositories.

EntityManager is registered as a service in the Zend Framework 2 service manager (see Chapter3 for additional information about service manager). In your controller class, you retrieve theEntityManager 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 entitymanager’s persist() method. To remove an entity from repository, you use entity manager’sremove() method.

When you call persist() or remove(), EntityManager remembers your changes in memory, butdoesn’t apply changes to database automatically (by performance reasons). To apply changes todatabase 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 Postentity and save it to database:

Page 401: Using Zend Framework 2

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 theQuery object. You then execute the query and get results (an array of entities matching searchconditions).

The getRepository() method is designed to get repository by entity class name. Please lookbelow 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 forretrieving entities from database. The repository can be considered as a collection of all availableentities 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 therepository for an entity, it connects to the database, loads the data from the table mapped to theentity, and assigns entity’s fields with the data.

The Doctrine\ORM\EntityRepository class implements the default repository. Ifneeded, you can, by extending the EntityRepository, create your own repository forcertain 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,

$offset)

Finds entities by a set of criteria.

Page 402: Using Zend Framework 2

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, lookbelow:

// 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 itsID (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 returnsa 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 publishedposts:

// 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 tocreate complex DQL queries. For information on using query builder, please refer to Doctrinedocumentation.

If standard find methods are not sufficient (or if you have complex search criterias and DQLqueries), you can create your own repository by extending the standard EntityRepository classand encapsulate the search logic there. We will show how to do that later when implementingtag 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 webapplication. 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:

Page 403: Using Zend Framework 2

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 (line18). With the findBy() method provided by repository, we select published posts sorted by datein 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’sview 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

Page 404: Using Zend Framework 2

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’stitle and content. That simple!

Now, if you open the Blog web application in your browser, you should be able to see thefollowing 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 toblog. 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;

Page 405: Using Zend Framework 2

Database Management with Doctrine ORM 386

• the PostController::addAction() controller action method will be used for getting formdata, 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. Putthe 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

Page 406: Using Zend Framework 2

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(

Page 407: Using Zend Framework 2

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

Page 408: Using Zend Framework 2

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 codepresented above deeply.

Page 409: Using Zend Framework 2

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 ourBlog sample, we will create and register the PostManager service. This service will have theaddNewPost() public method that will contain business logic of adding Post entity to databaseand associating it with one or several Tag entities.

The PostManager service will contain business logic of the Blog sample. This businesslogic includes, but not limited to, adding new post to blog.

Create the PostManager.php file inside the Application/Service directory under the module’ssource 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()

Page 410: Using Zend Framework 2

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

Page 411: Using Zend Framework 2

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 servicemodel implements the ServiceManagerAwareInterfaceinterface (line 11). When we use service manager to instantiate this service, it will automaticallycall the setServiceManager() method on the service and pass an instance of ServiceManagerto 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() privatemethod is called (line 47) to assign the post with one or several tags. And the flush()method isused for applying changes to database in a single transaction (line 50).

The addTagsToPost() private method contains logic for removing old associations between thepost and tags (lines 61-64), then parsing comma-separated list of tags (line 67), and assigningnew tags to the post (lines 75-84).

Finally, we register PostManager service by modifying module.config.php configuration file asfollows:

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-providedgetServiceLocator() method, as shown in example below:

$postManager = $this->getServiceLocator()->get('post_manager');

Page 412: Using Zend Framework 2

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 createthe PostController controller class. We create the addAction() action method inside thePostController 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.

Page 413: Using Zend Framework 2

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 instanceof the PostManager service.

In line 17, we check whether this is a POST request. If the request is a POST request, we fillthe 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 undermodule’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">

Page 414: Using Zend Framework 2

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 shouldsee the Add New Post page like shown in figure 12.5 below:

Page 415: Using Zend Framework 2

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. Thenyou 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 toedit the data of existing post, send new data to server and apply changes to database. Sitevisitor 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:

Page 416: Using Zend Framework 2

Database Management with Doctrine ORM 397

• to create a form that would allow to enter post title, content, etc. For this page, we cansuccessfully reuse the PostForm form we created earlier (we just rename the Create buttoncaption into Save).

• to add updatePost()method to the PostManager service. The method would find the postby ID in database and update its data;

• to add convertTagsToString() method to the PostManager service. This method wouldtake the post entity, and on output produce string containing comma-separated list of tags;

• to add the PostController::editAction() actionmethod that would take user input, passit 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 servicemodel 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);

Page 417: Using Zend Framework 2

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 newtitle, content, status and the list of tags. It then updates entity’s properties and saves changes todatabase using flush() method.

Note that the updatePost() method doesn’t use the persist() method of entitymanager, because here we have existing post, not a new one.

Then, we have the convertTagsToString() method (lines 26-39) which takes the post, goesthrough Tag entities associated with the post and formats and returns the comma-separatedlist 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);

Page 418: Using Zend Framework 2

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

Page 419: Using Zend Framework 2

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() methodprovided by the entity repository.

Then we check if this is a POST request. If this is the POST request, we fill in and validate theform 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 thefollowing 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>

Page 420: Using Zend Framework 2

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, youshould be able to see the Edit Post page that allows to edit an existing post (see the figure 12.5

Page 421: Using Zend Framework 2

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

Page 422: Using Zend Framework 2

Database Management with Doctrine ORM 403

associations. Site visitor will be able to trigger the action by entering the following URL inbrowser’s navigation bar: http://localhost/application/post/delete/<id>, where <id> is the uniqueidentifier 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 removethe 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 associatedwith the post using the getComments()method of the Post entity. Then we call EntityManager’s remove() method and pass it eachcomment that we want to remove.

Next, we get all tags associated with the post by calling Post’s getTags() method. We removeassociation 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).

Page 423: Using Zend Framework 2

Database Management with Doctrine ORM 404

Finally, we remove the post itself by calling the EntityManager’s remove() method. We applychanges 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() methodto remove the post and apply changes to database. Finally, it redirects the site visitor to theAdmin 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 }

Page 424: Using Zend Framework 2

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 wouldallow site visitors to preview certain post by entering the following URL in browser’s navigationbar: 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 ofthe page. For example of what we are trying to achive, please look at the figure 12.10 below:

Page 425: Using Zend Framework 2

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.

Page 426: Using Zend Framework 2

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. Createthe CommentForm.php file in Application/Form directory under module’s source directory. Putthe 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

Page 427: Using Zend Framework 2

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

Page 428: Using Zend Framework 2

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, andthe Submit button.

Since we covered forms in details in Chapter 7, here we do not explain the codepresented above deeply.

12.12.2 Modifying PostManager

Here, we add two methods: * the getCommentCountStr() method will format the commentcount string for the given post (e.g., “No comments”, “1 comment”, “2 comments”, etc.) * andthe 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.

Page 429: Using Zend Framework 2

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:

Page 430: Using Zend Framework 2

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

Page 431: Using Zend Framework 2

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

Page 432: Using Zend Framework 2

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

Page 433: Using Zend Framework 2

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 publishedor drafts), and allows to view, edit and delete posts.

To implement this page, add the adminAction() action method to the PostController class, asfollows:

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 {

Page 434: Using Zend Framework 2

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

Page 435: Using Zend Framework 2

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 navigationbar, you should be able to see the page like in figure 12.9 below:

Page 436: Using Zend Framework 2

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 onthe Home page. The tag cloud contains most popular tags, and tag’s font size varies dependingon popularity of the tag: most popular tags appear larger than less popular ones. Clicking the tagin 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 thelogic of filtering posts by tag;

Page 437: Using Zend Framework 2

Database Management with Doctrine ORM 418

• to modify the PostManager and add functionality for calculating font sizes for the tagcloud;

• 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 thedefault repository class. Custom repository is a class extended from EntityRepository class. Itis typically used when you need to encapsulate complex DQL queries and search logic in a singleplace in your code.

It is also possible to put the DQL queries to controller class, but that would makecontrollers “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 moreinformation 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 findpublished posts having at least one tag (to calculate total count of tagged posts), and, to findpublished posts filtered by particular tag.We plan to encapsulate this search logic into the customPostRepository repository.

Doctrine works with custom repositories transparently. This means, that you retrievethe 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 statusPublished and have one or more tags assigned;

• and the findPostsByTag() method is designed to return all published posts that have theparticular tag assigned (to filter posts by the given tag).

¹¹http://docs.doctrine-project.org/en/latest/reference/dql-doctrine-query-language.html

Page 438: Using Zend Framework 2

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 Postentity’s annotation as follows:

Page 439: Using Zend Framework 2

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

Page 440: Using Zend Framework 2

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

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

Page 441: Using Zend Framework 2

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 presentin HTTP request, all posts are retrieved as usual. If the variable present, we use our customrepository’s findPostsByTag() method to filter posts.

In line 15, we call the PostManager::getTagCloud() that returns array of tags and theirfrequencies. 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

Page 442: Using Zend Framework 2

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

Page 443: Using Zend Framework 2

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 allupdates for free as they appear. If you see some mistake, please contact the authorusing the following E-mail address: [email protected]. The author will be morethan 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 providesan easy way of accessing a database.

In this chapter, we’ve considered the usage of the Object Relational Mapper (ORM) componentof 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 aremapped to the properties of the class.

To load data from the database, you retrieve an entity from its repository. The repository is a classthat can be considered as a collection of all available entities. When you request the repositoryfor an entity, it connects to the database, loads the data from the table mapped to the entity, andassigns entity’s fields with the data.

Page 444: Using Zend Framework 2

Appendix A. Configuring WebDevelopment EnvironmentHere we will provide instructions on how to prepare your environment for developing ZF2-basedapplications. 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 cloudand to PHPCloud service.

Configuring the development environment is the first thing you have to do when beginning withcreating your first web-site. This includes installing a web server, the PHP engine with requiredextensions and a database.

For the purpose of running the code examples created in this book, we will use Apache HTTPServer (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 integrateddevelopment environment for PHP development. It allows for an easier navigation, editing anddebugging of your PHP application. The NetBeans IDE is written in Java and can be installed inWindows, 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 delivermore problems (like driver compatibility issues). 32-bit systems are with us for a longer time andhave less problems, which is important for novice users.

There are two big families of Linux distributions: Debian and Red Hat. Debian is a free andopen-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 LinuxCentOS and Linux Fedora.

Red Hat Linux is being developed by Red Hat Inc. Red Hat Linux (or its “free” modificationCentOS) is known as a “corporate” operating system. Its main advantage is “stability” (low rateof system crashes). However, this stability is achieved through carefully choosing the softwarewhich is installed out of the box. When you install such an operating system for the purposeof PHP development, this “stability” may become a problem, because you have access to someold (but “stable”) version of PHP and other software. They do not include a new “bleeding-edge”

¹²http://using-zend-framework-2-book.com/tutorials/

Page 445: Using Zend Framework 2

Appendix A. Configuring Web Development Environment 426

software into their repository, so if you want to install one, you will need to download it fromsomewhere, 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 PHPdevelopment. Its name is Linux Ubuntu. Ubuntu is being developed by Canonical Ltd. LinuxUbuntu has two editions: Desktop edition and Server edition. Ubuntu Desktop is a distributioncontaining graphics environment, while Ubuntu Server edition has console terminal only. Forthe 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 writingthis 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 versionsof 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 authorwould recommend to use the latest non-LTS version of UbuntuDesktop, because it has the newestversion of PHP and other software available from repository. The disadvantage of using such aversion is that you will need to upgrade it to the next release every 9 months (as support periodexpires).

For your information, table A.1 lists PHP versions avaiable for installation from repository indifferent Linux distributions:

Table A.1. Available PHP versions in different Linux distributions

Linux Distribution PHP Version

Linux Ubuntu 14.04 Trusty Tahr 5.5.4Linux Ubuntu 13.04 Raring Ringtail 5.4.9Linux Ubuntu 12.10 Quantal Quetzal 5.4.6Linux Ubuntu 12.04 Precise Pangolin LTS 5.3.10Linux Debian 7.0 Wheezy 5.4.4Linux Debian 6.0 Squeeze 5.3.3Red Hat Enterprise Linux 6.4 5.3.3Linux Cent OS 6.4 5.4.15Linux Fedora 19 5.5.0Linux Fedora 18 5.4.9

When choosing between 32-bit and 64-bit versions of the system, remember that the 64 bitversion of Linux Ubuntu will have more compatibility issues than its 32-bit counterpart. Thesupport 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 centralizedrepository. 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 apackage name) may differ based on linux distribution you use. For example, to download and

Page 446: Using Zend Framework 2

Appendix A. Configuring Web Development Environment 427

install packages in Debian-based Linux distributions (e.g. Ubuntu Linux), you use AdvancedPackaging 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 areprovided.

Debian or Linux Ubuntu

First of all, it is recommended that you update your system by installing the latest availableupdates. 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. Thesudo command (stands for “Super User DO”) allows to run another command, apt-get in ourcase, as system administrator (root). You typically use sudo when you need to elevate yourprivileges 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 ofApache HTTP Server, PHP engine and PHP extension module for Apache.

The commands above may ask you for confirmation when installing a package. It isrecommended 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 availableupdates. 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 ofApache HTTP Server and PHP engine.

Next, run the following commands to add Apache HTTP Server to system autorun and start it:

Page 447: Using Zend Framework 2

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 thatthe server sees the PHP engine. To do that, create phpinfo.php file in Apache document rootdirectory.

The document root is a directory where you can (by default) store the web files. Typically, theApache 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 installMidnight Commander (convenient file manager and text editor). To install MidnightCommander in Debian or Linux Ubuntu, type the following:

sudo apt-get install mc

The following command installs Midnight Commander in Fedora, CentOS or Red HatLinux:

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 tothe 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 figureA.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 differentpaths. 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

Page 448: Using Zend Framework 2

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 andlogging 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 editorwindow 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, forexample, UTC or America/New_York):

date.timezone = <your_time_zone>

Set max_execution_time, upload_max_filesize and post_max_size parameters to allow largefile uploads through POST. For example, setting upload_max_filesize with 128M allows toupload files up to 128 megabytes in size. Setting max_execution_timewith zero allows to executethe PHP script indefinitely long.

max_execution_time = 0

Page 449: Using Zend Framework 2

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 MidnightCommander’s editor.

Restarting Apache Web Server

After editing configuration files, you usually have to restart Apache HTTP Server to apply yourchanges. 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 toanother 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 enablemodules 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.

Page 450: Using Zend Framework 2

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 termmeans 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 filesanywhere 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 inChapter 2 when installing the Zend Skeleton Application. Now you just need to havean 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 applychanges.

You can also copy this file and create another virtual host, when you need several web sites tooperate 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

Page 451: Using Zend Framework 2

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 thepriority. Apache web server tries to direct an HTTP request to each virtual host inturn (first to 000-default, then to 010-vhost2), and if a certain virtual host cannot servethe 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 bottomof the document to the section called Virtual Hosts. You can edit this section as you need andrestart Apache to apply your changes.

Installing XDebug PHP extension

To be able to debug your web sites, it is recommended that you install theXDebug extension. TheXDebug extension allows to look inside a running program, see the variables passed from theclient, walk the call stack and profile your PHP code. XDebug also provides the code coverageanalysis 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 yourbrowser 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 packagewith the following command:

yum install php-pecl-xdebug

Page 452: Using Zend Framework 2

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 yourbrowser. If installation was successfull, you’ll see some XDebug-related information.

Page 453: Using Zend Framework 2

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 yourPHP web sites with the ability of caching and optimizing the PHP intermediate code generatedby the PHP interpreter. Although this extension is optional, it is recommended that you installit 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 byOracle. 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 MySQLextension 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 MySQLextension 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/

Page 454: Using Zend Framework 2

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 hasno password, so you have to set it manually. You will need that password for creating otherMySQL 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 followingcommand and press Enter (in the command below, replace the <your_password> placeholderwith 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 thefollowing:

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

Page 455: Using Zend Framework 2

Appendix A. Configuring Web Development Environment 436

Figure A.3. Apache HTTP Server download page

Weneed to download the binary installer forWindows, so on the page that appears, click binarieslink (see the figure A.4).

Figure A.4. Binaries folder

On the next page clickwin32 link. In this page, look for httpd-2.2.22-win32-x86-openssl-0.9.8t.msidownload link and click it to download the MSI installer (see the figure A.5).

Page 456: Using Zend Framework 2

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 seethe 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 website¹⁵. Click theWindows 5.5.0 binaries and source link and download VC11 x86 Thread Safe ZIParchive. 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 iconand choosing Restart from the context menu.

In Windows Vista/Windows 7, httpd.conf file permissions may prevent you fromsaving the changes to file. To be able to save your changes to httpd.conf, you shouldrun your editor as administrator. For example, if you use notepad editor, you shouldrun it as administrator by right-clicking its icon and choosing Run as administratorfrom context menu.

¹⁵http://www.php.net/downloads.php

Page 457: Using Zend Framework 2

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 serverrecognizes the PHP engine.

To check that Apache and PHP are installed correctly, create phpinfo.php file in Apachedocument root directory.

Typically, the Apache document root directory isC:Program Files (x86)Apache SoftwareFoundationApache2.2\htdocs.

In Windows Vista/Windows 7, htdocs directory permissions may prevent you fromcreating files in it. A workaround for this is to modify directory permissions. Right-click the htdocs directory name, choose Properties from context menu. Properties dialogappears (see the figure A.7). Click Security tab, then in the list select Users and clickEdit button. In the appered dialog, set check on Full control checkbox and click OKbutton.

Figure A.7. Directory Permissions Dialog

In the phpinfo.php file, enter the PHP method phpinfo() as follows:

Page 458: Using Zend Framework 2

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 enablemod_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 likethis:

Page 459: Using Zend Framework 2

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 virtualsites 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 FilesApacheSoftware 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. OpenC:Program FilesApache Software FoundationApache2.2\conf\extra\httpd-vhosts.confin 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 [email protected]

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>

Page 460: Using Zend Framework 2

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 installingthe Hello World application. Now you just need to understand how to create virtualhosts.

Installing XDebug PHP extension

To be able to debug your web sites in NetBeans IDE, it is recommended that you install theXDebug extension of your PHP engine. Download an appropriate DLL from this site¹⁶. Forexample, 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 yourbrowser and look for XDebug section (it should look like in the figure A.9):

¹⁶http://www.xdebug.org/download.php

Page 461: Using Zend Framework 2

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 downloadpage, 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 CommandLine Client. You can launch the client from Start Menu | All Programs | MySQL | MySQL Server5.x | MySQL 5.x Command Line Client. The MySQL Command Line Client console appears (seethe figure A.10):

¹⁷http://www.mysql.com/downloads/

Page 462: Using Zend Framework 2

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 downloadand 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 thefollowing in the MySQL client window:

CREATE SCHEMA test_db;

The command above creates an empty database schema that we will populate later. If thecommand 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 website 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 thetest_db database schema.

If you found the installation procedure described above to be boring, you might wantto use an integrated solution (stack) for installing Apache HTTP Server, PHP engineand MySQL database server using a single click. For an example of such an integratedsolution, visit the WAMP Server¹⁸ page.

¹⁸http://www.wampserver.com/en/

Page 463: Using Zend Framework 2

Appendix A. Configuring Web Development Environment 444

Installing NetBeans IDE in Linux

You can install NetBeans IDE using twomethods: either from repository, as you did with Apache,PHP and MySQL, or by downloading the installer from NetBeans web site and running it. Thefirst method is simpler, but it may give you an outdated version of NetBeans, depending on yourLinux distribution. The second method is a little more complicated, but it gives you the mostup-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 yourcommand 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 dependentpackages. 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

Page 464: Using Zend Framework 2

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 Settingstab 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 thelist of all available plugins. Then in the list, set check mark to PHP plugin and click the Installbutton (see the figure A.13).

When the PHP plugin installation is complete, restart the IDE. Then you should be able to createnew PHP projects from menu New->New Project....

It is also recommended to update NetBeans IDE to the latest version by opening menuHelp->Check for updates.

Method 2. Downloading from Web Site

Because NetBeans is written in Java, we have to first install Java virtual machine (JVM). JVMcan be installed from an external Oracle-provided repository. To let your system know about therepository, 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 yourcommand 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 encounterseveral NetBeans bundles available for download. Download the distribution that is intended forPHP development.

¹⁹https://netbeans.org/

Page 465: Using Zend Framework 2

Appendix A. Configuring Web Development Environment 446

Figure A.12. NetBeans Plugins

Figure A.13. NetBeans Plugins

Page 466: Using Zend Framework 2

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 bytyping 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 versionof NetBeans.

When the installation is completed, you can run NetBeans by typing netbeans in the commandline. You should be able to see NetBeans splash screen, and then the IDE main window shouldappear.

Page 467: Using Zend Framework 2

Appendix A. Configuring Web Development Environment 448

Installing NetBeans IDE in Windows

Installing NetBeans in Windows is strightforward. You just need to download the installerfrom NetBeans site²⁰ and run it. You may encounter several bundles of NetBeans available fordownload, and you should download the bundle that is intended for PHP development (see thefigure A.14 for example).

Figure A.14. NetBeans PHP Download Page

If your machine does not contain Java Runtime Environment (JRE), you shoulddownload and install it before NetBeans installation. Java JRE can be downloaded fromOracle site²¹.

Summary

In this appendix, we’ve provided instructions on how to install and configure Apache HTTPServer, 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. Itallows 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

Page 468: Using Zend Framework 2

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 authorappreciates your feedback and will be happy to impove the installation instructions.

²²https://groups.google.com/forum/#!forum/using-zend-framework-2-book

Page 469: Using Zend Framework 2

Appendix B. Introduction to PHPDevelopment in NetBeans IDEIn this book, we use NetBeans IDE for developing Zend Framework 2 based applications. InAppendix A we have installed NetBeans. Here we will provide some useful tips on usingNetBeans 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 coverall IDEs for PHP development in this book. The author only provides instructionsfor NetBeans IDE. It would be easier for a beginner to use NetBeans IDE. Advanceddevelopers 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 selectProperties item. The project’s Properties dialog will appear (shown in figure B.1).

Page 470: Using Zend Framework 2

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 theWebRoot field to point to your web site’s APP_DIR/public directory. You can do this by clicking theBrowse button to the right of the field. Then, in the dialog, click on the public directory and thenclick 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 runsettings for your site (figure B.3).

Page 471: Using Zend Framework 2

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 cancreate 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 listenon 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 ouractual index.php file.

• In theArguments field, you can specify which GET parameters to pass to your site throughthe 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, pressthe Run button on the Run Toolbar (figure B.4). Alternatively, you can press F6 button on yourkeyboard.

Page 472: Using Zend Framework 2

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, andin 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 toolbarallows 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 codeblock 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 thenstop program execution. While this can be used for debugging even a complex site, it makes thedebugging 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, untilyou determine the reason of the problem.

In contrast, when you debug your web site in NetBeans IDE, the PHP interpreter pauses programexecution flow at every line where you set a breakpoint. This makes it possible to convenientlyretrieve information about the current state of the program, like the values of the local variablesand 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 youhaven’t installed it yet, please refer to Appendix A for additional information on howto 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 lineof the index.php file (shown in figure B.5):

Page 473: Using Zend Framework 2

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 browseris waiting for data from the web server. Once you continue program execution, the browserreceives 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 whenyou’re done with debugging the program. The same effect would have pressing the SHIFT+F5key combination.

Clicking the Continue button (or pressing F5 key) continues the program execution until the nextbreakpoint, or until the end of the program, if there are no more breakpoints.

Page 474: Using Zend Framework 2

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 nextline of the program.

The Step Into toolbar button (or pressing F7 key) moves the current program counter to the nextline of the program, and if it is the function entry point, enters the function body. Use this whenyou want to investigate your code in-depth.

The Step Out toolbar button (CTRL+F7) allows to continue program execution until returningfrom the current function.

And Run to Cursor (F4) allows to continue program execution until the line of code where youplace the cursor. This may be convenient if you want to skip some code block and pause at acertain 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 thebreakpoint to appear and click on the line number. Alternatively, you can put the cursor caret tothe 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 appearsto the left of it (shown in figure B.7):

Figure B.7. Setting a breakpoint

Page 475: Using Zend Framework 2

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 abreakpoint 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 programexecution until it encounters the next breakpoint. Once the program flow comes to thebreakpoint, 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 figureB.9). The Breakpointswindow is located in the bottom part of NetBeans window. In this windowyou 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 nameinside of the code and waiting for a second. If the variable value can be evaluated, it will bedisplayed inside of a small pop up window.

Another way to watch variables is through the Variables window (shown in figure B.10), whichis displayed in the bottom part of NetBeans window. The Variables window has three columns:Name, Type and Value.

Page 476: Using Zend Framework 2

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, $_COOKIESand so on. They typically contain server information and parameters passed by the webbrowser 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 theIndexController::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 incontext of a PHP class.

Some variables can be “expanded” (to expand a variable, you need to click on a triangle iconto the left of variable’s name). For example, by clicking and expanding $this variable, you canwatch all fields of the class instance. If you expand an array variable, you will be able to watcharray items.

Using the Variables window it is possible not only to watch variable’s value, but also to changethe value on the fly. To do that, place your mouse cursor over the value column and click overit. 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 fullname of the class, the name of the method within the class and line number. Moving down thestack, you can better understand the current execution state of the program.

Page 477: Using Zend Framework 2

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() isbeing executed, and thismethodwas 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 topof the stack. You can also click a stack frame to see the place of the code that is currently beingexecuted.

Debugging Options

NetBeans allows you to configure some aspects of the debugger’s behavior. To open the Optionsdialog, select menu Tools->Options. In the dialog that appears, click the PHP tab, and in that tab,select Debugging subtab (figure B.12).

Page 478: Using Zend Framework 2

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 theydo. 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 debuggerport 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 yourindex.php file, instead of stopping at the first breakpoint. This may be annoying, so youmay want to uncheck this option.

• The Watches and Balloon Evaluation option group is disabled by default, because thesemay cause XDebug fault. You can enable these options only when you know what you aredoing.

– The Maximum Depth of Structures parameter sets whether nested structures (likenested arrays, objects in objects, etc.) will be visible or not. By default, the depth isset to 3.

– TheMaximum Number of Children option defines how many array items to displayin Variables window. If you set this to, say 30, you will see only the first 30 itemseven when the array has more than 30 items.

• The Show Requested URLs option, when enabled, displays the URL which is currentlybeing processed. It prints the URL to an Output window.

Page 479: Using Zend Framework 2

Appendix B. Introduction to PHP Development in NetBeans IDE 460

• The Debugger Console option allows to see the output of PHP scripts being debugged. Theoutput is shown in theOutput window. If you plan to use this feature, it is recommended toadd output_buffering = Off parameter in [xdebug] section of your php.ini file, otherwisethe output may appear with delays.

Profiling

When your site is ready and working, you are typically interested in making it as fast andperforming as possible. XDebug provides you with an ability to profile your web site. Profilingmeans determining which class methods (or functions) spend what time to execute. This allowsyou 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 functionexecutes, and writes the profiling information to a file. Typically, the profiling info files areplaced into the system temporary directory (in Linux, to /tmp directory) and have names likexdebug.out.<timestamp>, where the <timestamp> placeholder is the timestamp of the HTTPrequest. All you have to do is to open a profiling file and analyze it.

To enable XDebug profiler, you should set the following XDebug configurationparameter in your php.ini file:

xdebug.profiler_enable = 1

Unfortunately, NetBeans for PHP does not have an embedded tool for visualizing the profilingresults. That’s why you need to install a third-party visualizer tool. Below, we will provideinstructions on how to install a simple web-based tool named Webgrind²³. Webgrind can workon 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. InLinux, 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 willdownload 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 needto 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. Forexample, if you configured the virtual host to listen on port 8080, enter “http://localhost:8080”

²³https://code.google.com/p/webgrind/

Page 480: Using Zend Framework 2

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 (seefigure B.13):

Figure B.13. Webgrind Output Page

At the top of the Webgrind page, you can select the percentage of the “heaviest” function callsto show (figure B.14). By default, it is set to 90%. Setting this to a lower percentage will hide thefunctions 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 recenttimestamp. You may need to select another file, for example, if your web pages use asynchronousAJAX requests.

The right-most drop-down list allows to set the units which should be used for measuring thedata. Possible options are: percent (default), milliseconds and microseconds.

Page 481: Using Zend Framework 2

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 letWebgrind to visualize the data for you (the calculation may take a few seconds). As thecalculation finishes, you should be able to see the table of function calls, sorted in descendingorder 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 ofa method call) or function name (in case of a regular function).

• The second column contains the “paragraph” icons, which can be clicked to open thecorresponding 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 thefunction (excluding the time spent on executing other non-standard functions).

• Total Inclusive Cost column contains the total execution time for the function, includingbuilt-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, andcontains 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 parentfunction;

• 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 notpart of a PHP classes).

Please note that the profiler creates a new data file in your /tmp directory for eachHTTP request to your web site. This may cause disk space exhaustion, which canbe fixed only by rebooting your system. So, when you’ve finished profiling yourapplication, it is recommended to disable the profiling by editing the php.ini file,commenting the xdebug.profiler_enable parameter as follows, and then restartingthe Apache web server.

;xdebug.profiler_enable = 0

Page 482: Using Zend Framework 2

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 itin interactive step-by-step mode. To be able to run a web site, you first need to edit the site’sproperties (run configuration).

To debug the site, you need to have the XDebug PHP extension installed. When you debug yourweb site in NetBeans, the PHP engine pauses program execution at every line where you set abreakpoint. You see the debugging information (like local variables and call stack) in NetBeanswindow in graphical form.

Along with debugging, XDebug extension also provides the ability to profile web sites. Withprofiling, 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.

Page 483: Using Zend Framework 2

Appendix C. Introduction to TwitterBootstrapTwitter Bootstrap (shortly, Bootstrap) is a popular CSS framework allowing to make your website professionally looking and visually appealing, even if you don’t have advanced designerskills. In this appendix, you can find some introductive information about Bootstrap and itsusage examples.

Overview of Bootstrap Files

The source code of Bootstrap framework’s components is spread across many CSS files. It isknown that downloading multiple small files is typically slower than downloading a singlelarge file. For this reason, Bootstrap CSS stylesheets are “concatenated” with the special tooland distributed in a form of a single file named bootstrap.css.

However, this bootstrap.css file has a disadvantage: it contains many characters (white spacecharacters, new line characters, comments, etc.) unneeded for code execution, wasting networkbandwidth when downloading the file, thus increasing page load time. To fix this problem, theminification is used.

The minification is the process of removing all unnecessary characters from the source codewithout 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 Bootstrapcode to understand how it works, you better use the usual (non-minified) file, or evendownload the original source files (not concatenated ones).

Let’s look in more details at the files stored inside the APP_DIR/public directory and itssubdirectories (figure C.1).

The css directory contains the CSS stylesheets:

• The bootstrap.css and bootstrap.min.css files are the usual and minified versions ofBootstrap, respectively.

• The bootstrap-theme.css is the optional Bootstrap theme file for a “visually enhancedexperience”. 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 ownCSS rules which will be applied on top of Bootstrap rules. This way you can customize theappearance of your web application.

Page 484: Using Zend Framework 2

Appendix C. Introduction to Twitter Bootstrap 465

The fonts directory contains several files (e.g. glyphicons-halflings-regular.svg) needed byBootstrap for rendering icons. These icons (also called Glyphicons) can be used to enhance theappearance 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. Thebootstrap.min.js file is its minified version.

• Because Bootstrap extensions are implemented as jQuery plugins, they require the latestversion of jQuery library to be present. Thus, the js directory includes the jQuery libraryfile 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 orjQuery. They are used for compatibility with older versions of Internet Explorer webbrowser. The first one²⁵ enables the use of HTML5 elements and provides basic HTML5styling, 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 theminified file back to its usual state.

²⁵https://github.com/aFarkas/html5shiv²⁶https://github.com/scottjehl/Respond

Page 485: Using Zend Framework 2

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 andcolumns. In figure C.2, you can see an example layout of a typical web site: it has the headerblock 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 cellshave 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 pagesin rows and columns.

Each row consists of up to 12 columns ²⁷ (figure C.3). Column width is flexible and depends onthe width of the grid container element. Column height may vary depending on the height ofthe content of the cell. The space between columns is 30 pixels (15 pixels padding at both sidesof the column).

Figure C.3. Bootstrap grid system

Columns can be spanned, so a single cell takes the space of several columns. For example, infigure C.3, the upper grid row consists of 12 columns, and each cell spans a single column. In thebottom 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 spanswithout the calculator. Fortunately, Bootstrap allows for customizing the count ofcolumns 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 ofthe last column will be empty.

Page 486: Using Zend Framework 2

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 addinga <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-1class), 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 thefollowing 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 -->

Page 487: Using Zend Framework 2

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

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 nestyour content, you add a new <div> element containing .row class, and set of .col-md-* columnswithin 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:

Page 488: Using Zend Framework 2

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): thefirst cell spanning 2 columns, the second cell spanning 8 columns and the third cell spanning2 columns. Then we put the nested grid rows inside of the second cell. Because the parent cellspans 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 displaysto tablets and mobile phones. By this reason, the layout grid is adapted to different screenresolutions.

This is also called the responsiveness, or the “mobile first” concept. Bootstrap v3.0 ismobile-first, which means your web site will be viewable and usable on any-sizedscreen. However, this does not free you of painstaking preparation and planning thelayout.

This adaptation is performed in two ways. The first way is that the column width within the gridis flexible. For example, if you increase the size of the browser window, the grid will be scaledaccordingly 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 sitevisitor will need to scroll it to the right. For mobile phones and other low-resolution devices thisis not a good approach. Instead, it would be better for the grid to become “stacked” below somescreen width. When the grid is stacked, its rows are transformed, making cells to be positionedone below another (see figure C.6 for example).

To better control when the grid becomes “stacked”, Bootstrap provides you with additionalCSS classes: .col-xs-1 to col-xs-12 (the “xs” abbreviation means “extra-small” devices, or

Page 489: Using Zend Framework 2

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 “mediumdevices”, or desktops).

For example, .col-md-* classes define the grid which will become “stacked” when the screenis below 992 pixels wide, and horizontal for wider screens. The .col-sm-* can be used to makethe 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 mayneed a much more complex layout, and the simple grid system will be insufficient. Insuch 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 TwitterBootstrap. We plan to use some of these components in our further examples, and this sectionshould give an idea of how you can use these widgets in your own web sites.

Page 490: Using Zend Framework 2

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 thenavbar (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 yoursite can be placed here) and the links to main pages. To put a navbar on your page, you use thefollowing 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’sappearance. 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 withmachine-extractable semantic information about the purpose of an element. In this example, theattribute 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 Helppages, these can be implemented as a dropdown menu (see figure C.8).

²⁸http://www.w3.org/TR/xhtml-role/

Page 491: Using Zend Framework 2

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 inthe 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> elementwith CSS class .dropdown that indicates the dropdownmenu (line 1). In lines 2-4, the <a> element defines the hyperlink to showwhen themenu 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 dropdownmenu 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 threehorizontal bars at the right denote the Toggle button. Clicking the button would expand thehidden navbar items.

You define the collapsible navigation bar as shown in the example below:

Page 492: Using Zend Framework 2

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 headerwhichwill be displayed independently on screenresolution. 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 navigationlinks and the dropdown menu items.

Inverse Navbar Style

The navigation bar can be displayed using two standard “themes”: the default theme (we saw itabove), and the inverse theme. The inverse thememakes the navbar elements be displayed in darkcolors (figure C.10). You probably saw such an inverse navbar in the Zend Skeleton Applicationdemo.

Page 493: Using Zend Framework 2

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> elementby 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 givethe 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 userwhich page he is visiting right now so the user will not get lost and will be able to return to thepage he visited previously, and to the upper-level pages.

To define the breadcrumbs, you use the ordered list <ol> element with the .breadcrumb CSSclass (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 longlist, if displayed on a single page, would require the user to scroll the page down several times tosee 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:

Page 494: Using Zend Framework 2

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 aform), 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 additionalbutton classes: .btn-success (for buttons applying some change on the page), .btn-info (forinformational 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 usingthese 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>

Page 495: Using Zend Framework 2

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 likethe 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 shouldhave two classes: the .glyphicon class is common for all icons; the second class represents theicon 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 ofour 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 smallbuttons, or .btn-xs class for extra-small buttons. For example, in figure C.16, a large Downloadbutton is presented.

Figure C.16. Large button

To define such a button, you can use the following HTML code:

Page 496: Using Zend Framework 2

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 someaspects of Bootstrap framework. You can customize the Bootstrap look and feel using theCustomize²⁹ 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/

Page 497: Using Zend Framework 2

Appendix C. Introduction to Twitter Bootstrap 478

it from the resulting file, thus reducing the network traffic and page load time. You can alsoremove some unused JavaScript code components from the resulting bootstrap.js file.

Additionally, you can choose different CSS parameters like background color, base text color andfont 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 downand press the Compile and Download button. As a result, the bootstrap.zip archive will bedownloaded, which contains all the customized Bootstrap files (both usual and minified CSSand JS files and glyphicons fonts).

Summary

Twitter Bootstrap is a CSS framework developed to make designing your web pages easier. Itprovides 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 agrid with rows and columns. The grid adapts to different screen resolutions, making your pageequally 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 extensionsbundled with the framework.

Bootstrap is shipped with Zend Skeleton Application, so you can start using it out of the boxor, alternatively, you can download the newest version of Bootstrap from the project’s page andcustomize it as you wish.

³⁰LESS is a dynamic stylesheet language extending standard CSS with features like variables, mixins (embedding all the properties of a CSSclass into another CSS class), code block nesting, arithmetic operations, and functions.

Page 498: Using Zend Framework 2

Appendix D. Introduction toDoctrineIn this appendix, we provide overview of the Doctrine library, such as its architecture andcomponents. 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 Doctrinecapabilities.

Doctrine and Database Management Systems

There are many database management systems (DBMS) on the market. Those systems can bedivided into two groups: traditional relational databases utilizing SQL language for queryingand 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 youmay 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 haveone or several columns. A record (or several records) of a table may be linked to a record (orseveral 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 posthas 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.1below.

Figure D.1. Relation between tables. Single post has many comments

Page 499: Using Zend Framework 2

Appendix D. Introduction to Doctrine 480

On themarket, 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 othersystems. 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 goodfor using in systems varying from small to middle scale;

• Commercial Oracle DBMS is mainly targeted on large-scale systems and has sophisticatedadministrative tools;

• PostgreSQL supports indefinitely large databases and can be considered as an open-sourcereplacement of Oracle.

Doctrine library is designed to work with all major databases using a unified programminginterface. This programming interface is implemented in two levels:

1. At the lower level, Doctrine provides the unified mechanism for building SQL queries toany supported relational database and manipulating database schema. This mechanism isimplemented in the Database Access Layer (DBAL) component.

2. At the higher level, the Object Relational Mapper (ORM) component of Doctrine providesan ability to query and manage database data in object-oriented way, by mapping thetables to PHP classes. This component also provides its custom database query languagecalled 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 caneasily work with lower-level DBAL component, if you find that more suitable for your particularneeds.

Doctrine is database-agnostic. In theory, when you use Doctrine you are able to abstractof database type and switch between databases more easily than when you use yourdatabase-dependent solution.

SQL vs. DQL

When using a relational database system, you typically use SQL language as a standard way foraccessing database data and managing database schema. However, each DBMS usually has itown 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

Page 500: Using Zend Framework 2

Appendix D. Introduction to Doctrine 481

Doctrine library is designed to work with all major relational database systems that useSQL language, but it is obvious that it supports only some subset of their functionalityand SQL language capabilities.

Doctrine is built on top of PHP PDO extension (and other database-specific PHP extensions, likesqlite, mysqli, oci8, etc.). Those extensions provide drivers for all major relational databasesystems. 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 W3SchoolsTutorials³⁶.

Since the Object Relational Mapper component of Doctrine is designed to work with objectsinstead of tables, it provides its own “object-oriented” query language called DQL. It is similarto SQL in sense that it allows to write and execute queries to database, but result of a query isan 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 mayprovide 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 theirfields. This is useful, for example, if you have an hierarchical document tree in a contentmanagement (CMS) system. Documents are addressed in the database via a unique keythat 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 toretrieve a document, the database will offer an API or query language that will allowretrieval of documents based on their contents.

• Column Store. Frequently used in web indexing. A column-oriented DBMS is a databasemanagement system that stores data tables as sections of columns of data rather than asrows 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 whereaggregates are computed over large numbers of similar data items.

• Key-Value Store. This is the simplest data storage using unique keys for accessing certaindata. Such database systems provide a simple key-value lookup mechanism.

• and others.

Doctrine provides support only to the Document Store subset of the NoSQL databasesystems. Column store and key-value store database systems typically have veryspecific field of applications, and not covered by Doctrine.

³⁶http://www.w3schools.com/sql/default.asp

Page 501: Using Zend Framework 2

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 documentnamed 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 anddate_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 documentdatabases. 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 isdistributed as a Composer-installable package and registered in Packagist.org⁴² catalogue. Thisis 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 ageneral idea of its capabilities.

Components Supporting Relational Databases

Main Doctrine components designed for working with relational databases are shown in figureD.2 and marked with green. Blue blocks denote the PHP engine and PHP extensions Doctrine isbuilt on top of.

As you can see from the figure, Doctrine is based on PHP engine features and on PHP extensionsthat are actually used as drivers to particular database management systems. Above that baselayer, there are core Doctrine components (like Annotations, Common, etc.) providing essentialfunctionality for other top-level components. The DBAL component provides an abstraction layerof database type. And on top of all that there is the ORM component providing API for workingwith data in object-oriented way. DoctrineModule and DoctrineORMModule components aredesigned 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/

Page 502: Using Zend Framework 2

Appendix D. Introduction to Doctrine 483

Doctrine ORM component uses the so called Data Mapper⁴³ pattern. This pattern tellsthat a database table can be represented as a PHP entity class. The database in thispattern is considered as some kind of repository (storage of entities). When you retrievean entity from the repository, an SQL query is performed internally, and an instanceof 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 theentity 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 vendorname (“Doctrine”) and the component name (e.g. “Common”). Below, you can find the listof Doctrine components together with their Composer-installable package names and briefdescriptions:

• Doctrine\Common. Common Library for Doctrine projects. This component containscommonly used functionality. Its Composer-installable package name is doctrine/common.

• Doctrine\Annotations. Docblock Annotations Parser. Its Composer-installable packagename is doctrine/annotations.

⁴³http://en.wikipedia.org/wiki/Data_mapper_pattern

Page 503: Using Zend Framework 2

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 DescentParsers. 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 layeraround a PDO-like API and a lot of additional, horizontal features like database schemaintrospection and manipulation through an object oriented API. Its Composer-installablepackage name is doctrine/dbal.

• Doctrine\Collections. Collections Abstraction library. Its Composer-installable packagename 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 aconsistent way to manage database schema and update it. Its composer installable packagename is doctrine/migrations.

• Doctrine\DataFixtures. Data Fixtures for all Doctrine Object Managers. Provides aframework formaking database fixtures. Its composer installable package name is doctrine/data-fixtures.

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 toDoctrine\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 PHPengine 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 enginefeatures and on PHP extensions that can be considered as “drivers” to particular databasemanagement 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 usedfunctionality. The Mongodb and CouchDB are components providing low-level API to correspond-ing databases. The MongodbODM, CouchdbODBM, OrientdbODM and PhpcrODM components provideObject Document Mappers (ODM) for corresponding databases. ODM concept is very similarto ORM in sence that it provides an ability to work with a NoSQL database in object orientedway by mapping a document to a PHP entity class. The DoctrineMongoODMModule component isintended for integration with ZF2.

Below, you can find the list of components together with their Composer-installable packagenames and brief descriptions:

• Doctrine\MongoDB is Doctrine MongoDB Abstraction Layer. Its Composer-installablepackage name is doctrine/mongodb.

Page 504: Using Zend Framework 2

Appendix D. Introduction to Doctrine 485

• Doctrine\MongodbODM (Object Document Mapper) provides a way to map NoSQL docu-ments to PHP entitymodels. 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-installablepackage name is doctrine/doctrine-mongo-odm-module.

• Doctrine\CouchDB component provides Simple API that wraps around CouchDBs HTTPAPI. Its Composer-installable package name is doctrine/couchdb.

• Doctrine\CouchDB component is CouchDB Document Object Mapper. It is analogous toDoctrine 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. ItsComposer-installable package name is doctrine/orientdb-odm.

• Doctrine\PhpcrODM is Object-Document-Mapper for PHPCR. Its Composer-installablepackage 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.

Page 505: Using Zend Framework 2

Appendix D. Introduction to Doctrine 486

On the market, there are two big groups of database management systems: traditional relationaldatabases and so called NoSQL databases. Although most relational databases use SQL languagefor querying and manipulating data, each particular database system has its own specificfeatures. The same thing can be seen with NoSQL databases, where each system provides its owncustom method for accessing data. Doctrine is designed to work with data in database-agnosticway by providing sophisticated abstraction layers.

The most useful component of Doctrine, Object Relational Mapper (ORM) is designed to let thedeveloper an ability to work with data in object oriented way. This is when instead of writing anSQL query, you load an entity object (or an array of entity objects) from a repository. With thisapproach, a database table is mapped to a PHP class (also called an entity), and a record fromthat table is mapped to an instance of that entity class.

Page 506: Using Zend Framework 2

About the AuthorOleg Krivtsov is a software developer currently living in Tomsk,Russia. He received a PhD degree in Computer Science from TomskPolytechnic University in 2010. Oleg has been professionally devel-oping C/C++ and PHP software since 2005. He also taught DigitalSignal 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 createthis book about Zend Framework 2. He also writes posts to hispersonal blog⁴⁴ on a regular basis. You can contact Oleg throughhis E-mail address [email protected].

⁴⁴http://olegkrivtcov.wordpress.com/