Practical CakePHP Projects
Kai Chan and John Omokore with Richard K. Miller
Practical CakePHP Projects Copyright © 2009 by Kai Chan and John Omokore with Richard K. Miller All rights reserved. No part of this work may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system, without the prior written permission of the copyright owner and the publisher. ISBN-13 (pbk): 978-1-4302-1578-3 ISBN-13 (electronic): 978-1-4302-1579-0 Printed and bound in the United States of America 9 8 7 6 5 4 3 2 1 Trademarked names may appear in this book. Rather than use a trademark symbol with every occurrence of a trademarked name, we use the names only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark. Java™ and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc., in the US and other countries. Apress, Inc., is not affiliated with Sun Microsystems, Inc., and this book was written without endorsement from Sun Microsystems, Inc. Lead Editor: Steve Anglin Technical Reviewer: David Golding Editorial Board: Clay Andres, Steve Anglin, Mark Beckner, Ewan Buckingham, Tony Campbell, Gary Cornell, Jonathan Gennick, Michelle Lowman, Matthew Moodie, Jeffrey Pepper, Frank Pohlmann, Ben Renow-Clarke, Dominic Shakeshaft, Matt Wade, Tom Welsh Project Manager: Richard Dal Porto Copy Editor: Marilyn Smith Associate Production Director: Kari Brooks-Copony Production Editor: Candace English Compositor: Patrick Cunningham Proofreader: Martha Whitt Indexer: Brenda Miller Artist: April Milne Cover Designer: Kurt Krames Manufacturing Director: Tom Debolski Distributed to the book trade worldwide by Springer-Verlag New York, Inc., 233 Spring Street, 6th Floor, New York, NY 10013. Phone 1-800-SPRINGER, fax 201-348-4505, e-mail kn`ano)ju
For Rita —Kai Chan For Comfort —John Omokore For Marian —Richard K. Miller
Contents at a Glance About the Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii About the Technical Reviewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
CHAPTER 1
Cake Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
CHAPTER 2
Blogging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
CHAPTER 3
E-Commerce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
CHAPTER 4
A Message Forum Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
CHAPTER 5
Google Maps and the Traveling Salesman . . . . . . . . . . . . . . . . . . . . . . 131
CHAPTER 6
Mashing Twitter with the Google Translator . . . . . . . . . . . . . . . . . . . . 173
CHAPTER 7
Unit Testing and Web Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
CHAPTER 8
A Cake Control Panel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
CHAPTER 9
Translating Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
CHAPTER 10
Adding Automagic Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
CHAPTER 11
Cake Tags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
CHAPTER 12
Dynamic Data Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
CHAPTER 13
Captcha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359
INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
v
Contents About the Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii About the Technical Reviewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiv Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xvii
CHAPTER 1
Cake Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Cake Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 The Ingredients of Cake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 The Model-View-Controller Design Pattern . . . . . . . . . . . . . . . . . . . . . . 2 Rapid Application Development . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 PHP 4+. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Object-Oriented Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Dissecting Cake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Cake’s Directory Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 The Cake Naming Conventions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Model Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Data Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 Cake Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Helpers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Vendors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
CHAPTER 2
Blogging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Creating the Database . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Reviewing the Application Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Creating the Post Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
vii
viii
NCONT ENTS
Creating the Posts Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Listing the Posts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Adding a Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Updating a Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Unpublishing a Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 Publishing a Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Deleting a Post . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Creating an RSS Feed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
CHAPTER 3
E-Commerce . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 The Online Shop Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Two Site Layouts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Layout of the Main Content . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 The User Journey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Setting Up the Shop Database. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 Interacting with the Online Shop Database . . . . . . . . . . . . . . . . . . . . . . . . . . 52 The Category Model. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 The Categories Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 The Product Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 The Products Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 The Cart Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Handling User Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 The AppController Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 The Home Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 The Carts Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 The Order Model. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 The Google Checkout Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 The PayPal Submit Button . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
CHAPTER 4
A Message Forum Web Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Our Take on Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 Web Service Elements. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 REST and HTTP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Result Return Formats. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Application Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Threads and Posts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Web Service Requests. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
NC O N T E N T S
Application Structure. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 JSON Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Our Application Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94 Fetch a Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Fetch Several Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 Fetch the Threads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Post Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 Process a Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Process a Search Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Writing the API Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
CHAPTER 5
Google Maps and the Traveling Salesman . . . . . . . . . . . . . . . 131 Hello Map! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 Google Maps Explained . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Geocoding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Google Map Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Map Interface Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Overlays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Driving Directions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Application Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 Application Structure. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Cake Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 The Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 The Global Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 Home Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 Travel Mappr Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 Finding Locations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 The Traveling Salesman Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 Plotting the Journey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 Journey Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Saving a Journey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Saving Tags. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 Retrieving and Editing a Journey. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 Viewing a Journey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
ix
x
NCONT ENTS
CHAPTER 6
Mashing Twitter with the Google Translator . . . . . . . . . . . . . 173 The Twitter API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 The Google Ajax Language API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 Application Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 Application Structure. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 Cake Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 Internationalization and Localization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184 Web Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186 Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Caching Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 Caching Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 Caching Twitter and Google Translations . . . . . . . . . . . . . . . . . . . . . 189 Caching and the Application Layout . . . . . . . . . . . . . . . . . . . . . . . . . . 190 Changing Languages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 Changing Locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 The Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 The Twittertwister Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 The TwitterRequest Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194 The TwitterStatus Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196 The AppController . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211
CHAPTER 7
Unit Testing and Web Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 Getting Programming Done . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 Our Case Study: An App Like In/Out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 Creating the Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214 Adding Username Validation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 Using Cake’s Unit Testing Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Installing SimpleTest . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 Creating Your Own Unit Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Using Assert Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231 Testing the Entire MVC System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 Web Testing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 Creating Web Tests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232 Web Testing Any Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234 Test-Driven Development. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
NC O N T E N T S
CHAPTER 8
A Cake Control Panel
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Application Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 The Authentication and ACL Components . . . . . . . . . . . . . . . . . . . . . . . . . . 238 The Authentication Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 The Access Control List Component . . . . . . . . . . . . . . . . . . . . . . . . . . 238 Component Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Control Panel Application Controllers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 The Control Panel Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242 The Actions Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 The Groups Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 The Users Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259 Testing the Control Panel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
CHAPTER 9
Translating Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 Application Structure. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271 The Translate Behavior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 Baking Cake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273 Adding Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 Administering Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Translating Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Viewing Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282 Deleting Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284 Listing Stories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284 Translation Pagination . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288 Locale and Language Selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 Setting Locale by Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 Setting Locale by Language Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 Setting Locale by Hand . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 User Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293 Logging In . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 Logging Out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296 Baked Code. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
xi
xii
NCONT ENTS
CHAPTER 10
Adding Automagic Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307 Cake’s Built-in Magic Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307 Writing a Custom Behavior. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308 Building Custom Magic Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Access Data Field. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 Record Order Data Field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 Locking Data Field . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
CHAPTER 11
Cake Tags
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
Content and Data Separation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317 View Template . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 Cake View Class Extension . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321 Cake Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323 Yahoo! Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
CHAPTER 12
Dynamic Data Fields . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 Traditional Product Searching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 The Dynamic Data Approach . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330 Considerations for Using the Dynamic Data Approach . . . . . . . . . . 331 The Product Database Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 Baking for This Application. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337 Building the Product Search Feature . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338 Creating the Product Search Form . . . . . . . . . . . . . . . . . . . . . . . . . . . 338 Processing the Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Adding a Product . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 Creating Table Entries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 Entering Product Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
CHAPTER 13
Captcha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359 Captcha Implementations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 359 Captcha Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 ASCII Art Captcha . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361 A Captcha Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 The ASCII Art Component Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 The Captcha Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
About the Authors NKAI CHAN started his computing career in the late 1980s. His current interests include programming methodology, the Semantic Web, data visualization, and enterprise systems. Kai holds a Computer Science bachelor’s degree and a master’s degree in Computer Graphics. He is a cofounder of the Azzian MVC CMS framework. Together with John Omokore and others, he runs a software and training company in London, specializing in various large-scale projects, from SAP to e-commerce web sites. When he has a spare moment, he likes tennis, squash, and long-distance running. NJOHN OMOKORE is a developer, technical consultant, writer, and trainer. John has programming experience in many technologies, including Linux, PHP, MySQL, and Ajax. He has worked on market research data analysis, database development, and related systems. He received his bachelor’s degree in Mathematics and is pursuing a postgraduate degree in software engineering at Oxford University in England. John provides consulting and web development services to corporate organizations around the world. He’s a cofounder of AlternativeBrains and the Azzian MVC CMS framework and sits on the board of many companies. John lives outside London with his wife, two children, and some animals. His career interests include open source scripting languages, OOP programming, and the use of SAP in large-scale industries (chiefly oil and gas). When not scripting, he enjoys playing chess and squash, visiting the gym, and a bit of socializing. NRICHARD K. MILLER graduated from Brigham Young University with a degree in Business Management but has been interested in technology since he began computer programming at age 10. His experience includes web programming, Internet marketing, and new media strategies. He is the developer of several MediaWiki extensions and WordPress plugins, including the widely used What Would Seth Godin Do plugin.
xiii
About the Technical Reviewer NDAVID GOLDING began developing web sites in 1999 and first started using CakePHP on a bet he couldn’t complete a web application in five minutes. He is the author of Beginning CakePHP: From Novice to Professional (Apress, 2008) and has taught CakePHP even while it was still in early stages of development. David has a degree in European Studies from Brigham Young University and continues work in religious studies and history as a graduate student at Claremont Graduate University. He lives with his wife, Camille, and his son, Kenny, in Southern California.
xiv
Acknowledgments W
hen we first decided to write this book, we really didn’t think it would be that difficult a task. After all, we’ve been coding and writing documentation for years and years. Now having written the book, we can honestly say it has been one of the hardest projects we’ve done since we wrote our first-ever Hello World program. As such, with tears streaming from our eyes, we would wholeheartedly like to thank all the people involved. It all sounds like a cliché, but it’s all true. Thank you to the team at Apress, the Cake Software Foundation, colleagues, friends, families, and neighbors. In no particular order, we would like to thank them individually. They are Steve Anglin, Richard Dal Porto, Matt Wade, Marilyn Smith, Joohn Choe, David Golding, Nancy Wright, Richard K. Miller, Rita Woo, Terry Wells, Dan Jackson, Candace English, and God. Kai Chan and John Omokore
Thank you to Kai Chan and John Omokore for allowing me to take part in this book. I’ve enjoyed working with them and the entire Apress team. Thanks to David Golding for getting me involved. I’m thankful for good parents, family, friends, and colleagues, and to God. Richard K. Miller
xv
Introduction F
irst off, thank you for picking up this book. Whether you are standing in a bookshop or reading this at home, we assume you probably have a strong interest in developing web sites. In the past few years, the number of web site frameworks has increased dramatically. This is especially true for PHP-based frameworks. Many people have chosen to adopt CakePHP (Cake, for short) for various reasons, such as these: Ê
UÊ **Ê«À}À>iÀÃÊ>ÀiÊÜ`iÞÊ>Û>>Li°ÊÃÌÊ«ÀiVÌÃÊ
>ÛiÊÌ}
ÌÊ`i>`iÃ]Ê>`ÊÞÕÊ want team members who can quickly pick up a new piece of technology.
Ê
UÊ >i**ÊÃÊi>ÃÞÊÌÊi>À°Ê9ÕÊÜ>ÌÊ>Ê«ÜiÀvÕÊÌÊÌ
>ÌÊÞÕÊV>Êi>ÃÞÊ>ÃÌiÀ°
Ê
UÊ >i**Ê
>ÃÊ}`ÊÃÕ««ÀÌ°Ê iÛi«iÀÃÊvÀiµÕiÌÞÊ«ÃÌÊ>`ÊÀi«ÞÊÌÊiÃÃ>}iÃÊÊÌ
iÊ Cake forum. And there are always some good discussions happening on the Cake IRC. (To see for yourself, simply download mIRC from dppl6++sss*ien_*_ki+, connect to the server en_*bnaajk`a*jap, and join the _]galdl channel.)
When you are developing a site using Cake, you often find yourself trawling through tutorials online to see how things are done. We’ve done that ourselves many times. However, despite the power of the Internet, we still like to look through books. And we think you will find this book a great help in your Cake development endeavors, in addition to all of the material available online. Most of the applications in this book have been written as a result of some real-world development we have done in the past. We focus on projects that we think are relevant to the future of web development. Let’s take mashups, for example. We should all take an interest in this ever-expanding area of web development. We can honestly say that any successful online web site in the future will need to easily communicate with other applications. Application designers will need to bear this in mind. Matters such as search engine optimization need to be built into the application itself. Cake allows us to think in terms of the high-level architecture instead of the nuts and bolts of a web application.
Who Should Read This Book Practical CakePHP is a book mainly for developers. To get the most from it, you should be comfortable with a number of web technologies and programming concepts. These include PHP, SQL, HTML, JavaScript, object-oriented programming, and design patterns, as well as the general principles of web development. If you are at the forefront of web development, then this book is for you!
xvii
xviii
NINTRO DUCT ION
If our book sounds a little too advanced for you, we recommend that you do some preliminary reading. We suggest the following books: Ê
UÊ Beginning PHP and MySQL: From Novice to Professional, Third Edition, by W. Jason Gilmore (Apress, 2008)
Ê
UÊ Beginning CakePHP: From Novice to Professional by David Golding (Apress, 2008)
How This Book Is Organized Each chapter in this book has been chosen so it will cover the core features in Cake, plus some of the minor features as well. The following is a rough breakdown of what each chapter includes. Ê
UÊ
>«ÌiÀÊ£]ʺ >iÊÕ`>iÌ>Ã]»Ê}ÛiÃÊÞÕÊ>ÊÌÀ`ÕVÌÊÌÊ >i°ÊvÊÞÕÊ>ÀiÊiÜÊÌÊ the CakePHP framework, this is the place to start.
Ê
UÊ
>«ÌiÀÊÓ]ʺ }}}]»Ê«ÀÛ`iÃÊÞÕÊÜÌ
Ê>ÊëiÊL}}}Ê>««V>̰Ê̽ÃÊ«iÀviVÌÊvÀÊ beginners who want to know what a Cake application looks like. If there are two chapters in the book that need to be read in sequence, they are Chapters 1 and 2.
Ê
UÊ
>«ÌiÀÊÎ]ʺ iÀVi]»Ê}ÛiÃÊÞÕÊ}Ài>ÌiÀÊÃ}
ÌÊÌÊÌ
iÊÜ>ÞÊ >iÊÃÊÕÃi`ÊÊ>Ê common application. We walk through implementing an online shop using the Cake framework.
Ê
UÊ
>«ÌiÀÊ{]ʺÊiÃÃ>}iÊÀÕÊ7iLÊ-iÀÛVi]»ÊVÛiÀÃÊÌ
iÊ`iÛi«iÌÊvÊ>ÊÜiLÊÃiÀÛViÃÊ API. We guide you through creating a clean API, so any third party can access your application using standard protocols.
Ê
UÊ
>«ÌiÀÊx]ʺ}iÊ>«ÃÊ>`ÊÌ
iÊ/À>Ûi}Ê->iÃ>]»ÊÃ
ÜÃÊÞÕÊ
ÜÊÌ
iÊ}iÊ Maps API is used with Cake. One of the main features of this chapter’s application relates to the classic traveling salesman problem: a salesman needs to visit a number of cities only once and return to where he started.
Ê
UÊ
>«ÌiÀÊÈ]ʺ>Ã
}Ê/ÜÌÌiÀÊÜÌ
ÊÌ
iÊ}iÊ/À>Ã>ÌÀ]»Êi«
>ÃâiÃÊÌ
iÊ«ÀÌ>ViÊ of web services in modern web application development. In true Web 2.0 and Cake fashion, this chapter’s application mashes the Google Ajax Language API with the Twitter API to provide automatic translation of Twitter messages.
Ê
UÊ
>«ÌiÀÊÇ]ʺ1ÌÊ/iÃÌ}Ê>`Ê7iLÊ/iÃÌ}]»ÊVÛiÀÃÊiÊvÊÌ
iÊ
ÌÌiÃÌÊÌ«VÃÊ>}ÊÜiLÊ professionals. Cake 1.2 devotes a large section to testing, and this chapter shows you how to take advantage of Cake’s integrated unit testing features.
Ê
UÊ
>«ÌiÀÊn]ÊºÊ >iÊ ÌÀÊ*>i]»ÊVÛiÀÃÊ >i½ÃÊ>VViÃÃÊVÌÀÊÃÌÃÊ>`ÊÃiVÕÀÌÞÊ features. We develop a web-based front end that allows administrators to manage user security.
NI N T R O D U C T I O N
Ê
UÊ
>«ÌiÀÊ]ʺ/À>Ã>Ì}Ê-ÌÀiÃ]»Ê«ÀÛ`iÃÊÞÕÊÜÌ
ÊÌ
iÊÜi`}iÊÌÊÌ>ViÊ >i½ÃÊ internationalization and localization features. We develop an application in which news stories are available in other languages, with an administration area where translators can translate stories from a base language to another language.
Ê
UÊ
>«ÌiÀÊ£ä]ʺ``}ÊÕÌ>}VÊi`Ã]»Ê`iÃÌÀ>ÌiÃÊiÝÌi`}Ê >i½ÃÊÕÃiÊvÊ>ÕÌ magic fields like _na]pa`, ik`ebea`, and pepha. We create three new automagic fields.
Ê
UÊ
>«ÌiÀÊ££]ʺ >iÊ/>}Ã]»ÊÃ
ÜÃÊÞÕÊÕÀÊÌ>iÊÊ>ÊiÃÌ>LÃ
i`ÊÌiV
}ÞÊÜ
iÀiÊ XML tags are used as a wrapper to coding logic. Using Cake, we develop our own HTML-based tags to display two Yahoo maps.
Ê
UÊ
>«ÌiÀÊ£Ó]ʺ Þ>VÊ >Ì>Êi`Ã]»ÊiÝÌi`ÃÊÌ
iÊiViÀViÊV
>«ÌiÀÊÜÌ
Ê>ÊëiV>Ê product-filtering technique. We take a dynamic data approach to product searches.
Ê
UÊ
>«ÌiÀÊ£Î]ʺ >«ÌV
>]»ÊÃ
ÜÃÊ
ÜÊ- ÊÀÌÊV>ÊLiÊÕÃi`Ê>ÃÊ>Ê >«ÌV
>ÊÌiÃ̰ÊÊÌ
ÃÊ chapter’s project, the Captcha test is housed in a Cake component so it can be used by other applications.
How to Contact the Authors The authors can be contacted as follows: Ê
UÊ >Ê
>ÊV>ÊLiÊVÌ>VÌi`Ê>ÌÊg]e*_d]j
Ê
UÊ
Ê"ÀiÊV>ÊLiÊVÌ>VÌi`Ê>ÌÊfkdj
Ê
UÊ ,V
>À`ʰÊiÀÊV>ÊLiÊVÌ>VÌi`Ê>ÌÊne_d]n`
xix
CHAPTE R
1
Cake Fundamentals U
sing a framework of some sort has now become the order of the day for building large-scale web applications. Organizations have found that using an in-house framework for web projects enhances code reuse, scalability, quick project turnarounds, and security. New and evolving frameworks provide rapid application development tools to promote the adoption of particular programming languages. Many frameworks derived from PHP have been popular with programmers in the open source community. CakePHP—Cake for short—is currently one of the fastest-growing rapid application development frameworks. When you are developing large web applications or creating components that you will reuse in many applications, you’ll find Cake to be a great help. In this chapter, we’ll highlight some of the concepts, technologies, and tools that Cake relies on, including the PHP scripting language, the Model-View-Controller design pattern, and object-oriented programming techniques. We will also outline the default folder structures and naming conventions and introduce some Cake best practices. And, of course, we’ll demonstrate how to write some Cake code. This chapter will serve as a quick reference that will provide you with a solid foundation on which to build your knowledge of the framework throughout the rest of the book.
Cake Features Why should you use Cake when there are so many other frameworks in town? There is a number of good reasons for the popularity of Cake PHP. It has a short learning curve in comparison to other frameworks, because Cake is easy to use and understand. Also, because there are so many PHP programmers, Cake has a large community. New users can find many projects to refer to and use. Here are some features of Cake that make web application development with it easy and fast: Ê
UÊ ÌÊÕÃiÃÊÌ
iÊÊ`i6iÜÊ ÌÀiÀÊ6 ®ÊvÀ>iÜÀÊvÀÊ**°
Ê
UÊ ÌÃÊ`>Ì>L>ÃiÊViVÌÛÌÞÊÃÕ««ÀÌÊVÕ`iÃÊÞ-+Ê>`Ê*ÃÌ}Ài-+]Ê>ÃÊÜiÊ>ÃÊ>ÞÊ other database platforms.
Ê
UÊ >iÊÃÊi>ÃÞÊÌÊÃÌ>ÊÊÃÌÊ«>ÌvÀÃ]ÊVÕ`}Ê1ÝÊ>`Ê7`Üð
Ê
UÊ ÌÃÊ/ÊViÃiÊÃÊÀiÊviÝLiÊÌ
>ÊÌ
iÀÊViÃið
Ê
UÊ ÌÊÕÃiÃÊi>ÃÞÊ>`ÊviÝLiÊÌi«>Ì}ÊÜ
V
Ê>ÜÃÊ**ÊÃÞÌ>Ý]ÊÜÌ
Ê
i«iÀî° 1
2
C HAPTER 1 N CA K E FU NDA MENTA L S
Ê
UÊ >iÊ
>ÃÊÛiÜÊ
i«iÀÃÊÌÊ>ÃÃÃÌÊÊÌ
iÊÃiÀÌÊvÊÊvÌiÊÀi«i>Ìi`Êë«iÌÃÊvÊ/Ê>`Ê vÀÃÊV`i]Ê>Ý]Ê>Û>-VÀ«Ì]Ê>`ÊÃʰ
Ê
UÊ ÌÊ
>ÃÊV«iÌÃÊvÀÊ
>`}ÊÊi>]Ê>ÕÌ
iÌV>Ì]Ê>VViÃÃÊVÌÀ]ÊV>â>Ì]Ê security, sessions, and request handling.
Ê
UÊ >iÊ«ÀÛ`iÃÊÕÌÌÞÊV>ÃÃiÃÊÌÊ>«Õ>ÌiÊÀiÃÕÀViÃÊÃÕV
Ê>ÃÊÃiÌÃ]ÊviÃ]Êv`iÀÃ]Ê8]Ê and many others.
Ê
UÊ 9ÕÀÊ1,ÃÊ>ÀiÊ«Ìâi`ÊvÀÊÃi>ÀV
Êi}ið
NNote For a complete and up-to-date list of Cake features; see the official web site at dppl6++ _]galdl*knc. You can also find many discussions regarding how Cake compares with other frameworks, such as Ruby on Rails, symfony, Zend Framework, and CodeIgniter. For a comparison of Cake with the aforementioned frameworks, check dppl6++bnejepu*^hkcolkp*_ki+.,,4+,2+sdu)_dkkoa)_]galdl) kran)kpdan*dpih.
The Ingredients of Cake In this section, we’ll delve into the core concepts and technologies employed by Cake, starting with the MVC design pattern.
The Model-View-Controller Design Pattern Cake supports the MVC design pattern, which aims to modularize an application into three parts: Ê
UÊ /
iÊmodel represents the data for the application.
Ê
UÊ /
iÊview represents the presentation.
Ê
UÊ /
iÊcontroller ties the model and view together and deals with user input.
Familiarity with the MVC pattern is a plus, but this book does not assume you have any prior knowledge of MVC. This chapter covers how Cake employs the MVC concept.
Rapid Application Development Along with MVC, Cake took on the philosophy of À>«`Ê>««V>ÌÊ`iÛi«iÌÊ, ®]Ê ÃiÌiÃÊ>ÃÊÜÊ>ÃÊÀ>«`Ê«ÀÌÌÞ«}°Ê, ÊÃÊL>ÃV>ÞÊ>ÊiÌ
`ÊvÊ`iVÀi>Ã}ÊÌ
iÊÌiÊ taken to design software systems by using many prebuilt skeleton structures. This provides developers with many advantages, including easier maintenance, code reuse, more efficient Ìi>ÜÀ]Ê>`ʵÕVÊ«ÀiVÌÊÌÕÀ>ÀÕ`°Ê, Ê>ÃÊ«ÀÛ`iÃÊÌ
iÊ>LÌÞÊÌÊ>iÊÀ>«`ÊV
>}iÃÊ based on client feedback, decreasing the dangers of feature creep. Additionally, you can find a lot of off-the-shelf open source code, which you can easily plug into your Cake applications. A great place to start is dppl6++^]ganu*_]galdl*knc.
C H A P T E R 1 N C A K E F U N D A M E N T A LS
PHP 4+ PHP 4+ refers to PHP version 4 and above. PHP has become one of the most important server-side scripting languages on the Web. It is currently a predominant language for the development of web applications. It provides web developers the functionalities to quickly create dynamic web applications. PHP has come a long way since PHP 3 was first introduced more than a decade ago. The adoption of the Cake framework assumes knowledge of PHP 4. The official PHP manual, at dppl6++sss*ldl*jap, provides a complete reference on PHP.
NNote A common problem faced in life with a new adventure is where to go for the right information in order to avoid the mistakes of predecessors. If you are just starting out with PHP, you can refer to the many online PHP forums and repositories, such as the popular PEAR library and the ever-growing dppl6++sss* ldl_h]ooao*knc web site.
Object-Oriented Programming Object-oriented «À}À>}Ê""*®ÊV>ÊLiÊ`iÃVÀLi`Ê>ÃÊ>ÊiÌ
`ÊvÊ«iiÌ>ÌÊÊ which the parts of a program are organized as a collection of objects, each of which represents an instance of a class, and whose classes are all members of a hierarchy of classes united via
iÀÌ>ViÊÀi>ÌÃ
«Ã°ÊÀÊiÝ>«i]Ê>Ê@kc object o]uo$% #skkbskkb#, while a ?]p object o]uo$%#iaksiaks#, and they both inherit o]uo$% from the Lapo class. The Cake framework supports the three key principles of object-oriented development: encapsulation, inheritance, and polymorphism. For the simple magic called encapsulation, Cake’s implementation of one object is protected, or hidden away, from another object to eliminate interference. However, there must be some interaction with other objects in the application, or the object is useless. As in most OOP applications, an object in the Cake framework provides an interface to another object to enable this interaction. ÊÃÌ}Ê££ÊÃ
ÜÃÊÌ
iÊ`iv>ÕÌÊ`>Ì>L>ÃiÊVv}ÕÀ>ÌÊV>ÃÃ]Ê called @=P=>=OA[?KJBEC, which encapsulates `ab]qhp and odkl database connection arrays. Listing 1-1. The Cake Database Configuration Class _h]oo@=P=>=OA[?KJBECw r]n `ab]qhp9]nn]u$ #`neran#9:#iuomh#( #lanoeopajp#9:#pnqa#( #dkop#9:#hk_]hdkop#( #hkcej#9:#]`iej#( #l]ooskn`#9:#oqlan]`iej#( #`]p]^]oa#9:#qoan`^#( #lnabet#9:##
3
4
C HAPTER 1 N CA K E FU NDA MENTA L S
r]n odkl9]nn]u$ #`neran#9:#iuomh#( #lanoeopajp#9:#pnqa#( #dkop#9:#hk_]hdkop#( #hkcej#9:#qoan#( #l]ooskn`#9:#qoania#( #`]p]^]oa#9:#odkl`^#( #lnabet#9:#ol# %7 By default, Cake internally interfaces with the `ab]qhp connection database. It uses its >ÀÀ>ÞÊ«>À>iÌiÀÃÊvÀÊÌÃÊ`iv>ÕÌÊ`>Ì>L>ÃiÊViVÌÊÕiÃÃÊÞÕÊiÝ«VÌÞÊëiVvÞÊ>Ê`vviÀiÌÊ database connection by assigning the qoa@^?kjbec9# odkl# property in a model class. This iÝ«VÌÊÌiÀv>ViÊÜÊi>LiÊÃiÊÌiÀ>VÌÊÜÌ
ÊÌ
iÊÌ>LiÃÊÊÌ
iÊodkl database. Cake’s support for inheritance cannot be overemphasized. It wraps a lot of database manipulation and other utility functions in its default classes in a manner that enables an LiVÌÊÌÊÌ>iÊÊÌ
iÊvÕVÌÃÊvÊ>Ì
iÀÊLiVÌÊ>`ÊiÝÌi`ÊÀÊÌ>ÀÊÌ
ÃiÊvÕVÌÃÊÃÊÞÕÊ don’t repeat the same code. We consider this act of charity as one of the greatest benefits to developers, as it undoubtedly ensures fast application development. Therefore, you need to spend some time sharpening your knives by reading a Cake cheat map or its online API dppl6++]le*_]galdl*knc®ÊÌÊÕ`iÀÃÌ>`ÊÜ
>ÌÊÞÕÀÊLiVÌÃÊÜÊ
iÀÌ°Ê In a controller genealogy, user-defined controller objects inherit from the =ll?kjpnkhhan object. The =ll?kjpnkhhan inherits from ?kjpnkhhanÊLiVÌ]ÊÜ
V
ÊiÝÌi`ÃÊÌ
iÊK^fa_p class. A controller class can be derived from the =ll?kjpnkhhan class, as shown in Ê ÃÌ}Ê£Ó°Ê ÌÀiÀÃÊ>ÀiÊ`ÃVÕÃÃi`ÊÊÀiÊ`iÌ>ÊÊÌ
iÊÕ«V}ÊÃiVÌÃÊ>LÕÌÊ >iÊ`iÃ]ÊÛiÜÃ]Ê >`ÊVÌÀiÀð® Listing 1-2. The Application Controller Class _h]ooLnk`q_po?kjpnkhhanatpaj`o=ll?kjpnkhhanw bqj_pekj^abknaBehpan$%w y y This default class contains the ^abknaBehpan$% method, which can be overridden in >ÞÊV>ÃÃÊÌ
>ÌÊiÝÌi`ÃÊÌ
iÊ=ll?kjpnkhhan class, such as a user-defined controller class. In ÊÃÌ}Ê£Ó]ÊLnk`q_po?kjpnkhhanÊiÝÌi`ÃÊÌ
iÊ=ll?kjpnkhhan class. And lastly, Cake implements polymorphism and ensures that functions within an object can behave differently depending on the input. It basically creates the ability to respond to the same function call in many different ways. /
iÊ >iÊvÀ>iÜÀÊVÀi>ÌiÃÊ>ÞÊÀiÕÃ>LiÊLiVÌðÊ9ÕÊV>ÊÕÃiÊÌ
iÃiÊLiVÌÃÊÜÌ
ÕÌÊ knowing their internal workings. This is one of the key benefits of using Cake.
NNote For more information about OOP in relation to PHP, refer to the PHP manual at dppl6++sss*ldl* jap+kkl1.
C H A P T E R 1 N C A K E F U N D A M E N T A LS
Dissecting Cake Before you start baking a Cake application, you will need to download the Cake framework from _]galdl*kncÊ>`ÊÃÌ>ÊÌÊÊÞÕÀÊV«ÕÌiÀ°Ê,iiLiÀÊÌ
>ÌÊ >iÊÃÊL>Ãi`ÊÊÌ
iÊ**Ê scripting language, so you need to have PHP up and running first. If you will be using informaÌÊÃÌÀi`ÊÊ>Ê`>Ì>L>Ãi]ÊÞÕÊÜÊii`ÊÌÊÃÌ>ÊÌ
iÊ`>Ì>L>ÃiÊi}i°ÊÊÕÀÊiÝ>«iÃÊ>ÃÃÕiÊ the Þ-+Ê`>Ì>L>Ãi°
Cake’s Directory Structure When you unpack Cake, you will find the following main folder structures: Ê
UÊ ]ll: Contains files and folders for your application. The ]ll folder is your development folder, where your application-specific folders and files reside.
Ê
UÊ _]ga: Contains core Cake libraries. The _]ga folder contains the core libraries for
>i**°Ê9ÕÊÃ
Õ`ÊÌÊÌÕV
ÊÌ
iÃiÊLÀ>ÀiÃÊÕiÃÃÊÞÕÊÀi>ÞÊÜÊÜ
>ÌÊÞÕÊ>ÀiÊ doing.
Ê
UÊ `k_o: Contains Cake document files such as the read me, copyright, and change log ÌiÝÌÊviðÊ9ÕÊV>ÊÃÌÀiÊÞÕÀÊÜÊ`VÕiÌ>ÌÊÊÌ
ÃÊv`iÀ°
Ê
UÊ raj`kno: Contains third-party code. The raj`kno folder can contain third-party LÀ>ÀiÃ]ÊÃÕV
Ê>ÃÊÌ
iÊ-ÜvÌÊ>iÀÊ«>V>}iÊvÀÊÃi`}ÊÊi>ÊiÃÃ>}ið
-i«>À>Ì}ÊÌ
iÊ`iv>ÕÌÊ >iÊVÀiÊLÀ>ÀÞÊv`iÀÊvÀÊÌ
iÊ>««V>ÌÊv`iÀÊ>iÃÊÌÊ«Ãsible for you to have many different applications sharing a single Cake installation. With this v`iÀÊÃÌÀÕVÌÕÀi]ÊÞÕÊV>Êi>ÃÞÊÕ«}À>`iÊÞÕÀÊiÝÃÌ}ÊÛiÀÃÊvÊ >iÊÜÌ
ÕÌÊ>vviVÌ}Ê>ÞÊ >««V>ÌÃÊÞÕÊ
>ÛiÊÜÀÌÌi°ÊÊ/>LiÊ££Ê`iÌ>ÃÊ >i½ÃÊ`iv>ÕÌÊv`iÀÊÃÌÀÕVÌÕÀi° Table 1-1. The Cake Default Folder Structure
Directory
Description
]ll+
The parent folder for your application
_kjbec+ _kjpnkhhano+
Contains configuration files for global structures such database connections, security, and access control
Ì>ÃÊÞÕÀÊ>««V>ÌÊVÌÀiÀÃÊviÃÊi°}°]Êqoan[
_kjpnkhhan*ldl® _kilkjajpo+ Contains your user-defined component files +ej`at*ldl
Allows you to deploy Cake with +]ll as the @k_qiajpNkkp
hk_]ha+
Contains locale files that deal with internationalization
ik`aho+
Contains the model files
lhqcejo+
Contains the plugin files
paopo+
Contains the test folders and files
pil+
1Ãi`ÊvÀÊV>V
iÃÊ>`Ê}Ã
raj`kno+
Contains third-party libraries Continued
5
6
C HAPTER 1 N CA K E FU NDA MENTA L S
Table 1-1. Continued
Directory
Description reaso+
Contains view folders and files for presentation i°}°]ÊÌ
iÊ*_plÊviî
ahaiajpo+
Elements, which are bits of views, go here
annkno+
Custom error pages
dahlano+
Helpers
h]ukqpo+
Application layout files
sa^nkkp+
The @k_qiajpNkkp for the application
_oo+
Contains the application style sheet files
behao+
Contains any files
eic+
Contains graphics
fo+
Ì>ÃÊ>Û>-VÀ«ÌÊviÃ
_]ga+
Contains Cake core libraries
raj`kno+
Contains third-party libraries for all applications
The Cake Naming Conventions iÊÃ>ÀÊframeworks, Cake employs naming conventions instead of configuration files for many of its workings, such as for its MVC structure. It is good practice to understand and i«ÞÊÌ
iÊ >iÊVÛiÌðÊ9ÕÊV>ÊÛiÀÀ`iÊÃiÊvÊÌ
iÊÀÕiÃÊ>ÌiÀ]ÊÜ
iÊÞÕÊLiViÊ a proficient Cake baker. Cake has naming conventions for the four core objects: controllers, models, views, and tables. It also provides global constants and functions.
Controller Naming Controller class names must be plural and must have ?kjpnkhhan appended, as in Lnk`q_po?kjpnkhhan. If the object has more than one word, the second word must also begin with an uppercase letter, as in KjhejaLnk`q_po?kjpnkhhan°Ê ÊÌÊÕÃiÊÕ`iÀÃVÀiÃÊ to separate words. File names must be plural, with [_kjpnkhhan appended and the *ldlÊiÝÌiÃ]Ê>ÃÊÊ lnk`q_po[_kjpnkhhan*ldl. If the object has more than one word, the subsequent words must be delimited with underscores, as in kjheja[lnk`q_po[_kjpnkhhan*ldl.
Model Naming Model class names are singular, as in Lnk`q_p. If the object has more than one word, the ÃiV`ÊÜÀ`ÊÕÃÌÊ>ÃÊLi}ÊÜÌ
Ê>ÊÕ««iÀV>ÃiÊiÌÌiÀÊV>iÊV>Ãi®]Ê>ÃÊÊKjhejaLnk`q_p. File names are singular, with the *ldlÊiÝÌiÃ]Ê>ÃÊÊlnk`q_p*ldl. If the object has more than one word, the subsequent words are delimited with underscores, as in kjheja[lnk`q_p*ldl.
C H A P T E R 1 N C A K E F U N D A M E N T A LS
View Naming View file >iÃÊÌ>iÊÊÌ
iÊ>VÌÊ>iÊÊÌ
iÊVÌÀiÀ°ÊÀÊiÝ>«i]ÊvÊÌ
iÊLiVÌÊ
>ÃÊ a method Lnk`q_po?kjpnkhhan66qlcn]`a$%, the path is ]ll+reaso+lnk`q_po+qlcn]`a*_pl.
Table Naming >Ì>L>ÃiÊÌ>LiÊnames should be plural, with words delimited with underscores, as in _kqjpnu[ _k`ao°Ê9ÕÊV>ÊÛiÀÀ`iÊÌ
ÃÊ>}ÊVÛiÌÊLÞÊÃiÌÌ}ÊÌ
iÊ qoaP]^ha property to your «ÀiviÀÀi`ÊÌ>LiÊ>i°ÊÀÊiÝ>«i]ÊÞÕÊVÕ`ÊÃiÌÊÌ
iÊvÜ}\ qoaP]^ha9#iup]^ha#7 where #iup]^ha# is the name of a table in a database.
Global Constants The global constants are categorized into three major parts: Ê
UÊ Core defines: For iÝ>«i]Ê?=GA[OAOOEKJ[OPNEJC defines the Cake application session value.
Ê
UÊ Web root configurable paths: For iÝ>«i]ÊSA>NKKP[@EN defines the web root folder Ü
iÀiÊÀiÃÕÀViÃÊÃÕV
Ê>ÃÊ>}iÊ>`Ê --ÊviÃÊ>ÀiÊÃÌÀi`°Ê
Ê
UÊ Paths: For iÝ>«i]ÊREASOÊ`iviÃÊÌ
iÊ«>ÀiÌÊv`iÀÊvÀÊÌ
iÊ«ÀiÃiÌ>ÌÊviÃÊÛiÜî°Ê
ÀÊiÝ>«i]ÊÞÕÊV>Ê
>ÛiÊÌ
iÊvÜ}ÊV`iÊë«iÌÊÌÊÀi>`ÊÌ
iÊat]ilha*_pl page as an array from the REASO folder: l]ca9REASO*#at]ilha*_pl#7 beha9beha$ l]ca%7
Global Functions The global vÕVÌÃÊÃiÀÛiÊ>ÃÊÜÀ>««iÀÃÊvÀÊÃiÊÕÌÌÞÊvÕVÌðÊÀÊiÝ>«i]ÊÌ
iÊvÜ}Ê function code snippet performs a simple search and replace operation to add style to patp. skn`9osaap7++oa]n_dopnejc)Pdajaa`ha patp9Pdeo?]gaeo]oosaap]odkjau7++Pdad]uop]_g ++n$***%eopdachk^]hbqj_pekj n$# skn`#(8`er_h]oo9#na`#: skn`8+`er:(#patp#%7 The controller should contain most of the business logic, like this: odkllejc[^]ogap[^]h]j_a9 jap[lne_a' p]t7 +ÕÌiÊvÌiÊÊÀi>Ê>««V>ÌÃ]ÊÌ
iÊLÕÃiÃÃÊ}VÊÃÊëÌÊÌÊÃi«>À>ÌiÊ«>ÀÌðÊÜiÛiÀ]ÊÊ Cake, the business logic is often separated into components or vendors, as discussed later in this chapter.
7
8
C HAPTER 1 N CA K E FU NDA MENTA L S
NNote It is advisable to familiarize yourself with the global constants and functions to avoid reinventing the wheel. To see a complete list of Cake’s various classes and functions, visit dppl6++]le*_]galdl*knc.
Models The model is the first of the MVC concepts. Communicating with data stores such as tables, >ÊiÛiÌÃ]ÊÃÌÀÕVÌÕÀi`ÊviÃ]Ê *ÊÀiVÀ`Ã]Ê>`ÊÃÊÊÃÊ>ÊiÛÌ>LiÊ>ëiVÌÊvÊ>ÞÊÊ>À}iÊÃV>iÊ web application, especially when it involves a large number of users. The actions of manipulating data stored in a data store are best done within a model. The model should be involved ÜÌ
ÊÕÃÌÊviÌV
}Ê>`ÊÃ>Û}Ê`>Ì>ÊvÀÊ`>Ì>ÊÃÌÀiðÊÀÊiÝ>«i]ÊÌ>LiʵÕiÀiÃÊÃ
Õ`ÊLiÊ placed in the model.
Model Creation Models are declared using the keyword _h]oo, followed by the name you wish to give to the `i°ÊÕÃÌÊiÊ>ÞÊ**ÊV>ÃÃ]Ê>ÊÊÕÃiÀÊ`ivi`Ê`iÊ>ÞÊVÌ>ÊÃiÊ«À«iÀÌiÃÊ>`ÊiÌ
ods specific to the implementation of that model as determined by the business requirement. A user-defined model class should follow the Cake naming convention and predefined ÀÕiÃÆÊvÀÊiÝ>«i]Ê>ÊLnk`q_pÊ`iÊV>ÃÃÊÃ
Õ`ÊiÝÌi`ÊÌ
iÊ=llIk`ah class. The =llIk`ah class iÝÌi`ÃÊÌ
iÊIk`ahÊV>ÃÃ]ÊÜ
V
Ê`iviÃÊ >i½ÃÊ`iÊvÕVÌ>ÌÞ°ÊÀÊiÝ>«i]ÊÌ
iÊLnk`q_p model class in ÊÃÌ}Ê£ÎÊÛ>À>LÞÊ
iÀÌÃÊ>ÊvÊÌ
iÊIk`ah class properties and methods. Listing 1-3. A Sample Model Class _h]ooLnk`q_patpaj`o=llIk`ahw y Though the Lnk`q_pÊV>ÃÃÊÊÊÃÌ}Ê£ÎÊ>««i>ÀÃÊi«ÌÞ]ÊÌÊÃÊ
i>ÛÞÊ>`i`ÊÜÌ
ÊÃiÊvÊ Cake’s properties and methods. We will bring some of these useful properties and methods into the limelight throughout this book. The =llIk`ah class is originally defined in the _]ga+ directory. To create your own, place it in ]ll+]ll[ik`ah*ldl. This allows methods to be shared among the models. The Ik`ah class, which =llIk`ahÊiÝÌi`Ã]ÊÃÊ>ÊÃÌ>`>À`Ê >iÊLÀ>ÀÞÊ`ivi`ÊÊ_]ga+he^o+ik`ah*ldl. Model default methods such as the bej`$% method are defined in the Ik`ah class stored in _]ga+he^o+ik`ah+ik`ah*ldl°Ê ½ÌÊÃÌ>ÀÌÊÌÜi>}Ê >i½ÃÊ`iv>ÕÌÊIk`ah class until you become >ÊiÝ«iÀÌÊL>iÀ°
C H A P T E R 1 N C A K E F U N D A M E N T A LS
NNote Refer to the cheat sheet in at dppl6++_]galdl*knc+behao+_]gaodaap*l`b before writing a query method in your Ik`ah class definition. Alternatively, check the Cake API at dppl6++]le*_]galdl*knc. This effort will save you from rewriting existing functionalities and enhance rapid application development. For example, Cake provides a bej`$#]hh#% query to retrieve some or all information from a database table.
i̽ÃÊÕÃiÊ>ÊiÝ>«iÊÌÊ`iÃÌÀ>ÌiÊÌ
iÊvÕÀÊÊÜiÊÜÊÌÞ«iÃÊvÊ«iÀ>ÌÃÊÌ
>ÌÊÞÕÊÜÊ À>ÞÊ«iÀvÀÊÊ>Ê`>Ì>L>ÃiÊViVÌÛiÞÊÜÊ>ÃÊ ,1 ®\ Ê
UÊ Ài>ÌiÊ>ÊÀiVÀ`Ê>`ÊÃiÀÌÊÌÊÌÊ>Ê`>Ì>L>ÃiÊÌ>Li°
Ê
UÊ ,iÌÀiÛiÊrecords from one or more database tables.
Ê
UÊ 1«`>ÌiÊtables.
Ê
UÊ iiÌiÊrecords.
First, we will create a table named `al]npiajpoÊ>`ÊÃiÀÌÊÃiÊÃ>«iÊ`>Ì>ÊÜÌ
ÊÌ
iÊ-+Ê shown in ÊÃÌ}Ê£{° Listing 1-4. The Table Schema for departments ?NA=PAP=>HAEBJKPATEOPO\`al]npiajpo\$ \e`\ejp$-,%qjoecja`JKPJQHH]qpk[ej_naiajp( \j]ia\r]n_d]n$.11%`ab]qhpJQHH( \nacekj\r]n_d]n$.11%`ab]qhpJQHH( LNEI=NUGAU$\e`\% %7 EJOANPEJPK\`al]npiajpo\$\e`\(\j]ia\(\nacekj\%R=HQAO $-(#?qopkianOanre_ao#(#QG#%( $.(#O]hao#(#QG#%( $/(#LnaooKbbe_a#(#QG#%( $0(#EjraopknNah]pekjo#(#QO#%( $1(#Dqi]jNaokqn_ao#(JC%( $2(#L]npjanodelKllknpqjepeao#(#QO#%( $3(#I]ngapejc#(#QG#%( $4(#KjhejaI]ngapejc#(#QO#%7 ÊÃÌ}Ê£{ÊVÌ>ÃÊÃiÊÀiVÀ`ÃÊ>LÕÌÊÌ
iÊ>iÊ>`ÊÀi}ÊvÊÌ
iÊ`i«>ÀÌiÌÃ°Ê /
ÃÊÌ>LiÊÃÊëiÊ>`ÊÊÃivÊiÝ«>>ÌÀÞ°Ê ÜÊÌ
>ÌÊÜiÊ
>ÛiÊ>Ê`>Ì>L>Ãi]ÊÜi½Ê«iÀvÀÊÌ
iÊvÀÃÌÊvÊÌ
iÊ ,1 Ê«iÀ>ÌðÊ7i½Ê create some records, by using the default o]ra$%ÊiÌ
`Ê«ÀÛ`i`ÊÊ>Ê`iÊV>ÃðÊ1Ã}Ê this method comes at the price of ensuring that the format of the data to be passed to it as >Ê«>À>iÌiÀÊÕÃÌÊ>`
iÀiÊÌÊÌ
iÊ >iÊ«ÀivÀ>ÌÌi`Ê>ÀÀ>ÞÊÃÌÀÕVÌÕÀi°Êi̽ÃÊÌ>iÊ>ÊÊ>ÌÊ>Ê sample data structure in ÊÃÌ}Ê£x°
9
10
C HAPTER 1 N CA K E FU NDA MENTA L S
Listing 1-5. Cake’s Expected $this->data Format =nn]u $ W,Y9:=nn]u $ W@al]npiajpY9:=nn]u $ We`Y9:5 Wj]iaY9:S]nn]jpeao WnacekjY9:Nqooe] % % W-Y9:=nn]u $ W@al]npiajpY9:=nn]u $ We`Y9:-, Wj]iaY9:Sa^oepa WnacekjY9:QG % % % ÊÊÃÌ}Ê£x]ÊÜi½ÛiÊ«ÀivÀ>ÌÌi`ÊÌÜÊ>``Ì>ÊÀiVÀ`ÃÊÌÊLiÊ>``i`ÊÌÊÕÀÊ`al]npiajpo database table. This structure, stored in a PHP variable such as `]p] or pdeo)`]ta, will save its values to matching fields in the `al]npiajpo database table. To commit the data in this structure into this table, the o]ra$% method is at your service, but the format of pdeo):`]p] argument is crucial to the success of the operation. ÜÊÌ
>ÌÊÜi½ÛiÊVÀi>Ìi`ÊÌ
iÊiÝ«iVÌi`Ê`>Ì>ÊÃÌÀÕVÌÕÀi]Êi̽ÃÊ`iviÊÌ
iÊ@al]npiajp model class to use this preformatted data and commit the two additional records into the `al]npiajpo table. The @al]npiajp model class is shown in ÊÃÌ}ʣȰ Listing 1-6. The Department Model Class 8;ldl _h]oo@al]npiajpatpaj`o=llIk`ahw r]n j]ia9#@al]npiajp#7 r]n qoaP]^ha9#`al]npiajpo#7 bqj_pekjo]raIaoo]ca$ `]p]%w eb$ pdeo):o]ra$ `]p]%%w napqnjpnqa7 yahoaw napqnjb]hoa7 y y ;:
C H A P T E R 1 N C A K E F U N D A M E N T A LS
ÊÊÃÌ}Ê£È]ÊvÀÃÌÊÜiÊ`iV>ÀiÊÌ
iÊ@al]npiajpÊV>ÃÃÊÌ
>ÌÊiÝÌi`ÃÊ=llIk`ah°Ê iÝÌÊ>ÀiÊÌ
iÊ properties, starting with the j]ia property assigned the value @al]npiajp. This property is iViÃÃ>ÀÞÊvÊÞÕÊ>ÀiÊÀÕ}ÊÊ>ÞÌ
}ÊiÃÃÊÌ
>Ê**Êx°Ê/
iÊ qoaP]^ha property specifies the name of the table required for data access or manipulation in the model. Although it isn’t ÀiµÕÀi`]ÊvÊÌÌi`]Ê >iÊÜÊÕÃiÊ>ÊÌ>LiÊÜÌ
ÊÌ
iÊ>iÊvÊÌ
iÊ`i°ÊÀÊiÝ>«i]ÊvÊÌ
iÊ model name is `al]npiajp, Cake will use the `al]npiajpo table for the model by default. It is «ÀÌ>ÌÊÌÊiÝ«VÌÞÊëiVvÞÊÜ
V
Ê`>Ì>L>ÃiÊÌ>LiÊÞÕÊ>ÀiÊÕÃ}]ÊiëiV>ÞÊvÊ >i½ÃÊÌ>LiÊ naming convention is not followed. The o]raIaoo]ca$% model function call should be done in a controller class. We’ll discuss the controller in more detail later in this chapter. In our imaginary controller class, to invoke the o]raIaoo]ca$%ÊiÌ
`Ê`ivi`ÊÊÊÃÌ}Ê£È]ÊÜiÊii`ÊÌ
iÊvÜ}ÊÃÌ>ÌiiÌ\ pdeo):@al]npiajp):o]raIaoo]ca$ `]p]%7 This method accepts as a parameter the preformatted array information called `]p], as `ivi`ÊÊÊÃÌ}Ê£x°Ê1Ã}Ê>ÊEb statement, if the `]p] passed to Cake’s o]ra$% model function is committed to the `al]npiajpo database table, a Boolean pnqa value is returned; if not, b]hoaÊÃÊÀiÌÕÀi`°Ê9ÕÊV>Ê>ÃÊÃ>ÛiÊ`>Ì>ÊÌÊ>Ê`>Ì>L>ÃiÊÌ>LiÊÊÌ
ÃÊ>iÀÊLÞÊÕÃ}Ê >i½ÃÊ _na]pa$% model function. 7
iÊÃÕLÌÌ}Ê>Ê/Êform created using the bkni object in a view, Cake automatically structures the form fields data submitted to a controller in a format that is similar to that Ã
ÜÊÊÊÃÌ}Ê£x° iÝÌ]Êi̽ÃÊ`iÛiÊÌÊÌ
iÊÀiÌÀiÛiÊ«>ÀÌÊvÊ ,1 Ê«iÀ>ÌÃ]ÊÀÊ`>Ì>Ê>VViÃðÊ/ÊLiÊi>ingful, most data-access operations are filtered using some criteria. We’re going to add a cap@al]npiajp$% method to the @al]npiajpÊ`iÊV>ÃÃ]Ê>ÃÊÃ
ÜÊÊÊÃÌ}ʣǰ Listing 1-7. Retrieving Records Using $region=‘US’ Criteria with the find() Method bqj_pekjcap@al]npiajp$ nacekj9jqhh%w napqnjpdeo):bej`$#]hh#(]nn]u$#_kj`epekjo#9:]nn]u$#nacekj#9: nacekj%%%7 y ÊÊÃÌ}Ê£Ç]ÊÜiÊ`iviÊ>Êcap@al]npiajp$% method that accepts nacekj as its parameter. This method employs the service of the Cake’s bej`$% method to retrieve some department information based on nacekjÊ>ÃÊÌÃÊ«>À>iÌiÀ°Ê/ÊÃi>ÀV
ÊvÀÊ>Ê`i«>ÀÌiÌÊÊÌ
iÊ1Ìi`Ê -Ì>ÌiÃ]ÊÜi½ÊVÀi>ÌiÊÌ
iÊvÜ}ÊÊ>ÊVÌÀiÀÊV>ÃÃ\ pdeo):@al]npiajp):cap@al]npiajp$#QO#%7 /
ÃÊÃÌ>ÌiiÌÊÜÊÀiÌÀiÛiÊ>`ÊvÀ>ÌÊ>ÊÌ
iÊ`i«>ÀÌiÌÃÊÊÌ
iÊ1-ÊÀi}]Ê>ÃÊÃ
ÜÊÊ ÊÃÌ}Ê£n°
11
12
C HAPTER 1 N CA K E FU NDA MENTA L S
Listing 1-8. Structure of the Return Department Data Where Region Equals US =nn]u $ W,Y9:=nn]u $ W@al]npiajpY9:=nn]u $ We`Y9:0 Wj]iaY9:EjraopknNah]pekjo WnacekjY9:QO % % W-Y9:=nn]u $ W@al]npiajpY9:=nn]u $ We`Y9:2 Wj]iaY9:L]npjanodelKllknpqjepeao WnacekjY9:QO % % W.Y9:=nn]u $ W@al]npiajpY9:=nn]u $ We`Y9:4 Wj]iaY9:KjhejaI]ngapejc WnacekjY9:QO % % %
NNote The formatted array data in Listing 1-8 might appear completely different when there are associations between the @al]npiajp model class and other model classes that are connected to database tables. In case of associations, the array will include array data from tables of associated models. You will come across preformatted associated data in Chapter 3.
C H A P T E R 1 N C A K E F U N D A M E N T A LS
The bej`$% function is one of the most useful Cake functions for data access. It has the following format: bej`$_kj`epekjoW]nn]uY(beah`oW]nn]uY(kn`anWopnejcY(na_qnoeraWejpY% /
ÃÊiÌ
`Ê>ÃÊ>VVi«ÌÃÊÌ
iÊ«>À>iÌiÀÃ]ÊÊÌ
iÊÀ`iÀÊÃÌi`ÊÊÊ/>LiÊ£Ó° Table 1-2. The find() Function Parameters
Name
Description
Default Value
pula
Can be set to ]hh, benop, _kqjp, jaecd^kno, or heop to determine what type of data-access operation to carry out
benop
_kj`epekjo
An array of conditions specified as key and value
jqhh
beah`o
An array of fields of key and value to retrieve
jqhh
kn`an
/ÊëiVvÞÊÜ
iÌ
iÀÊÌÊÀ`iÀÊvi`ÃÊÊ>ÃVi`}Ê=O?®ÊÀÊ `iÃVi`}Ê@AO?®ÊÀ`iÀ
jqhhÊÊ-+ÊKN@AN>U clause will be used if no kn`anÊvi`ÊÃÊëiVvi`®
l]ca
To determine the page number
jqhh
heiep
To limit the page result
jqhh
kbboap
/
iÊ-+ÊvvÃiÌÊÛ>Õi
jqhh
na_qnoera
Whether to include the associated model
-
9ÕÊV>ÊÕÃiÊ>ÞÊÌ
iÀÊ >iÊ«Ài`ivi`Ê`iÊiÌ
`Ã]ÊÃÕV
Ê>ÃÊÌ
iÊmqanu$% method or the na]`$% method, which returns a list of fields from the database and sets the current model `>Ì>ÊIk`ah66 `]p]®ÊÜÌ
ÊÌ
iÊÀiVÀ`ÊvÕ`°Ê"ÀÊÞÕÊV>ÊVÀi>ÌiÊÞÕÀÊÜÊÊÕÃiÀÊ`ivi`ÊiÌ
`ÃÊ to manipulate data specific to the table a model object uses. ÊÃÌ}Ê£ÊÃ
ÜÃÊ>Ê>ÌiÀ>ÌÛiÊÜ>ÞÊvÊÀiÌÀiÛ}ÊvÀ>ÌÊ>LÕÌÊÌ
iÊ`i«>ÀÌiÌÃ* /
ÃÊÃÌ}ÊÜÊÀiÌÕÀÊiÝ>VÌÞÊÌ
iÊÃ>iÊÀiVÀ`ÃÊvÀÊÌ
iÊ`al]npiajpo table as the one shown ÊÊÃÌ}Ê£nÊÜ
iÊÕÃi`ÊÊÕÀÊ>}>ÀÞÊVÌÀiÀ°Ê/
iÊ`vviÀiViÊÃÊÌ
>ÌÊÊÃÌ}Ê£nÊÕÃiÃÊÌ
iÊ bej`$%ÊiÌ
`]ÊÜ
iÊÊÃÌ}Ê£ÊÕÃiÃÊÌ
iÊmqanu$% method to access data. Listing 1-9. Retrieving Records with the query() Method bqj_pekjcap@al]npiajp$ nacekj9jqhh%w omh9OAHA?P&BNKI\`al]npiajpo\SDANA\nacekj\9 nacekj7 napqnjpdeo):mqanu$ omh%7 y One advantage of using the mqanu$%ÊiÌ
`ÊÃÊÌ
>ÌÊÞÕÊV>Ê«ÕÌÊ>Ê>Ài>`ÞÊ`ivi`Ê-+Ê statement, from a legacy system, into this method without going through the trouble of dividing the query parameters into parts, as you would need to do to use the bej`$% method.
Data Validation >Ì>ÊÛ>`>ÌÊis an essential part of ensuring integrity and accuracy of data submitted by the ÕÃiÀ]ÊÃÕV
Ê>ÃÊÛ>Ê>ÊÜiLÊvÀ°Ê >iÊ
>ÃÊÊLÕÌÊÊÛ>`>ÌÊiV
>ÃðÊ9ÕÊëiVvÞÊÌ
iÊÛ>`>tion rules in a model, and Cake automatically applies the rules when a web form is connected ÌÊÌ
>ÌÊ`i°Ê/
iÃiÊÀÕiÃÊV>Ê>ÃÊLiÊ>««i`ÊÌÊ8Ê`>Ì>°
13
14
C HAPTER 1 N CA K E FU NDA MENTA L S
First, let’s add a simple validation rule to our @al]npiajp model using the r]he`]pa array, >ÃÊÃ
ÜÊÊÊÃÌ}Ê££ä°Ê/
iÊÛ>`>ÌÊÀÕiÊ>ÀÀ>ÞÊÃÊL>ÃV>ÞÊ>Ê>ÃÃV>ÌÛiÊ>ÀÀ>Þ°Ê/
iÊiÞÃÊ are the names of the form fields to validate, and the corresponding values represent the rules attached to the form fields. We’ll make use of this rule later, in the “Views” section. Listing 1-10. The Validation Rule for the Department Model r]n r]he`]pa9]nn]u$#nacekj#9:]nn]u$ #]hld]Jqiane_#9:]nn]u$ #nqha#9:#]hld]Jqiane_#( #namqena`#9:pnqa( #iaoo]ca#9:#Ajpan]nacekj*# % % %7 A field can have multiple validation rules. The r]he`]paÊ>ÀÀ>ÞÊÊÊÃÌ}Ê££äÊ`iviÃÊ a rule for the nacekj field in our @al]npiajp model. If a user does not submit a valid nacekj field, the model will return an error to the controller and quit committing the data to the `al]npiajpo database table. The iaoo]ca key deals with the error messages during validation. To display the error message on a form, use the form helper’s annkn function: 8;ldla_dkbkni):annkn$#nacekj#%7;: This will display the error message “Enter a region” if a user enters a nonalphanumeric value in the nacekj input field. «>ÀÌÊvÀÊÌ
iÊÀÕiÃÊi«Þi`ÊÊÊÃÌ}Ê££ä]Ê >iÊ«ÀÛ`iÃÊ>ÊÕLiÀÊvÊÊLÕÌÊÊ validation rules to check the validity of form inputs and ensure the integrity of information ÞÕÊÜ>ÌÊÌÊÃÌÀi°ÊÊ/>LiÊ£ÎÊÃÌÃÊ>ÊviÜÊvÊÌ
iÊÊLÕÌÊÊÀÕiÃÊ«ÀÛ`i`ÊLÞÊ >i° Table 1-3. Some of Cake’s Built-in Validation Rules
Rule
Description
Example
__
Checks for a valid credit card number
#nqha#9:]nn]u$#__#(#b]op#%
`]pa
Checks for a valid date
#nqha#9:#`]pa#
ai]eh
Checks for a valid e-mail address
#nqha#9:#ai]eh#
el
Checks for a valid IP address
#nqha#9:#el#
ldkja
Checks for a valid phone number
#nqha#9:]nn]u$#ldkja#(jqhh(#qg#%
For a complete list of the validation constants in your Cake build, see the predefined rules in the _]ga+he^o+r]he`]pekj*ldl file. 9ÕÊV>ÊiÝÌi`ÊÌ
iÊÃÌÊvÊÌ
iÊÀÕiÃÊLÞÊ>``}ÊÞÕÀÊÜÊÊÕÃiÀÊ`ivi`ÊÀÕiðÊÊÊÃÌ}Ê£££]Ê we define a simple custom rule to check if a value is a string.
C H A P T E R 1 N C A K E F U N D A M E N T A LS
Listing 1-11. A Custom Rule Called String bqj_pekjopnejc$ _da_g%w napqnjeo[opnejc$ _da_g%7 y Before you apply a custom rule, such as the opnejcÊÀÕiÊÃ
ÜÊÊÊÃÌ}Ê£££]Ê>``ÊÌÊÌÊÌ
iÊ _]ga+he^o+r]he`]pekj*ldl file, and then simply add the rule to your model r]he`]pa array: r]n r]he`]pa9]nn]u$#j]ia#9:#opnejc#%7 This will ensure that the j]ia field is a valid string. In upcoming chapters, you will come across more validation rules. The model object is robust and provides a lot of functionality for database manipulation. However, part of the data retrieved by a model is required for web surfers’ consumption. When >ÊÕÃiÀÊ>iÃÊ>Ê1,ÊÀiµÕiÃÌ]ÊÃiÊÀiëÃiÊÃÊiÝ«iVÌi`ÊÌÊLiÊ`ë>Þi`ÊÊ>ÊÛiÜ]ÊÜ
V
ÊÜi½Ê look at in the following section.
Views Now that ÜiÊ
>ÛiÊÃiÊÛ>`>ÌÊÀÕiÃ]Êi̽ÃÊLÕ`Ê>Ê/Êform to ask users to enter department information. The task of building a web form is done in a view. 6iÜÃÊ>ÀiÊ«ÀiÃiÌ>ÌÊ«>}iðÊ/
iÊ/ÊÀÊ8Ê`VÕiÌÃÊÊÌ
iÊ7iLÊ>ÀiÊÛiÜÃÊÌÊ the users. However, views can be anything, especially if Cake is used to output other formats iÊ,--]Ê* ]Ê>`ÊÃÊÊÜ
V
ÊÃÊViÀÌ>ÞÊ«ÃÃLiÊÜÌ
ÊÌ
iÊNamqaopD]j`han component and «>ÀÃ}ÊiÝÌiÃÃÊÊÌ
iÊÀÕÌiÀ®°Ê6iÜÃÊÀi`iÀÊvÀ>ÌÊÌÊÌ
iÊÕÃiÀðÊ6iÜÃÊ>ÀiÊV«Ãi`Ê vÊ>ÊÝÌÕÀiÊvÊ/Ê>`Ê**ÊV`i°Ê ÞÊ`iv>ÕÌ]Ê>ÊÛiÜÊÃ
Õ`ÊLiÊÃÌÀi`ÊÕ`iÀÊÌ
iÊVÌÀiÀÊ>iÊv`iÀ°ÊÀÊiÝ>«i]ÊÌ
iÊ view for the ]``$% method in the @al]npiajpo?kjpnkhhan is stored as ]ll+reaso+`al]npiajpo+ ]``*ldl. >Ì>ÊvÀÊÌ
iÊVÌÀiÀÊÃÊ«>ÃÃi`ÊÌÊÌ
iÊÛiÜÊLÞÊÕÃ}ÊÌ
iÊoap$% method in a controller.
NNote Views should be involved only with displaying output. For example, this is where you will see HTML tags and XML tags. Business logic, such as odkllejc[^]ogap[^]h]j_a9 jap[lne_a' p]t7, should not be in the view. However, the following is OK in a view: Eb$ odkllejc[^]ogap[^]h]j_a:-,,,%w a_dk#Ukq]naahece^habkn]`eo_kqjp#7y*
i̽ÃÊLÕ`ÊÌ
iÊÛiÜÊÌÊ>``ÊvÀ>ÌÊÌÊÕÀÊ`al]npiajpo database table. We are going to use another utility provided by Cake to build forms: the bkni object. ÊÃÌ}Ê££ÓÊÃ
ÜÃÊÌ
iÊ add view.
15
16
C HAPTER 1 N CA K E FU NDA MENTA L S
Listing 1-12. The Add View for the departments Table 8d-:=``@al]npiajp8+d-: 8;9 bkni):_na]pa$#`al]npiajp#(]nn]u$#]_pekj#9:#]``#%%7;: 8l:J]ia6 8;9 bkni):ejlqp$#@al]npiajp*j]ia#(]nn]u$#oeva#9:#-.,#%%7;: 8;9 bkni):annkn$#j]ia#%7;:8+l: 8l:Nacekj6 8;9 bkni):ejlqp$#@al]npiajp*nacekj#(]nn]u$#oeva#9:#0,#%%7;: 8;9 bkni):annkn$#nacekj#%7;:8+l: 8;9 bkni):aj`$#O]ra#%;: The view code created using the bkni object is stored in +]ll+reaso+`al]npiajp+]``*_pl. ,iiLiÀÊÌ
>ÌÊÌ
iÊÛ>`>ÌÊÀÕiÊvÀÊÌ
ÃÊvÀÊÃÊVÀi>Ìi`Ê>ÌÊÊÃÌ}Ê££ä° To display the add view to a user, we need a controller object with a function called ]``$%, Ü
V
ÊÌÀiÃÊÌÊ`ÊiÝ>VÌÞÊÜ
>ÌÊÌÊÃ>ÞÃ\Ê>``ÊÌ
iÊvÀÊ`>Ì>ÊÌÊÕÀÊ`al]npiajpo database table. ÊÃÌ}Ê££ÎÊshows the action ]``$% method of the @al]npiajpo?kjpnkhhan. Listing 1-13. The add() Action in the Departments Controller 8;ldl _h]oo@al]npiajpo?kjpnkhhanatpaj`o=ll?kjpnkhhanw bqj_pekj]``$%w eb$ailpu$ pdeo):`]p]%%w pdeo):@al]npiajp):_na]pa$%7 eb$ pdeo):@al]npiajp):o]ra$ pdeo):`]p]%%w pdeo):Oaooekj):oapBh]od$£ [[$#Pda@al]npiajp`]p]d]o^aajo]ra`#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#]``#%%7 yahoaw pdeo):Oaooekj):oapBh]od$£ [[$#Pda@al]npiajp`]p]_kqh`jkp^ao]ra`*Lha]oa(pnu]c]ej*#(pnqa%%7 y y y ;: Now that we’ve built a web form using the /Ê
i«iÀÊ>`ÊVÀi>Ìi`Ê>ÊVÌÀiÀÊÌÊ handle the ]``$% action, let’s demonstrate how data is passed to a view. We’ll start by creating a show action in our @al]npiajpo controller class. The action method is shown in Ê ÃÌ}Ê££{°
C H A P T E R 1 N C A K E F U N D A M E N T A LS
Listing 1-14. The show() action in the Departments Controller bqj_pekjodks$ nacekj%w pdeo):oap$#`]p]#(pdeo):@al]npiajp):cap@al]npiajp$ nacekj%%7 y ÊÊÃÌ}Ê££{]ÊÌ
iÊodks$% method accepts the nacekj data as a parameter, and then retrieves departmental data and uses the oap$% method to prepare the data for the view in ÊÃÌ}Ê££x°ÊÊÛiÜÊÃÊ>Ü>ÞÃÊ>i`Ê>vÌiÀÊ>Ê>V̰ÊÀÊiÝ>«i]ÊÌ
iÊodks$% action in the @al]npiajpo?kjpnkhhanÊvÊÊÃÌ}Ê££{ÊÜÊ
>ÛiÊ>ÊÛiÜÊviÊÃÌÀi`ÊÊ]ll+reaso+`al]npiajp+ odks*_pl. Listing 1-15. A View for the Action show($region) of DepartmentsController 8d-_h]oo9benop: Kqn@al]npiajp6) 8;ldl bkna]_d$ `]p]]o `al]npiajp%w a_dk `al]npiajpW#@al]npiajp#YW#j]ia#Y7 y ;: 8+d-: 8l:Kqn@al]npiajpo]naha`^u@n*?]ga*8+l: /
iÊÛiÜÊÊÊÃÌ}Ê££xÊÜÊ`ë>ÞÊÌ
iÊ>iÊvÊÌ
iÊ`i«>ÀÌiÌÊvÀÊiÝ>«i]Ê->iîÊÊ the header section of the web page presented to the user.
NTip If you have a number of data items to display in a view, such as
pepha9Ln]_pe_]h?]ga Lnkfa_po7 `al]npiajp9O]hao7]j` nacekj9QG7, using pdeo):oap$_kil]_p$#pepha #(#`al]npiajp#(#nacekj#%%7 will enable you to use only one oap$% function in your controller class to
pass all the information to your view. You can then access the individual variable in your view; for example, 8;ldlln$ pepha%7;:.
An essential part of any framework is the part that handles requests. In the MVC structure, this is handled by the controller.
17
18
C HAPTER 1 N CA K E FU NDA MENTA L S
Controllers As you’ve seen, a controller is a class with methods called actions. These actions or methods VÌ>ÊÃÌÊvÊÌ
iÊ}VÊÌ
>ÌÊÀië`ÃÊÌÊÕÃiÀÊÀiµÕiÃÌÃÊÊ>Ê>««V>̰ÊÀÊiÝ>«i]ÊvÊ a user wants to know the number of departments in a particular region, the user needs to access the odks$% method of @al]npiajpo?kjpnkhhanÊ`ivi`ÊÊÊÃÌ}Ê££ä]ÊLÞÊÌÞ«}ÊÌ
iÊ vÜ}Ê1,ÊÊ>ÊLÀÜÃiÀÊ>``ÀiÃÃÊL>À\ hk_]hdkop+`al]npiajpo+odks+QO /
iÊ`iv>ÕÌÊÃÌÀÕVÌÕÀiÊvÀÊ>VViÃÃ}Ê>Ê >iÊ1,ÊÃÊÌÊvÀÃÌÊëiVvÞÊÌ
iÊVÌÀiÀÊ>`ÊÌ
iÊ Ì
iÊ>V̰ÊÊÌ
iÊ«ÀiVi`}Ê1,]Ê`al]npiajpo is the controller, odks is the action, and QO is a parameter. By convention, a Cake request should be structured in the following manner: dppl6++Wiu`ki]ej*_kiY+W=llhe_]pekjY+W?kjpnkhhanY+W=_pekjY+WL]n]i-Y+ÅWL]n]iJY
NNote The ej`at$%method is the default access point to a controller when a method is not explicitly specified in a user’s request. For example, you can load the ej`at$% method with codes that will invoke the welcome page of your application. However, do not forget to create a view, or you will get a warning from Cake stating that you should create a view for the action.
9ÕÀÊ>««V>̽ÃÊVÌÀiÀÊV>ÃÃiÃÊ>ÀiÊiÝ«iVÌi`ÊÌÊiÝÌi`ÊÌ
iÊ=ll?kjpnkhhan class, Ü
V
ÊÊÌÕÀÊiÝÌi`ÃÊ>ÊVÀiÊ?kjpnkhhan class, which is a standard Cake library. The =ll?kjpnkhhan class is defined in +]ll+]ll[_kjpnkhhan*ldl, and it should contain methods that are shared between two or more controllers. These controllers can include any number of actions. The =ll?kjpnkhhan serves as a global class that can contain properties and methods common to all the user-defined conÌÀiÀÃÊÊ>Ê>««V>̰ÊÀÊiÝ>«i]ÊÞÕÊV>Ê
>ÛiÊ>ÊiÌ
`ÊÌÊ`iÌiVÌÊ>`ÊiÝÌÀ>VÌÊÌ
iÊ*Ê address of a user, and then use the value of this address to determine the flow of the applicaÌ°Ê >ÀiÀ]ÊÊÊÃÌ}Ê£Ó]ÊÜiÊÕÃi`ÊÌ
iÊ`iv>ÕÌÊVÌÀiÀÊiÌ
`ÊV>i`Ê^abknaBehpan$% in our controller class to reference the method defined in the =ll?kjpnkhhan class stored in ]ll+ ]ll[_kjpnkhhan*ldl°ÊÌ
iÀÊëiÊiÝ>«iÊÃÊÌÊÃiÌÊ>Êdefault page title for an application, >ÃÊÊÌ
ÃÊiÝ>«i\ 8;ldl _h]oo=ll?kjpnkhhanatpaj`o?kjpnkhhanw r]n l]caPepha9#?d]lpan-Ì=>]ganu=llhe_]pekj#7 -ViÊÕÀÊÊÕÃiÀÊ`ivi`ÊVÌÀiÀÊiÝÌi`ÃÊÌ
iÊ=ll?kjpnkhhan, using the statement pdeo):L]caPepha within our controller gives us access to the string #?d]lpan-Ì=>]ganu =llhe_]pekj# assigned to the l]caPepha property in the =ll?kjpnkhhan class.
C H A P T E R 1 N C A K E F U N D A M E N T A LS
The qoao property is an important property within the controller. It works similarly to the namqena[kj_aÊÃÌ>ÌiiÌÊÊ**°Ê >ÃV>Þ]ÊViÊÞÕÊ
>ÛiÊVÀi>Ìi`Ê>Ê`iÊÃÕV
Ê>ÃÊ@al]npiajp®Ê and you want to use the model in a controller, you need to include it in the qoao array. For iÝ>«i]ÊÜ
iÀiÊ@al]npiajp and Pn]`aÊ>ÀiÊiÝÃÌ}Ê`iÃ]ÊÞÕÊV>Ê
>ÛiÊÌ
iÊvÜ}ÊÃÌ>Ìiment in your controller: qoao9]nn]u$#@al]npiajp#(#Pn]`a#%7*
Cake Components Components are classes defined to carry out specific application tasks to support the controller. Cake comes with many built-in components, such as =_hÊvÀÊÕÃiÀÊ>`Ê}ÀÕ«Ê>VViÃÃÊ VÌÀ®]Ê=qpdÊvÀÊÕÃiÀÊ>`Ê}ÀÕ«Ê>ÕÌ
iÌV>Ì®]ÊAi]eh, Oaooekj, and NamqaopD]j`han. Components can also be user-defined. In fact, in large web applications, you will most likely need to build some of your own components to be used by several controllers. All the components that you develop should be stored in the folder ]ll+_kjpnkhhano+_kilkjajpo. Components follow the same Cake conventions as controllers. ÀÊ>Ê«À}À>iÀ½ÃÊ«ÌÊvÊÛiÜ]ÊV«iÌÃÊi>LiÊÞÕÊÌÊiÝÌi`ÊÌ
iÊvÕVÌ>ÌÞÊ of Cake. If you find that your component is quite useful and you possess the free open source spirit, you can and should post it on the Cake web site, where there is a public repository of components. To demonstrate, we’ll dive straight in and create our own simple component—a utility to convert an array to an object. ÊÃÌ}Ê££ÈÊÃ
ÜÃÊÌ
iÊV`iÊÌÊVÀi>ÌiÊÌ
ÃÊV«ḭ Listing 1-16. A Component to Convert an Array to an Object 8;ldl _h]oo=nn]uPkK^fa_p?kilkjajpatpaj`oK^fa_pw bqj_pekjop]npql$" _kjpnkhhan%w pdeo):?kjpnkhhan9 _kjpnkhhan7 y bqj_pekj_kjranp$ ]nn]u(" k^f%w bkna]_d$ ]nn]u]o gau9: r]hqa%w eb$eo[]nn]u$ r]hqa%%w k^f): gau9jasop`?h]oo$%7 pdeo):]nn]u[pk[k^f$ r]hqa(k^f): gau%7 yahoaw k^f): gau9 r]hqa7 y y napqnj k^f7 y y ;:
19
20
C HAPTER 1 N CA K E FU NDA MENTA L S
The =nn]uPkK^fa_p?kilkjajpÊV>ÃÃÊÊÊÃÌ}Ê££ÈÊVÌ>ÃÊÌÜÊL>ÃVÊvÕVÌÃ]ÊÜ
V
Ê>ÀiÊ stored in ]ll+_kjpnkhhano+_kilkjajpo+]nn]u[pk[k^fa_p*ldl: Ê
UÊ /
iÊvÀÃÌÊvÕVÌ]ÊV>i`Êop]npql$%, is used to instantiate the controller object. This enables all other functions within the component to access information contained in the parent controller. It’s basically a callback method used to bring the controller object into the component.
Ê
UÊ /
iÊÃiV`ÊvÕVÌ]Ê_kjranp$%, is our user-defined function. It does the work of >VVi«Ì}Ê>Ê>ÀÀ>ÞÊvÊ`>Ì>Ê>`ÊÀiÌÕÀ}ÊÌ
iÊ>ÀÀ>ÞÊ>ÃÊ>ÊLiV̰Ê9ÕÊV>ÊÕÃiÊÌ
ÃÊVponent whenever you want to convert an array to an object.
ÛiÀÞÌ
}ÊÃ`iÊ>ÊV«iÌÊÃ
Õ`ÊLiÊ}iiÀV°Ê ÊÌÊ«ÕÌÊÊVÌÀiÀÊëiVvVÊV`i]Ê such as a database table name, into components. 9ÕÊV>ÊÕÃiÊV«iÌÃÊÜÌ
Êcontrollers or other components. To use a component— whether it is a built-in one or one you have created—you need to first declare the component within the _kilkjajpo array in a user-defined controller, another component, or in the =ll?kjpnkhhanÊV>ÃðÊÀÊiÝ>«i]ÊÌÊÕÃiÊÌ
iÊV«iÌÊÊÊÃÌ}Ê££È]ÊVÕ`iÊÌ
iÊvÜ}Ê statement: r]n _kilkjajpo9]nn]u$#=nn]uPkK^fa_p#% ÊÊÃÌ}Ê££Ç]ÊÜiÊ>iÊÀiviÀiViÃÊÌÊÌ
iÊÊLÕÌÊÊOaooekj component and our =nn]uPkK^fa_p component in the @al]npiajpo?kjpnkhhan class. Listing 1-17. Using Components in DepartmentsController 8;ldl _h]oo@al]npiajpo?kjpnkhhanatpaj`o=ll?kjpnkhhan w r]n qoao9]nn]u$#@al]npiajp#%7 r]n _kilkjajpo9]nn]u$#Oaooekj#(#=nn]uPkK^fa_p#%7 bqj_pekj`eolh]u$%w ]nn@]p]9]nn]u$%7 ]nn@]p]9pdeo):bej`$#]hh#%7 ln$ pdeo):=nn]uPkK^fa_p):_kjranp$]nn@]p](" k^f%%7 y y ;: ÊÊÃÌ}Ê££Ç]ÊÜiÊVÛiÀÌÊÌ
iÊÀiÃÕÌÊvÊÌ
iÊ`>Ì>ÊÀiÌÀiÛi`ÊvÀÊÕÀÊ`al]npiajpo database table from an array to an object. First, we use the qoao array to reference the @al]npiajp model. We then use the _kilkjajp array to reference the Oaooekj component, which is a built-in Cake component, and the =nn]uPkK^fa_p component, which is our user-defined V«iÌ°Ê iÝÌ]ÊÜiÊVÀi>ÌiÊ>Ê`eolh]u$% function that contains a declaration of an array variable called ]nn@]p]. We retrieve the department data using the default bej`$% function, store the result in the array, and then pass the array to the _kjranp$% method of the =nn]uPkK^fa_p component. Finally, we use the Cake ln$% global function to print the resulting object.
C H A P T E R 1 N C A K E F U N D A M E N T A LS
Helpers Cake helpers are classes that help to decrease development time by providing shortcuts to generate presentational elements. Earlier, we used the Cake form helper, which helps with form element creation and data handling. Helper files should be stored in the ]ll+reaso+dahlano v`iÀ°ÊÊ/>LiÊ£{ÊLÀivÞÊ`iÃVÀLiÃÊÃiÊvÊ >i½ÃÊÊLÕÌÊÊ
i«iÀðÊÀÊvÕÊdocumentation of the Cake helpers, check the Cake API at dppl6++]le*_]galdl*knc. Table 1-4. Some of Cake’s Built-in Helpers
Helper
Description
/
Helps >ÕÌ>ÌiÊVÀi>Ì}Ê/ÊiiiÌÃÊ>`Ê>ÃÊi>LiÃÊ`Þ>VÊ}iiÀ>ÌÊvÊ /ÊÌ>}ÃÊLÞÊ>VVi«Ì}Ê>`Ê«>ÀÃ}ÊÛ>À>LiðÊ/
ÃÊ
i«iÀÊÃÊV>i`ÊÊ>ÊÛiÜÊLÞÊÕÃ}Ê the dpihÊLiV̰Ê/ÊVÕ`iÊ>ÊÀiviÀiViÊÌÊ/Ê
i«iÀ]ÊÕÃiÊÌ
iÊÛ>À>LiÊ dahlano 9]nn]u$#Dpih#%7°Ê/Ê
i«iÀÊvÕVÌÃÊÕÌ«ÕÌÊ/ÊiiiÌÃÊÃÕV
Ê>ÃÊ_d]noap, _oo, `er, `k_Pula, ei]ca, hejg, iap], jaopa`Heop, l]n], opuha, p]^haDa]`ano, p]^ha?ahho, and so on.
Form
Helps ÊvÀÊVÀi>ÌÊ>`Ê«ÀViÃÃ}°Ê1ÃiÊÌ
iÊ bkni object together with its funcÌÃÊÌÊVÀi>ÌiÊvÀÊiiiÌðÊÀÊiÝ>«i]ÊÌÊVÀi>ÌiÊ>ÊvÀÊ«ÕÌÊiiiÌ]ÊÕÃiÊÌ
iÊ bkni):ejlqp$% function. To start the form tag, use the bkni):_na]pa$% function. Other form input element functions include h]^ah, _da_g^kt, `]paPeia, de``aj, n]`ek, patp]na], and so on. There are many options that can be used in form element functions, such as i]tHajcpd]ÊÌÊÃiÌÊÌ
iÊ>ÝÕÊi}Ì
ÊvÊ>Ê/Ê>ÌÌÀLÕÌi°
>Ý
:hejg$]nn]u$# Helps ÌÊëvÞÊ>ÝÊÌ>ÃðÊÌÊÀiµÕÀiÃÊÌ
iÊÃÌ>ÌiiÌÊf]r]o_nelp) lnkpkpula#%%ÊÊÌ
iÊÛiÜ]ÊÜ
V
ÊÀiviÀiViÃÊÌ
iÊ*ÀÌÌÞ«iÊ>Û>-VÀ«ÌÊvÀ>iÜÀ]ÊÊ À`iÀÊÌÊÜÀÊ«À«iÀÞÊÊ>ÊÛiܰÊ9ÕÊV>Ê}iÌÊ>ÊV«ÞÊvÊ*ÀÌÌÞ«iÊvÀÊdppl6++sss* lnkpkpulafo*knc+`ksjhk]`.
>Û>-VÀ«Ì
Helps ÌÊëvÞÊ>Û>-VÀ«ÌÊÌ>ÃÃ]ÊÃÕV
Ê>ÃÊÌÊVÀi>ÌiÊ>Ê>Û>-VÀ«ÌÊ"LiVÌÊ Ì>ÌÊ :k^fa_p®°Ê/Ê>ÌÌ>V
Ê>ÊiÛiÌÊÌÊ>Ê -" ®ÊLiVÌÊvÀÊ>Ê>ÀÀ>Þ]ÊÕÃ}Êf]r]o_nelp) :arajp®° element, use f]r]o_nelp)
Paginator
Helps to format data into multiple pages or to sort data based on some parameters. ÀÊiÝ>«i]ÊÌÊVÀi>ÌiÊ>ÊÊÌÊÌ
iÊiÝÌÊÃiÌÊvÊ«>}>Ìi`ÊÀiÃÕÌÃ]ÊÕÃiÊÌ
iÊ l]cej]pkn):jatp$% function.
-iÃÃ
Provides vÕVÌÃÊÌÊ`i>ÊÜÌ
ÊÃiÃÃÊ>>}iḭÊÀÊiÝ>«i]ÊÌÊÀi`iÀÊ :bh]od®°Ê/ÊÀi>`Ê>ÊÛ>ÕiÃÊÃÌÀi`ÊÊ>Ê}ÛiÊÃiÃÃ]ÊÕÃiÊ messages, use oaooekj) oaooekj):na]`®°
/iÝÌ
Provides vÕVÌÃÊÌÊ`i>ÊÜÌ
ÊÌiÝÌÊÀÊÃÌÀ}Ê
>`}°ÊÀÊiÝ>«i]ÊÌÊÀiÛiÊ :pnei$% function. Ü
Ìië>ViÊvÀÊÌ
iÊLi}}Ê>`ÉÀÊi`ÊvÊÌiÝÌ]ÊÕÃiÊÌ
iÊpatp)
Time
Helps >>}iÊ`>ÌiÃÊ>`ÊÌiðÊÀÊiÝ>«i]ÊÌÊV
iVÊvÊ>Ê}ÛiÊ`>ÌiÉÌiÊÃÌÀ}ÊÃÊ :eoPk`]u$% function. today, use the peia)
8
:ahai$% Helps ÜÌ
Ê8Ê>«Õ>̰Ê/ÊVÀi>ÌiÊ8ÊiiiÌÃ]ÊÕÃiÊÌ
iÊtih) vÕV̰Ê/
ÃÊ
i«iÀÊV>Ê>ÃÊLiÊÕÃi`ÊÌÊVÛiÀÌÊ>ÊÀiÃÕÌÊÃiÌÊÌÊ8°
To reference the common helpers that you need in your application, you can specify the following statement in your =ll?kjpnkhhan class: r]n dahlano9]nn]u$#Dpih#(#Bkni#(#=f]t#(#F]r]O_nelp#%7
21
22
C HAPTER 1 N CA K E FU NDA MENTA L S
This will ensure that the f]r]o_nelp):hejg$% function in a layout works properly. 9ÕÊ>ÞÊii`ÊÌÊVÀi>ÌiÊÞÕÀÊÜÊ
i«iÀÊÀÊÌÜi>Ê>ÊiÝÃÌ}Ê
i«iÀÊV>ÃÃÊÌÊ«ÀÛ`iÊ >``Ì>ÊvÕVÌ>ÌÞÊÌ
>ÌÊÃÊÌÊÞiÌÊÃÕ««i`ÊLÞÊ >i°ÊÃÊ>ÊiÝ>«i]ÊÊÃÌ}Ê££nÊVÀi>ÌiÃÊ a simple helper called /]ll+reaso+dahlano+^na]g*ldl. This helper will print a variable and insert a new break after printing. Listing 1-18. A Sample Custom Break Helper _h]oo>na]gDahlanatpaj`o=llDahlanw bqj_pekjjasheja$ r]h%w napqnjpdeo):kqplqp$ r]h8^n+:%7 y y We can use this helper in our @al]npiajpo controller object to insert a break whenever we use the controller’s lnejp$%ÊvÕVÌ]Ê>ÃÊÃ
ÜÊÊÊÃÌ}Ê££°Ê Listing 1-19. Using the Sample Break Helper 8;ldl _h]oo@al]npiajpo?kjpnkhhanatpaj`o=ll?kjpnkhhanw r]n j]ia9#@al]npiajpo#7 r]n dahlano9]nn]u$#>na]g#%7 bqj_pekjlnejp$ r]h%w napqnjpdeo):>na]g):jasheja$ r]h%7 y y ;: ÀÃÌ]ÊÊÊÃÌ}Ê££]ÊÜiÊÀiviÀiViÊÌ
iÊLÀi>Ê
i«iÀÊLÞÊ`iV>À}Êr]n dahlano9 ]nn]u$#>na]g#%7°Ê iÝÌ]ÊÜiÊ`iviÊ>Êlnejp$% function that accepts a r]h parameter. This function contains the statement that invokes the break helper’s jasheja$ r]h% method, and consequently returns the result with a newline after it.
Plugins With Cake, you can create a complete MVC package called a plugin, which you can integrate into other Cake applications. A plugin is a mini-application with its own controllers, modiÃ]ÊÛiÜÃ]Ê>`ÊÌ
iÀÊ >iÊÀiÃÕÀViÃ°Ê >iÊ`iÃÊÌÊ
>ÛiÊ>ÞÊÊLÕÌÊÊ«Õ}ðÊ9ÕÊV>ÊÕÃiÊ third-party plugins, or better still, build your own.
C H A P T E R 1 N C A K E F U N D A M E N T A LS
Here, we will create a basic feedback plugin that will provide mailing facility. It will have the following directory structure: +]ll +lhqcejo +baa`^]_g +_kjpnkhhano +ik`aho +reaso +baa`^]_g[]ll[_kjpnkhhan*ldl +baa`^]_g[]ll[ik`ah*ldl where Ê
UÊ +_kjpnkhhano contains plugin controllers.
Ê
UÊ +ik`aho contains plugin models.
Ê
UÊ +reaso contains plugin views.
Ê
UÊ +baa`^]_g[]ll[_kjpnkhhan*ldl is the plugin’s =ll?kjpnkhhan, named after the plugin.
Ê
UÊ +baa`^]_g[]ll[ik`ah*ldl is the plugin’s =llIk`ah, named after the plugin.
NNote You must create both an =ll?kjpnkhhan and an =llIk`ah for a plugin to work properly. If you forget to define the Baa`^]_g=ll?kjpnkhhan class and the Baa`^]_g=llIk`ah, Cake will throw a “Missing Controller” error.
The feedback plugin’s =ll?kjpnkhhan is stored in ]ll+lhqcejo+baa`^]_g[]ll[_kjpnkhhan* ldl, and its corresponding =llIk`ah class is stored in ]ll+lhqcejo+baa`^]_g[]ll[ik`ah*ldl( as shown in ÊÃÌ}Ê£Óä° Listing 1-20. Feedback App Classes for the Feedback Plugin 8;ldl _h]ooBaa`^]_g=ll?kjpnkhhanatpaj`o=ll?kjpnkhhanw ++** y ;: 8;ldl _h]ooBaa`^]_g=llIk`ahatpaj`o=llIk`ahw ++** y ;:
23
24
C HAPTER 1 N CA K E FU NDA MENTA L S
Now, let’s create the Baa`^]_gOaj`?kjpnkhhan for our feedback plugin. The code in ÊÃÌ}Ê£Ó£ÊÃÊÃÌÀi`ÊÊ]ll+lhqcejo+baa`^]_g+_kjpnkhhano+baa`^]_g[oaj`[_kjpnkhhan*ldl. Listing 1-21. The FeedbackSendController to Invoke the send() Method 8;ldl _h]ooBaa`^]_gOaj`?kjpnkhhanatpaj`oBaa`^]_g=ll?kjpnkhhanw r]n j]ia9#Baa`^]_g#7 r]n qoao9]nn]u$#Baa`^]_g#%7 bqj_pekjoaj`$ pkAi]eh%w pdeo):oap$naoqhp(b]hoa%7 eb$pdeo):Baa`^]_g):oaj`Ai]eh$ pkAi]eh%%w pdeo):oap$naoqhp(pnqa%7 y y y ;: iÝÌ]ÊÜi½ÊVÀi>ÌiÊ>`ÊÃÌÀiÊÌ
iÊBaa`^]_gOaj`Ik`ah class in the ]ll+lhqcejo+baa`^]_g+ ik`aho+baa`^]_g[oaj`[ik`ah*ldlÊvi]Ê>ÃÊÃ
ÜÊÊÊÃÌ}Ê£ÓÓ° Listing 1-22. The FeedbackSendModel That Uses the PHP mail() Function to Send a Message 8;ldl _h]ooBaa`^]_gOaj`Ik`ahatpaj`oBaa`^]_g=llIk`ah w bqj_pekjoaj`Ai]eh$ na_eleajp%w ++Dana#osdanasapnupkoaj`]jai]ehiaoo]ca* eb$i]eh$ na_eleajp(#De#(#Sd]peo^]gejc#(#Bnki6oqc]n]ga*_ki#%%w napqnjpnqa7 y napqnjb]hoa7 y y ;: iÝÌ]Êi̽ÃÊVÀi>ÌiÊ>ÊëiÊvii`L>VÊ«Õ}ÊÛiÜÊÃÌÀi`ÊÊÌ
iÊ]ll+lhqcejo+baa`^]_g+ reaso+baa`^]_g[oaj`+oaj`*_plÊvi]Ê>ÃÊÃ
ÜÊÊÊÃÌ}Ê£Óΰ
C H A P T E R 1 N C A K E F U N D A M E N T A LS
Listing 1-23. The Feedback Email View 8d-:Baa`^]_gAi]eh8+d-: 8;ldl eb$ naoqhp%w a_dk#Pd]jgukqbknukqnbaa`^]_g*#7 yahoaw a_dk#OknnuKqnouopaieo`ksj]ppdaikiajp*Lha]oapnu]c]ejh]pan*#7 y ;: y ;: Now that we have installed the feedback plugin, we can use it. To access the plugin within a Cake application, you can add name of the plugin, then the action, then the parameter to the 1,]Ê>ÃÊvÜÃ\ dppl6++hk_]hdkop+baa`^]_g+baa`^]_gOaj`+baa`^]_gOaj`+oaj`+Wai]ehl]n]iY 9ÕÊV>Ê
>ÛiÊ>Ê`iv>ÕÌÊVÌÀiÀÊÜÌ
ÊÌ
iÊ>iÊvÊÞÕÀÊ«Õ}°ÊvÊÞÕÊ`ÊÌ
>Ì]ÊÞÕÊV>Ê access it via +WlhqcejY+]_pekj°ÊÀÊiÝ>«i]Ê>Ê«Õ}Ê>i`Êqoano with a controller named Qoano?kjpnkhhan can be accessed at dppl6++Wukqn`ki]ejY+qoano+]`` if there is no plugin called =``?kjpnkhhan in your WlhqcejY+_kjpnkhhano folder. Plugins will use the layouts from the ]ll+reaso+h]ukqpoÊv`iÀÊLÞÊ`iv>Õ̰Ê9ÕÊÜÊÃiiÊ
ÜÊ to override layouts in Chapter 3. 9ÕÊV>Ê>VViÃÃÊ>Ê«Õ}ÊÜÌ
ÊVÌÀiÀÃÊÊÞÕÀÊ >iÊ>««V>ÌÊLÞÊÕÃ}ÊÌ
iÊ namqaop=_pekj$% function: oajp9pdeo):namqaop=_pekj$]nn]u$#_kjpnkhhan#9:#Baa`^]_gOaj`#(#]_pekj#9:#oaj`#%%7
Vendors Many modern frameworks adopt the ½ÌÊ,i«i>ÌÊ9ÕÀÃivÊ ,9®Ê«ÀV«i°Ê>âÞÊÀÊ>ÞLiÊ ivvVi̮ʫÀ}À>iÀÃÊ`½ÌÊiÊÌÊÀiÛiÌÊÌ
iÊÜ
iitÊ/Ê>ÜÊÕÃÊÌÊÃii«ÊÊ>ÊÌÌiÊ}iÀ]Ê Cake has provided a raj`kno folder. This is where we store third-party applications that don’t
>ÛiÊ>ÞÊÀi>ÌÃ
«ÊÜÌ
Ê >i]ÊÃÕV
Ê>ÃÊÌ
iʫ
« ÊiÃÃ>}iÊL>À`Ê>««V>ÌÊ>`ÊÌ
iÊ-ÜvÌÊ Mailer mailing application. This comes in handy, considering the number of utility scripts and programs available in various PHP repositories such as dppl6++sss*ldl_h]ooao*knc.
>i½ÃÊÌiV
µÕiÊvÊVÕ`}ÊiÝÌiÀ>Êscripts is as simple as using the following function: =ll66eilknp$#Raj`kn#(#beha#(]nn]u$#beha#9:#behaJ]ia*ldl#%%7 /
iÊÌ
À`Ê«>À>iÌiÀÊvÊÌ
iÊvÕVÌÊ>VVi«ÌÃÊ>Ê>ÀÀ>ÞÊvÊviÊ>iðÊ1ÃÕ>Þ]ÊbehaJ]ia*ldl is the startup file of the third-party application. The =ll66eilknp$% function can be used in controllers, models, and views of Cake applications. However, it is important that the call is made before any class definition. The raj`kno folder provides a standard way to include third-party applications.
25
26
C HAPTER 1 N CA K E FU NDA MENTA L S
ÃÊ>ÊiÝ>«i]Êi̽ÃÊVÀi>ÌiÊ>Êolehhkqp*ldlÊÃVÀ«Ì]Ê>ÃÊÃ
ÜÊÊÊÃÌ}Ê£Ó{°Ê/
ÃÊÃVÀ«ÌÊÜÊ serve as our third-party script that we’ll use in our O_naaj?kjpnkhhan later. This script should be stored in the ]ll+raj`kno folder. Listing 1-24. A Script to Display the Content of a File on the Screen 8;ldl _h]ooNa]`Behaw lnkpa_pa` beha7 lq^he_bqj_pekj[[_kjopnq_p$ behaJ]ia%w pdeo):beha9 behaJ]ia7 y lner]pabqj_pekjolehh$%w napqnjna]`beha$ pdeo):beha%7 y y ;: ÊÃÌ}Ê£Ó{ÊÜÊëÞÊÀi>`ÊÌ
iÊVÌiÌÊvÊÌ
iÊsah_kia*dpih file and send it to the screen for a user’s consumption. We can import this script into our O_naaj?kjpnkhhan as shown in ÊÃÌ}Ê£Óx° Listing 1-25. The ScreenController to Use a Script as a Vendor 8;ldl =ll66eilknp$#Raj`kn#(#beha#(]nn]u$#beha#9:#olehhkqp*ldl#%%7 _h]ooO_naaj?kjpnkhhanatpaj`o=ll?kjpnkhhanw bqj_pekjej`at$%w kqplqp9jasNa]`Beha$#sah_kia*dpih#%7 kqplqp):olehh$%7 y y ;: ÊÊÃÌ}Ê£Óx]ÊÜiÊÕÃiÊÌ
iÊeilknp$% function to load the content of the olehhkqp*ldl file, and then we create an instance of the Na]`Beha class using the sah_kia*dpih file as its parameter and store it in kqplqp. Finally, we send the content of the file to the screen using kqplqp):olehh$®Æ°
C H A P T E R 1 N C A K E F U N D A M E N T A LS
Summary ÊÌ
ÃÊV
>«ÌiÀ]ÊÜiÊLÀivÞÊÌÀ`ÕVi`ÊÞÕÊÌÊÌ
iÊ>Êvi>ÌÕÀiÃÊvÊ >i°Ê7iÊiÝ«>i`Ê
ÜÊÌÊÃÊ >Ê, Ê«>ÌvÀ]ÊÜÌ
ÊÌ
iÊ6 Ê`iÃ}Ê«>ÌÌiÀÊvÀ}ÊÌ
iÊL>ÃiÊvÕ`>̰Ê7iÊVÛiÀi`Ê
ÜÊ the Cake MVC structure works, with business logic stored in controllers and components, data access in models, and presentational markup in the view. Additionally, we showed how Cake reduces development time with helpers, plugins, and vendors. After reading this chapter, you should have an overview of how Cake structures a web >««V>̰Ê9ÕÊÃ
Õ`ÊviiÊVv`iÌÊÌ
>ÌÊi>À}Ê >iÊÃÊiÊvÊÌ
iÊLiÃÌÊ`iVÃÃÊvÀÊ>Þone interested in PHP programming and with a need to write rapid web applications. But do note, sometimes it may be better to write basic methods like “Hello World” in a simple PHP script, rather than using Cake, so that you don’t end up killing an ant with a sledge hammer. In the following chapters, we’ll present full-fledged Cake applications, beginning with a simple blogging application.
27
CHAPTE R
2
Blogging T
he Web has revolutionized the way we communicate with friends and strangers. We now freely exchange media content, such as textual information, graphics, audio, and video. One of the ways to exchange such information is known as blogging. Blogging uses HTML forms for tasks such as submitting posts, uploading content, and so on. In the 1990s, blogging started like a kiddie joke, with individuals posting their personal stuff online. Since then, there has been an explosion of blogging web sites. Nowadays, movie stars, politicians, and corporate organizations such as Microsoft host their own blogging sites to communicate their ideas. This chapter describes how to build your own blogging application. But why would you bother to develop such an application when you can use one of the many free or low-cost solutions, such as Blogger, Movable Type, Textpattern, WordPress, TypePad, or LiveJournal (to name a few)? The ready-made blogger solutions have common interface design features. Developing your own blogging application allows you to customize the site, giving it a unique look, excluding unnecessary features, and adding features that are not supplied with the prebuilt sites. In this chapter, we’ll build our own blog application, which will enable us to list, add, edit, delete, publish, and unpublish posts. We will use Cake’s form helper to automate some tasks, such as to generate form elements, validate user-submitted data, and repopulate invalid form elements with submitted data. We will insert the post data into an XML file to provide RSS service to those with an RSS reader (or aggregator). To create this blog application, you need a web server that supports PHP and a database server to store some information. If you are new to the concepts and the workings of Cake, read Chapter 1 before continuing with this chapter.
Creating the Database Building web sites that allow user interactivity sometimes requires working with persistent data, which can be stored in relational databases or local file systems. This requirement applies to building our blog application, as we need to manage the post records. As mentioned in the previous chapter, we’ll use the MySQL database server for the examples in this book. We’ll use Cake’s objects and their methods that allow us to store and retrieve data from a database. For information about how to configure Cake’s database connection parameters and connect to a database, see Chapter 1.
29
30
C HAPTER 2 N BLO G G ING
Our >hkc database will contain a single table named lkopo. This table will store records of posts. The records include fields for an ID to provide a unique reference for each post, the title of a post, the post’s content, the dates that a post was created and modified, and whether or not a post should be published (displayed to the public). Listing 2-1 shows the SQL to create the lkopo table. Listing 2-1. The SQL Statement to Create the posts Table ?NA=PAP=>HAEBJKPATEOPO\lkopo\$ \e`\ejp$--%JKPJQHH]qpk[ej_naiajp( \pepha\r]n_d]n$1,%`ab]qhpJQHH( \_kjpajp\patp( \_na]pa`\`]papeia`ab]qhpJQHH( \ik`ebea`\`]papeia`ab]qhpJQHH( \lq^heoda`\pejuejp$-%JKPJQHH`ab]qhp#-#( LNEI=NUGAU$\e`\% %7 The SQL schema shown in Listing 2-1 will handle basic post information. If you like, you can add more fields, such as a oqii]nu field to store summaries of posts. Now, let’s insert some sample post information into our lkopo table, using the following SQL statements: EJOANPEJPK\lkopo\£ $\e`\(\pepha\(\_kjpajp\(\_na]pa`\(\ik`ebea`\(\lq^heoda`\%R=HQAO $-(#=jkpdan`]uOpehhHkkgejc#(#IuHekjn]jkbb#(£ #.,,4),2)-5-46.26--#(#.,,4),2)-5-46.26--#(-%( $.(#=ckk``]u#(#PdaHekjeo^]_gejkjalea_a*#(£ #.,,4),2)-5-46/-61,#(#.,,4),2)-5-46/-61,#(-%( $/(#Pd]jgCK@#(#Aranupdejc^ahkjcopkiub]pdan#(£ #.,,4),2).,-460.6--#(#.,,4),2).,-460.6--#(-%7 If you added other fields, be sure to insert their corresponding values with SQL EJOANP statements.
NTip Cake will automatically populate the _na]pa` and ik`ebea` fields in a table with the current date information (in our example, the dates when saving and updating posts). In Cake, these fields are called automagic model fields.
C H A P T E R 2 N B LO G G I N G
Reviewing the Application Structure Before we start to build the blog application, let’s take a brief look at the folder structure and files that will form part of the application. Table 2-1 assumes that other default Cake folders and files also exist in the same environment. Table 2-1. The Blog Application Structure
Directory
Description
]ll+
The parent folder for the blog application
_kjbec+
Amended `]p]^]oa*ldl file to include our blog database parameters
_kjpnkhhano+
lkopo[_kjpnkhhan*ldl file, which contains all the actions, such as add post
ik`aho+
lkop*ldl file to deal with our blog application data
reaso+lkopo
ej`at*_pl, ]``*_pl, a`ep*_pl, and `ahapa*_pl files
We’ll create these files and explain their contents in upcoming sections. For details on Cake’s complete file system structure, refer to Chapter 1.
Creating the Post Model The Lkop object manages the post data. By using Cake’s naming convention, we’ll be able to take advantage of the functionality inherently provided by Cake. We’ll use that functionality to interact with the lkopo database table, and access and manipulate the post records. The Lkop model class, shown in Listing 2-2, is stored in ]ll+ik`aho+lkop*ldl. Listing 2-2. The Post Object That Handles the Post Data (app/models/post.php) 8;ldl _h]ooLkopatpaj`o=llIk`ah w r]n j]ia9#Lkop#7 r]n r]he`]pa9]nn]u$#pepha#9:]nn]u$ #]hld]Jqiane_#9:]nn]u$ #nqha#9:#]hld]Jqiane_#( #namqena`#9:pnqa( #iaoo]ca#9:#Ajpan]pephabknpdeolkop#( % %( #_kjpajp#9:]nn]u$ #]hld]Jqiane_#9:]nn]u$ #nqha#9:#]hld]Jqiane_#( #namqena`#9:pnqa( #iaoo]ca#9:#Ajpanokia_kjpajpbknukqnlkop#( % % %7 y;:
31
32
C HAPTER 2 N BLO G G ING
The Lkop model class consists of the j]ia property, used to handle PHP 4 backwardcompatibility, and the r]he`]pa array property, which contains the validation rules. In the r]he`]pa array, each element’s key corresponds to the name of the input element to be validated (for example, pepha), and its value defines the rules to apply against the input before the post data is saved to the lkopo table—when the post form is submitted. Listing 2-2 shows the validation rules. We check that the pepha and the _kjpajp fields of the form are not empty when the form is submitted. We also check whether the values submitted are alphanumeric. If not, the corresponding error messages set in the validation array against the #iaoo]ca# keys will be displayed. The Lkopo?kjpnkhhan object, which we will create next, will use the Lkop model object to access information from the lkopo table, ensure the integrity of the submitted post information, and then commit the post into the lkopo database table.
Creating the Posts Controller Now that the Lkop model class is created, we need a Lkopo?kjpnkhhan object to manage all the post actions. These actions include listing all the posts and providing the user interfaces for adding and editing post data. The controller calls the Lkop model object created in Listing 2-2 to handle the post data as required. The Lkopo?kjpnkhhan class will contain the methods listed in Table 2-2. Table 2-2. The PostsController Class Actions
Method
Description
ej`at
%$Lists all the posts from the lkopo table and handles the RSS feed for posts
]``
%$Invokes the add post page and saves validated posts to the lkopo table
a`ep
%$Invokes the edit post page
`eo]^ha
%$Disables a published post
aj]^ha
%$Enables a disabled post so it’s published
`ahapa
%$Removes a post record from the lkopo table
Listing the Posts The Lkopo?kjpnkhhan class, shown in Listing 2-3, extends the =ll?kjpnkhhan class. This file is stored in ]ll+_kjpnkhhano+lkopo[_kjpnkhhan*ldl.
C H A P T E R 2 N B LO G G I N G
Listing 2-3. The PostsController to Define Post Actions (app/controllers/posts_controller.php) 8;ldl _h]ooLkopo?kjpnkhhanatpaj`o=ll?kjpnkhhan w r]n j]ia9#Lkopo#7 bqj_pekjej`at$%w lkopo9pdeo):Lkop):bej`$#]hh#%7 pdeo):oap$_kil]_p$#lkopo#%%7 y First, we add the ej`at method, which displays the list of posts. By default, this method is called if no other action is called explicitly during a URL request. Along with showing all the published posts, the index page contains links that will enable users to perform operations such as edit, publish, unpublish, and delete a post record. In Listing 2-3, the ej`at method contains two simple statements. The first uses the Lkop model object with its default bej` method to pull all the posts from the lkopo database table and then store the results in an array called lkopo. The second prepares and sets the lkopo records so that the reaso+lkopo+ej`at*_pl file, shown in Listing 2-4, can display the list of all the posts from the lkopo variable. Listing 2-4. The View for the Post List (views/posts/index.ctp) 8`ere`9_ajpan[_kjpajp: 8d.:LkopHeopejco8+d.: 8l:Danaeo]heopkbpdaateopejclkopo*8+l: 8`er: 8+`er: 8;ldl eb$eooap$ lkopo%""eo[]nn]u$ lkopo%%w ;: 8p]^ha: 8pn: 8p`: 8^:E@8+^: 8+p`: 8p`: 8^:pepha8+^: 8+p`: 8p`: 8^:_kjpajp8+^: 8+p`: 8p`: 8^:H]opIk`ebea`8+^: 8+p`: 8p`: 8^:lq^heoda`8+^: 8+p`: 8p`_khol]j9.:8^:"j^ol7"j^ol7=_pekj8+^:8+p`: 8+pn:
33
34
C HAPTER 2 N BLO G G ING
8;ldlbkna]_d$ lkopo]o lkop%6;: 8pn: 8p`:8;ldla_dk lkopW#Lkop#YW#e`#Y7;:8+p`: 8p`:8;ldla_dk lkopW#Lkop#YW#pepha#Y7;:8+p`: 8p`:8;ldla_dk lkopW#Lkop#YW#_kjpajp#Y7;:8+p`: 8p`:8;ldla_dk lkopW#Lkop#YW#ik`ebea`#Y7;:8+p`: 8p`: 8;ldla_dkdpih):hejg$eba$ # lkopW#Lkop#YW#lq^heoda`#Y99-#( #Lq^heoda`#( #Qjlq^heoda`#%( #+lkopo+#*eba$# lkopW#Lkop#YW#lq^heoda`#Y99-#( #`eo]^ha#(#aj]^ha#%*#+#* lkopW#Lkop#YW#e`#Y %7 ;: 8+p`: 8p`: 8;ldla_dkdpih):hejg$ #A`ep#( #+lkopo+a`ep+#* lkopW#Lkop#YW#e`#Y%7;: 8+p`: 8p`: 8;ldla_dkdpih):hejg$ #@ahapa#( #+lkopo+`ahapa+#* lkopW#Lkop#YW#e`#Y%7;: 8+p`: 8+pn: 8;aj`bkna]_d7;: 8;ldl eb$oevakb$ lkopo%99,%w ;: 8pnopuha9^]_gcnkqj`)_khkn6______7: 8p`_khol]j92: 8ol]jopuha9bkjp)oeva6-3lt7: Jklkopbkqj`* 8+ol]j: 8+p`: 8+pn: 8;ldl y ;: 8+p]^ha: 8^n+: 8;ldl y ;: 8+`er:
C H A P T E R 2 N B LO G G I N G
The ej`at*_pl file in Listing 2-4 starts with the headings of the web page that displays the posts list. We then insert a section of PHP code immediately after these headings. If the lkopo variable set by the Lkopo?kjpnkhhan object contains some post records, we first display the headings for the individual elements in the variable. Next, using a bkna]_d loop statement to loop through the lkopo variable, we display a list item for each post. Finally, if the lkop variable is empty, we simply display the message Jklkopbkqj`. Figure 2-1 shows an example of a post listing.
Figure 2-1. Viewing the post listings page This list provides an interface to directly manage individual posts. As shown on the right side of Figure 2-1, the page has links to trigger the publish, edit, and delete actions.
Adding a Post The next method we need to implement in the Lkopo?kjpnkhhan class is the ]`` method, as shown in Listing 2-5. This method, as the name implies, handles adding post data.
35
36
C HAPTER 2 N BLO G G ING
Listing 2-5. The add Method for Adding Post Data bqj_pekj]``$%w ]_pekjDa]`ejc9#=``]Lkop#7 ]_pekjOhkc]j9#Lha]oabehhej]hhbeah`o*Baahbnaapk]``ukqn£ lkop]j`atlnaooukqnklejekj*#7 pdeo):oap$_kil]_p$#]_pekjDa]`ejc#(#]_pekjOhkc]j#%%7 eb$ailpu$ pdeo):`]p]%%w pdeo):Lkop):_na]pa$%7 eb$ pdeo):Lkop):o]ra$ pdeo):`]p]%%w pdeo):Oaooekj):oapBh]od$[[$#PdaLkopd]o^aajo]ra`#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 yahoaw pdeo):Oaooekj):oapBh]od$£ [[$#PdaLkop_kqh`jkp^ao]ra`*Lha]oapnu]c]ej*#(pnqa%%7 y y y In the ]`` method, the first two statements set the heading and slogan for the add view page. This is necessary because we are going to use a single element view to display the forms to add and edit posts. Elements in Cake enable you to reuse views. Next, we check if the add post form has been submitted. If the form has not been submitted, the add view is displayed. If the submitted data $ pdeo):`]ta) is not empty, using the o]ra method of the Lkop model object, the application will attempt to create a new post record. The o]ra method automatically uses the validation rules defined in Listing 2-2 to check the integrity of the submitted post. If the post does not pass the validation rules, the error message is set, using the oapBh]od method of the Oaooekj object. Otherwise, the post is saved to the database table, and the success message is set for display in the view. Next, we’ll create the add view and store the codes in reaso+lkopo+]``*_pl file. The content of the ]``*_pl file is simply the following code snippet: 8;ldla_dkpdeo):ahaiajp$#]``[kn[a`ep#%7;: The pdeo):ahaiajp$% method accepts the name of a file stored in the reaso+ahaiajpo folder (]``[kn[a`ep in this case), without the file extension (without *_pl). It simply transfers the content of ]``[kn[a`ep*_pl into the ]``*_pl file. The resulting source code for the add view is shown in Listing 2-6.
C H A P T E R 2 N B LO G G I N G
Listing 2-6. The Add View That Provides an Interface to Add a Post (app/views/posts/add.ctp) 8beah`oap: 8hacaj`:8;ldl[[$#=``]Lkop#%7;:8+hacaj`: Lha]oabehhej]hhbeah`o* 8;ldl a_dkbkni):_na]pa$#Lkop#%7 a_dkbkni):annkn$#Lkop*pepha#%7 a_dkbkni):ejlqp$#Lkop*pepha#( ]nn]u$#e`#9:#lkoppepha#(#h]^ah#9:#Pepha6#(£ #oeva#9:#1,#(#i]thajcpd#9:#.11#(#annkn#9:b]hoa%%7 a_dkbkni):annkn$#Lkop*_kjpajp#%7 a_dkbkni):ejlqp$#Lkop*_kjpajp#( ]nn]u$#e`#9:#lkop_kjpajp#(#pula#9:#patp]na]#(£ #h]^ah#9:#?kjpajp6#(#nkso#9:#-,#(#annkn#9:b]hoa%%7 a_dkbkni):aj`$]nn]u$#h]^ah#9:#Oq^iepLkop#%%7 ;: 8+beah`oap: In Listing 2-6, we start by displaying the heading of the interface for adding a post. We then insert a PHP opening code tag to house the creation of the form using Cake’s form helper functionality. First, the bkni):_na]pa$% method defines the start tag for our form. Its #Lkop# string argument represents the action that will be invoked, such as the URL to which the form data will be submitted. Note that if the method attribute is not specified, the LKOP method is the default request method. Next, we start to add the required form input elements using the bkni):annkn$% method, which deals with the error handling of the form. Its argument, Lkop*pepha, is a string that represents the name of the input element, where Lkop is the model name, followed by a dot (.), and pepha holds the value of the post’s title. Next is the bkni):ejlqp$% method to generate the text input element called pepha, whose first argument is in the same argument format as the error input element. The second argument of the text input element is an associative array of HTML text input element attributes. Following that is the code that generates the patp]na] input element called _kjpajp using an argument format similar to the pepha input element discussed previously. Finally, after we’ve added the form elements, we can add the form closing tag using the form helper method bkni):aj`(). It also accepts an associative array of HTML submit input element attributes. Figure 2-2 shows an example of an add post form when a user tries to submit a blank form. The error messages are displayed.
37
38
C HAPTER 2 N BLO G G ING
Figure 2-2. Error messages appear when you try to submit a blank add post form.
Updating a Post Sometimes, we are not completely satisfied with our posts and would like to make some amendments. We will create the a`ep action of the Lkopo?kjpnkhhan to handle this task. It uses a supplied post ID (in our case e`) to retrieve the details of a post from the lkopo database table and repopulates the edit form with the information. This a`ep method code snippet is shown in Listing 2-7.
C H A P T E R 2 N B LO G G I N G
Listing 2-7. The edit Action That Handles a Post Edit Request bqj_pekja`ep$ e`9jqhh%w ]_pekjDa]`ejc9#A`ep]Lkop#7 ]_pekjOhkc]j9#Lha]oabehhej]hhbeah`o*Jksukq_]j]iaj`ukqnlkop*#7 pdeo):oap$_kil]_p$#]_pekjDa]`ejc#(#]_pekjOhkc]j#%%7 eb$ e`""ailpu$ pdeo):`]p]%%w pdeo):Oaooekj):oapBh]od$[[$#Ejr]he`Lkop#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 y eb$ailpu$ pdeo):`]p]%%w eb$ pdeo):Lkop):o]ra$ pdeo):`]p]%%w pdeo):Oaooekj):oapBh]od$[[$#PdaLkopd]o^aajo]ra`#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 yahoaw pdeo):Oaooekj):oapBh]od$[[£ $#PdaLkop_kqh`jkp^ao]ra`*Lha]oapnu]c]ej*#(pnqa%%7 y y eb$ailpu$ pdeo):`]p]%%w pdeo):`]p]9pdeo):Lkop):na]`$jqhh( e`%7 y y In Listing 2-7, we first set the heading and slogan for the edit view page. The next step we take is to check whether e` and pdeo):`]p] (form data) are empty. If so, an error message is stored in our Oaooekj object, and the request is redirected to the blog home page. If the submitted form data is not empty, Cake will try to commit the edited post information to the lkopo database table and then flash appropriate messages upon success or failure. Finally, if only the submitted data is empty, a post’s information is pulled with the Lkop model na]` method using the supplied e` as the criterion. Next, we’ll create the edit view and store the codes in the reaso+lkopo+a`ep*_pl file. The content of this file is exactly the same as that of the ]``*_pl file: 8;ldla_dkpdeo):ahaiajp$#]``[kn[a`ep#%7;: Here, we’ve reused the ]``[kn[a`ep*_pl element file again to produce the source code for the edit post view, taking advantage of the elements resource of Cake. In principle, the views for adding and editing posts are the same, except for their page headings. Figure 2-3 shows an example of the edit view for post ID 1.
39
40
C HAPTER 2 N BLO G G ING
Figure 2-3. The view to update post ID 1 We’ve built our post forms using one of the key features of Cake: form helpers. These helpers have automated the tasks of generating our form elements, validating the form input, and repopulating the submitted data.
Unpublishing a Post When you don’t want a post record to be displayed on the home page, you can disable the record. Given a post e`, the `eo]^ha method disables the appropriate post, as shown in Listing 2-8. Listing 2-8. The disable Action to Disable a Published Post bqj_pekj`eo]^ha$ e`9jqhh%w lkop9pdeo):Lkop):na]`$jqhh( e`%7 eb$ e`""ailpu$ lkop%%w pdeo):Oaooekj):oapBh]od$£ [[$#Ukqiqoplnkre`a]r]he`E@jqi^anpk`eo]^ha]lkop*#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 y eb$ailpu$ lkop%%w lkopW#Lkop#YW#lq^heoda`#Y9,7 eb$ pdeo):Lkop):o]ra$ lkop%%w pdeo):Oaooekj):oapBh]od$[[$#LkopE@#* e`*#d]o^aaj`eo]^ha`*#(pnqa%%7
C H A P T E R 2 N B LO G G I N G
yahoaw pdeo):Oaooekj):oapBh]od$[[$#LkopE@#* e`*#s]ojkpo]ra`*#(pnqa%%7 y pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 yahoaw pdeo):Oaooekj):oapBh]od$[[$#JkLkop^upd]pE@s]obkqj`*#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 y y In Listing 2-8, using the request e`, a post record is retrieved from the lkopo database table and stored in the lkop array variable. If the e` value is null or the lkop variable is empty, we use the Oaooekj object to set the appropriate message and redirect to the blog home page. If there is a valid e` and the lkop is not empty, we set the post published element to , and update the lkopo database table. Finally, the Oaooekj object sets the appropriate message, and then we redirect to the blog home page.
Publishing a Post The aj]^ha method does the opposite of the `eo]^ha method. It uses the supplied post ID ( e`) to determine which post record to enable, as shown in Listing 2-9. Listing 2-9. The enable Action to Enable (Publish) a Disabled Post bqj_pekjaj]^ha$ e`9jqhh%w lkop9pdeo):Lkop):na]`$jqhh( e`%7 eb$ e`""ailpu$ lkop%%w pdeo):Oaooekj):oapBh]od$£ [[$#Ukqiqoplnkre`a]r]he`E@jqi^anpkaj]^ha]lkop*#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 y eb$ailpu$ lkop%%w lkopW#Lkop#YW#lq^heoda`#Y9-7 eb$ pdeo):Lkop):o]ra$ lkop%%w pdeo):Oaooekj):oapBh]od£ $[[$#LkopE@#* e`*#d]o^aajlq^heoda`*#(pnqa%%7 yahoaw pdeo):Oaooekj):oapBh]od$[[$#LkopE@#* e`*#s]ojkpo]ra`*#(pnqa%%7 y pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 yahoaw pdeo):Oaooekj):oapBh]od$[[$#JkLkop^upd]pE@s]obkqj`*#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 y y In Listing 2-9, the aj]^ha method contains the same code sections as that of the `eo]^ha method in Listing 2-8. The difference is in the aj]^ha method, the statement lkopW#Lkop#Y W#lq^heoda`#Y is set to -.
41
42
C HAPTER 2 N BLO G G ING
Deleting a Post We can do some housekeeping by removing posts that are no longer needed. We’ll use the `ahapa method to remove posts from the lkopo database table. Listing 2-10 shows the code for the `ahapa method. Listing 2-10. The delete Action to Remove Posts bqj_pekj`ahapa$ e`9jqhh%w eb$ e`%w pdeo):Oaooekj):oapBh]od$[[$#Ejr]he`e`bknLkop#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 y eb$ pdeo):Lkop):`ah$ e`%%w pdeo):Oaooekj):oapBh]od$[[$#Lkop`ahapa`#(pnqa%%7 pdeo):na`ena_p$]nn]u$#]_pekj#9:#ej`at#%%7 y y ;: When the `ahapa action is invoked by requesting to delete a post, the dialog box shown in Figure 2-4 appears. Clicking the OK button will permanently remove the selected post record from the database table.
Figure 2-4. The delete confirmation dialog box
C H A P T E R 2 N B LO G G I N G
Creating an RSS Feed Since we’ve decided to share our posts with the rest of the world, we can reduce the stress on our blog database by creating an RSS feed—a static XML file that will be updated whenever the posts change. RSS is an abbreviation for Really Simple Syndication. It is an XML web content syndication format that can be read by using news reader software (or through some online sites and scripts). For the blog application, using an RSS feed means that people can get updated information about your blog without needing to visit your blogging web site. Scripts such as robots can fetch your RSS feed so that your users can be kept informed of changes to your posts. Essentially, what we’ll create is an XML file of the information stored in the lkopo database table. Remember that our code will be writing to a static file stored somewhere on your web site, so it is important to ensure read-write privileges for the document. You can generate an RSS feed in several ways, such as with the Document Object Model (DOM), XMLWriter, SimpleXML, and so on. However, Cake provides a helper to handle RSS feeds, so you don’t need to worry about how to micromanage them. The RSS helper creates standards-compliant RSS feeds. You invoke its functionality with r]n dahlano9 ]nn]u$#Noo#%7. For the RSS feed for our blog application, we first need to include the following in our ]ll+_kjbec+nkqpao*ldl file: Nkqpan66l]noaAtpajoekjo$#noo#%7 This informs the router to parse out file extensions from the URL. For example, dppl6++ hk_]hdkop+lkopo*noo would yield a file extension of noo. It is used by NamqaopD]j`han components to automatically switch to alternative layouts and templates in order to load the NooDahlan with *noo content.
NCaution Don’t forget to add Nkqpan66l]noaAtpajoekjo$#noo#%7 to your ]ll+_kjbec+nkqpao*ldl file and also ensure that you add NamqaopD]j`han to your Lkopo?kjpnkhhan’s _kilkjajp variable. Things will not go well if the two instructions are not added. However, if you already have Nkqpan66l]noaAtpajoekjo, just add the noo string argument. Nkqpan66l]noaAtpajoekjo accepts many arguments.
Now we need to perform the following tasks: Ê
UÊ iÌiÀiÊÌ
iÊiiiÌÃÊvÀÊiÝ>«i]ÊÌ
iÊ`>Ì>Êvi`îÊÌ
>ÌÊÜiÊÜ>ÌÊÌÊ>««i>ÀÊÊÕÀÊ RSS feed.
Ê
UÊ Ài>ÌiÊÌ
iÊ>ÞÕÌÊvÀÊÕÀÊ,--É8ÊÛiܰ
Ê
UÊ Ài>ÌiÊÌ
iÊ«ÃÌÊ,--ÊÛiܰ
The first task requires that we tweak the ej`at method in the Lkopo?kjpnkhhan class by adding extra lines of code to it. In this case, we will add the channel array data and then fetch the ten most recent post records for public consumption. The content of our new ej`at method is shown in Listing 2-11.
43
44
C HAPTER 2 N BLO G G ING
Listing 2-11. The New index Method of the PostsController Modified for RSS bqj_pekjej`at$%w lkopo9pdeo):Lkop):bej`$#]hh#%7 _d]jjah@]p]9]nn]u$ #pepha#9:#?qnnajplkopoxPda^hkccan#( #hejg#9:]nn]u$ #_kjpnkhhan#9:#lkopo#(#]_pekj#9:#ej`at#(#atp#9:#noo#%( #qnh#9:]nn]u$#_kjpnkhhan#9:#lkopo#(#]_pekj#9:#ej`at#(£ #atp#9:#noo#%( #`ao_nelpekj#9:#Pda_qnnajplkopoejkqn^hkc#( #h]jcq]ca#9:#aj)qg# %7 lkopo9pdeo):Lkop):bej`$#]hh#( ]nn]u$#heiep#9:-,(#kn`an#9:#Lkop*_na]pa`#%%7 pdeo):oap$_kil]_p$#_d]jjah@]p]#(#lkopo#%%7 y In Listing 2-11, the channel and posts information are set for the views and layouts, which we’ll create next. The layout file, ]ll+reaso+h]ukqpo+noo+`ab]qp*_pl, will contain the following code snippet: 8;ldl a_dknoo):da]`an$%7 _d]jjah9noo):_d]jjah$]nn]u$%( _d]jjah@]p]( epaio%7 a_dknoo):`k_qiajp$]nn]u$%( _d]jjah%7 ;: Since Cake allows views to pass variables to the layout, we’ve set the epaio variable in the layout, instead of following the normal convention of setting variables in a controller. Next, we will create the RSS view in ]ll+reaso+lkopo+noo+ej`at*_pl. This file will contain a function called noo[pn]jobkni, which accepts the epaio (for example, post data) as an argument, and as its name implies, returns a transformed array version of the post data. Listing 2-12 shows the content of the RSS view file. Listing 2-12. The Content of the RSS View (app/views/posts/rss/index.ctp) 8;ldl bqj_pekjnoo[pn]jobkni$ epai%w napqnj]nn]u$#pepha#9: epaiW#Lkop#YW#pepha#Y( #hejg#9:]nn]u$#_kjpnkhhan#9:#lkopo#(#]_pekj#9:#reas#(£ #atp#9:#noo#( epaiW#Lkop#YW#e`#Y%( #cqe`#9:]nn]u$#_kjpnkhhan#9:#lkopo#(#]_pekj#9:#reas#(£
C H A P T E R 2 N B LO G G I N G
#atp#9:#noo#( epaiW#Lkop#YW#e`#Y%( #`ao_nelpekj#9:opnel[p]co$ epaiW#Lkop#YW#_kjpajp#Y%( #lq^@]pa#9: epaiW#Lkop#YW#_na]pa`#Y( %7 y pdeo):oap$#epaio#(noo):epaio$ lkopo(#noo[pn]jobkni#%7 ;: In Listing 2-12, the noo):epaio helper method converts our post data into an XML format and uses the oap method to pass the converted XML to the RSS layout. Here, the layout is going to do the work of rendering the output when you make the URL request lkopo+ej`at*noo. Your request should display an XML version of the posts as fetched in the Lkopo?kjpnkhhan ej`at action. When you choose to view the posts, you can select whether to save a copy of the RSS feed file or display it on the screen. Figure 2-5 shows an example of how this looks in Firefox.
Figure 2-5. Fetching posts from an RSS feed
45
46
C HAPTER 2 N BLO G G ING
Summary This chapter described how to build a blogging application using Cake. We first created a lkopo database table and populated it with some sample posts. We took advantage of Cake’s components by creating our posts controller and model classes to perform the necessary tasks: list, add, edit, delete, save, enable, disable, and access data. The corresponding action views were created using the bkni object methods. Finally, we created an RSS feed, with the help of the RSS helper, to present some of our posts to the public. The modularity of the blogging application allows it to be extended by adding more actions to the controller class. For example, you could add an action to upload images.
CHAPTE R
3
E-Commerce T
he buzzword e-commerce, short for electronic commerce, simply refers to the processing and recording of online transactions. To boast a competitive edge and increase profit, businesses and individuals that peddle services or products must not just have a web presence but also endeavor to sell their merchandise online. There are numerous e-commerce applications based on the PHP scripting language, such as the free, open source osCommerce and Magento. Here, we’re going to implement an online shop using the Cake framework. First, we’ll design a skeletal look and feel for our shop, and then we’ll create our shop database in MySQL. Next, we’ll populate the database with some category and product information. We’ll then use Cake features to allow users to select categories and products, add selected products to their shopping cart, click to check out, and, finally, make payments using the popular Google Checkout or PayPal payment system. This chapter assumes that you’re familiar with PHP, MySQL, and Cake. It also assumes that you have set up a development environment that supports these technologies.
The Online Shop Layout A typical online shop layout is divided into five sections (elements). We’ll follow that design and use the Cake view elements listed in Table 3-1. We’ll use Cake’s features to help us develop these elements of our shop application before stitching them together into the standard layout. Table 3-1. View Elements of Online Shop Layout
Division Name
Description
[Header]
Contains header information, such as the logo and banner
[Left Column]
Displays an expandable category level, sublevel, and product lists
[Center Column]
The main content area of the web site; what it contains depends on the visitor action, so it might show a product description, best-selling products, and so on
[Right Column]
Contains the mini-basket or other elements
[Footer]
Displays summary information about a shop, such as the copyright, contact information, shortcut links, and so on
47
48
C HAPTER 3 N E-C OMMER C E
NTip To include the left navigation element stored in ]ll+reaso+ahaiajpo+iajq*ldl in a view, use the code pdeo):ahaiajpo$#iajq#);.
Two Site Layouts In this example, we’re going to use two different layouts: Ê
UÊ ÃÌÊvÊÌ
iÊÜiLÊ«>}iÃÊÜÊÃ
ÜÊÌ
iÊvÛiÊÃiVÌÃÊvÊÌ
iÊÜiLÊÃÌiÊ>ÃÊÕÃÌÀ>Ìi`ÊÊ Figure 3-1. This first layout is stored in the ]ll+reaso+h]ukqpo+`ab]qhp*_pl file as the application default layout.
Ê
UÊ /
iÊÃiV`Ê>ÞÕÌÊÜÊiÝVÕ`iÊÌ
iÊÀ}
ÌÊVÕÊLÞÊV>«Ã}ÊÌ
iÊViÌiÀÊ>`ÊÌ
iÊÀ}
ÌÊ columns into one column. This layout stored in ]ll+reaso+h]ukqpo+_da_g^]oa*_pl. We’ll use this layout to display the _da_gkqp action of the orders controller.
Figure 3-1. The default layout
NNote One of the aims of this book is to show you how to properly organize elements (code snippets) of an application within the Cake’s structure in order to enjoy the benefit of Cake’s code reuse and ease of maintenance. However, in this chapter, we will not discuss how our online shop folders and files are organized, as this organization is identical to the structure explained in Chapter 1.
Layout of the Main Content When a user requests a web page, the `ab]qhp*_pl file takes care of the overall look and feel of the web site, which is shown in Figure 3-1. If you want to adapt the main layout of the shop application, just edit the file or create another layout, such as a _da_g^]oa*_pl file, to suit your needs. However, do not forget to include the h]ukqp9#_da_g^]oa#; statement in the method of your controller object that requires this new look.
CHAPTER 3 N E-COMMERCE
The following code displays only the main content area of the layout: 8`ere`9i]ej[_kjpajp[_kjp]ejan: 8`ere`9habpj]r:8;ldla_dkpdeo):ahaiajp$iajq#%7;:8+`er: 8`ere`9i]ej[^k`u[_kjp]ejan: 8;ldla_dk _kjpajp[bkn[h]ukqp7;: 8+`er: 8`ere`9necdp[j]r[_kjp]ejan: 8;ldla_dkpdeo):ahaiajp$#^]ogap#%7;: 8+`er: 8+`er: The pdeo):ahaiajp$iajq#% method is used to include the navigation view stored in the ]ll+reaso+ahaiajpo+iajq*_pl file. This file contains the view logic that generates the category tree menu that is displayed in the left column of the layout. This navigation section of the page is expected to display the product categories at all times. The _kjpajp[bkn[h]ukqp variable includes the view rendered in response to a user action. The pdeo):ahaiajp$#^]ogap#% method displays the mini-basket content in the right column of the layout.
The User Journey Like any successful dynamic web site, an online shop requires planning and a data store of some sort before jumping into the implementation of the application. These tasks include database design, program flow design, resource planning, and so on. Since we are fortunate to have an existing database schema to use for this example, we will skip the first step of categorizing and normalizing our shop data and move on to explaining the program flow, or user journey. The online shopping process starts with a customer surfing the Internet and ends with the customer parting with some cash, which ends up in some businessman’s online account. This basic flow for our online shop example looks like this: 1. A user visits our shop. 2. The user browses the categories and products. 3. The user views the product details. 4. The user adds products to the shopping basket. 5. The user clicks to check out and pay. 6. The customer receives an e-mail confirmation notice. In our example, users do not need to register. They browse categories and products, pay for selected items, and leave the shop. During the user journey, some vital transaction information is collected and stored in database tables. Needless to say, the tracked data is used for transaction completion and for making other business decisions.
49
50
C HAPTER 3 N E-C OMMER C E
Setting Up the Shop Database First, we need to connect our application to a MySQL database server. Our configuration, stored in the ]ll+_kjbec+`]p]^]oa*ldl file, includes the following array definition: r]n `ab]qhp9]nn]u$#`neran#9:#iuomh#( #lanoeopajp#9:b]hoa( #dkop#9:#hk_]hdkop#( #hkcej#9:#qoan#( #l]ooskn`#9:#l]ooskn`#( #`]p]^]oa#9:#odkl#( #lnabet#9:##( %7 Do not forget to replace the current database access information with your local username and password. If you need more information about how to configure a database connection, see Chapter 1. We'll create three database tables for our online shop application: Ê
UÊ /
iÊ_]packneao table stores product categories.
Ê
UÊ /
iÊlnk`q_po table stores the product descriptions.
Ê
UÊ /
iÊ_]npo table stores selected product items.
NNote We’ve said that we are not going to collect personal user information in our online shop application. So how do we identify a user? We will create and use a session ID for every unique user request. For information about implementing authentication, see Chapter 8. Also, some common online shop tables, such as the kn`an and kn`an[epai tables, are left out of this example. Any table schema not presented in this section is skipped for the sample implementation of this application.
As indicated in Figure 3-1, the left column of our shop layout will present a navigation menu tree that is generated from the product categories data. The _]packneao table and data are created by the SQL statements shown in Listing 3-1. Listing 3-1. The categories Table Schema ?NA=PAP=>HAEBJKPATEOPO\_]packneao\$ \e`\ejp$-,%qjoecja`JKPJQHH]qpk[ej_naiajp( \l]najp[e`\ejp$--%JKPJQHH`ab]qhp#,#( \j]ia\r]n_d]n$1,%_d]n]_panJKPJQHH( \`ao_nelpekj\r]n_d]n$.,,%_d]n]_panJKPJQHH( \ei]ca\r]n_d]n$.11%_d]n]_panJKPJQHH( LNEI=NUGAU$\e`\%( GAU\_]p[l]najp[e`\$\l]najp[e`\%( GAU\_]p[j]ia\$\j]ia\% %7
CHAPTER 3 N E-COMMERCE
EJOANPEJPK\_]packneao\$\e`\(\l]najp[e`\(\j]ia\(\`ao_nelpekj\(\ei]ca\%R=HQAO $-3(,(#F]vv#(#Aranupdejcbnki-45,o#(##%( $-.(,(#?h]ooe_]h#(#BnkiIa`ear]hpk?kjpailkn]nu#(##%( $-/(-3(#@evvuCehhaolea#(#PdaPnqilapanI]opan#(##%( $-0(-.(#Ikv]np#(#PdaKh`B]rkqnepa#(##%7 Next, our categories need to relate to some product information, which we’ll put in a lnk`q_po table. Listing 3-2 presents the SQL statements to create this table and populate it with some sample data. Listing 3-2. The products Table Schema ?NA=PAP=>HAEBJKPATEOPO\lnk`q_po\$ \e`\ejp$-,%qjoecja`JKPJQHH]qpk[ej_naiajp( \_]packnu[e`\ejp$-,%qjoecja`JKPJQHH`ab]qhp#,#( \j]ia\r]n_d]n$-,,%_d]n]_panJKPJQHH`ab]qhp##( \`ao_nelpekj\patp_d]n]_panJKPJQHH( \lne_a\`a_ei]h$5(.%JKPJQHH`ab]qhp#,*,,#( \mpu\oi]hhejp$1%qjoecja`JKPJQHH`ab]qhp#,#( \ei]ca\r]n_d]n$.,,%_d]n]_pan`ab]qhpJQHH( \pdqi^j]eh\r]n_d]n$.,,%_d]n]_pan`ab]qhpJQHH( \_na]pa`\`]papeiaJKPJQHH`ab]qhp#,,,,),,),,,,6,,6,,#( \ik`ebea`\`]papeiaJKPJQHH`ab]qhp#,,,,),,),,,,6,,6,,#( LNEI=NUGAU$\e`\%( GAU\_]p[e`\$\_]packnu[e`\%( GAU\j]ia\$\j]ia\% %7 EJOANPEJPK\lnk`q_po\$\e`\(\_]packnu[e`\(\j]ia\(\`ao_nelpekj\(£ \lne_a\(\mpu\(\ei]ca\(\pdqi^j]eh\(\_na]pa`\(\ik`ebea`\%R=HQAO $-(-/(#@evvu-55,o#(#>aopkb@evvuCehhaoleaejpda-55,o#(-.*,,(£ -,(JQHH(#-*flc#(#,,,,),,),,,,6,,6,,#(#,,,,),,),,,,6,,6,,#%( $.(-0(#Ikv]npbknHkrano#(#Nah]tsepdukqnhkra`kjasepdpdeo£ `kq^ha?@*#(-1*,,(1(JQHH(JQHH(#,,,,),,),,,,6,,6,,#(#,,,,),,),,,,6,,6,,#%( $..(-/(#@evvu]j`Op]j#(#Herasepd@evvuCehhaolea]j`Op]jCapv*#(£ -/*,,(-,(JQHH(#-*flc#(#,,,,),,),,,,6,,6,,#(#,,,,),,),,,,6,,6,,#%7 When a user selects a category, the products linked to this category are displayed. For example, when you select the category called Jazz, you’ll see the Dizzy 1990s item and the Dizzy and Stan item listed as products under this category. Next on the user journey, when a user has selected the type of music she wants to buy and she is happy with the vibes, she adds her selections to a shopping basket. The user-selected items are stored in the _]npo database table. This table and some sample data are created using the SQL in Listing 3-3.
51
52
C HAPTER 3 N E-C OMMER C E
Listing 3-3. The carts Table Schema ?NA=PAP=>HAEBJKPATEOPO\_]npo\$ \e`\ejp$-,%qjoecja`JKPJQHH]qpk[ej_naiajp( \lnk`q_p[e`\ejp$-,%qjoecja`JKPJQHH`ab]qhp#,#( \mpu\ia`eqiejp$4%qjoecja`JKPJQHH`ab]qhp#-#( \_p[oaooekj[e`\_d]n$/.%JKPJQHH( \_na]pa`\`]papeiaJKPJQHH`ab]qhp#,,,,),,),,,,6,,6,,#( LNEI=NUGAU$\e`\%( GAU\l`[e`\$\lnk`q_p[e`\%( GAU\_p[oaooekj[e`\$\_p[oaooekj[e`\% %7 EJOANPEJPK\_]npo\$\e`\(\lnk`q_p[e`\(\mpu\(\_p[oaooekj[e`\(\_na]pa`\%R=HQAO $1.(-(.(#/._]^^5`2b^/-0,0]1^3/24/,a]_2]]/#(#.,,4),5).0-160261/#%( $1-(.(/(#/._]^^5`2b^/-0,0]1^3/24/,a]_2]]/#(#.,,4),5).0-16.56,-#%7
NTip Some online shops store configuration information in a table. Alternatively, this information can be stored using Cake’s configuration method—for example, ?kjbecqna66snepa$#Odkl* j]ia#(#Okqj`Ailena#%7. To access the shop name, use Cake’s configuration na]` method, as in ?kjbecqna66na]`$#Odkl*j]ia#%. As you can imagine, a shop administrator would find it more difficult to update these configuration parameters than to work with information stored in a database table. Modifying the parameters would require physical file system access and consequently some manipulation fuss.
Interacting with the Online Shop Database Now that we’re connected to the database, created the required tables, and populated them with some sample data, our application needs the information stored in the database in order to provide the initial content of the web pages. For example, the site navigation requires the category data stored in the _]packneao table to do its bit. So who handles the tasks of accessing and manipulating information stored in the database? You’ll be happy to learn that Cake provides default model functions that serve as shortcuts to database operations. These prebuilt functions allow for easy and fast application development. Now let’s start digging out some information. First, we’ll create our custom objects to handle the display of an expandable category level, sublevel, and product lists.
CHAPTER 3 N E-COMMERCE
The Category Model Let’s create the ?]packnu model class to interact with the _]packneao table data created in Listing 3-1. The beginning of this model is shown in Listing 3-4. Listing 3-4. The Beginning of the Category Model (app/models/category.php) 8;ldl _h]oo?]packnuatpaj`o=llIk`ahw r]n j]ia9#?]packnu#7 r]n d]oI]ju9]nn]u$#Lnk`q_p#%7 In this model, we have two important properties: Ê
UÊ /
iÊ j]ia property will serve as a reference to this model object within the controllers of this application when needed.
Ê
UÊ /
iÊ>ÃÃV>ÌÊ«À«iÀÌÞÊ d]oI]ju simply defines the relationship between the _]packneao table and the lnk`q_po table. In this case, we define that a _]packneao record can relate to many lnk`q_po records. In our application, the category F]vv is associated with the products @evvu-55,o and @evvu]j`Op]j (see Listings 3-1 and 3-2). This property is required to ensure an association between the ?]packnu and Lnk`q_p model objects.
Now that the ?]packnu model class is attached to our _]packneao table data and it has established a relationship with the lnk`q_po table data, we can create some custom methods in the model class to interact with the _]packneao and lnk`q_po tables. These custom methods will internally use Cake’s default properties and methods, such as the bej` method for database create, read, update, and delete (CRUD) operations. The ?]packnu model class will contain three methods to provide information in the final format needed to generate the products category navigation: cap?]packneao, ^qeh`?]packneao, and cap?deh`?]packneao. The first method, cap?]packneao, returns the entire category list. This method is shown in Listing 3-5. Listing 3-5. The Category Model’s getCategories() Method bqj_pekjcap?]packneao$ beah`9#?]packnu*e`#( `ena_pekj9#=O?#%w napqnjpdeo):bej`$#]hh#(]nn]u$#kn`an#9: beah`*##* `ena_pekj%%7 y The method in Listing 3-5 is employed whenever we require the list of all the product categories from the _]packneao table, in ascending order of the category IDs. We’ve provided some parameters, beah` and `ena_pekj, so that it’s possible to use this function in more than one way elsewhere in the application. Next in this model class is the ^qeh`?]packneao method, which returns the currently selected category and its children categories. This method is shown in Listing 3-6.
53
54
C HAPTER 3 N E-C OMMER C E
Listing 3-6. The Category Model’s buildCategories() Method bqj_pekj^qeh`?]packneao$ _]packneao( l]najpE`% w ?deh`?]packneao9]nn]u$%7 e`o9]nn]u$%7 bkna]_d$ _]packneao]o _]packnu%w eb$ _]packnuW#?]packnu#YW#l]najp[e`#Y99 l]najpE`%w ?deh`?]packneaoWY9 _]packnuW#?]packnu#Y7 y e`oW _]packnuW#?]packnu#YW#e`#YY9 _]packnuW#?]packnu#Y7 y Dkh`L]najpE`9 l]najpE`7 sdeha$ Dkh`L]najpE`9,%w l]najp9]nn]u$ e`oW Dkh`L]najpE`Y%7 _qnnajpE`9 l]najpW,YW#e`#Y7 Dkh`L]najpE`9 e`oW Dkh`L]najpE`YW#l]najp[e`#Y7 bkna]_d$ _]packneao]o _]packnu%w eb$ _]packnuW#?]packnu#YW#l]najp[e`#Y99 Dkh`L]najpE`""£ ej[]nn]u$ _]packnuW#?]packnu#Y( l]najp%%w l]najpWY9 _]packnuW#?]packnu#Y7 y y ]nn]u[iqhpeoknp$ l]najp%7 j9_kqjp$ l]najp%7 ?deh`?]packneao.9]nn]u$%7 bkn$ e9,7 e8 j7 e''%w ?deh`?]packneao.WY9 l]najpW eY7 eb$ l]najpW eYW#e`#Y99 _qnnajpE`%w ?deh`?]packneao.9]nn]u[ianca$ ?deh`?]packneao.( ?deh`?]packneao%7 y y ?deh`?]packneao9 ?deh`?]packneao.7 y napqnj ?deh`?]packneao7 y This method takes two arguments: an array of the entire category records from the _]packneao table ( _]packneao) and a category’s parent ID ( l]najpE`). The cap?]packneao method in Listing 3-5 supplies the _]packneao array data used as the first argument in the ^qeh`?]packneao method in Listing 3-6.These two ?]packnu model methods, together with the pdeo):l]ooa`=ncoW#_#Y obtained from the user request, are used in the ?]packneao?kjpnkhhan class method called iajq to generate the application category navigation presented in the left column of the web pages. The ?]packneao?kjpnkhhan is discussed in the next section. In Listing 3-6, first we declare two array variables: ?deh`?]packneao and e`o. Using a bkn loop over _]packneao, if the current category’s parent ID equals that passed as an argument ( l]najpE`), we add the current category to the ?deh`?]packneao array variable. We also add
CHAPTER 3 N E-COMMERCE
the current category to the e`o array, making sure that the e`o array key is the category ID of the current category. This is followed by a sdeha loop to create a parent category and append children categories appropriately. Finally, the function formats and returns the current category list, which includes only the currently selected category and its children. This function is made so it can also handle deep category levels (more than two levels). Next, we create the cap?deh`?]packneao method, as shown in Listing 3-7. This method returns a list of the entire category IDs that belong to the children of a specified category. Listing 3-7. The Category Model’s getChildCategories() Method bqj_pekjcap?deh`?]packneao$ _]packneao( e`( na_qnoera9pnqa% w eb$ _]packneao99JQHH%w _]packneao9pdeo):cap?]packneao$%7 y j9_kqjp$ _]packneao%7 _deh`9]nn]u$%7 bkn$ e9,7 e8 j7 e''%w _]pE`9 _]packneaoW eYW#e`#Y7 l]najpE`9 _]packneaoW eYW#l]najp[e`#Y7 eb$ l]najpE`99 e`%w _deh`WY9 _]pE`7 eb$ na_qnoera%w _deh`9]nn]u[ianca$ _deh`( pdeo):cap?deh`?]packneao$ _]packneao( _]pE`%%7 y y y napqnj _deh`7 y The arguments supplied to the cap?deh`?]packneao method contain the following information, in the order presented here: Ê
UÊ _]packneao: All the category lists stored in the _]packneao table.
Ê
UÊ e`: A category ID that requires its children category IDs.
Ê
UÊ na_qnoera: Whether it should include all levels deep of children categories in the operation. Its default value is the Boolean pnqa.
Next in this method we use an eb statement to check for a valid category list, if the cap?deh`?]packneao method is not supplied with a valid category argument. This means that if there is no category information from the _]packneao table stored in the _]packneao array, we use the cap?]packneao method to generate the entire category list and assign the result to the _]packneao variable. Next, we count the number of elements in the _]packneao array and assign the result to the j variable. We then declare an array variable called _deh`, which will contain a list of the children category IDs.
55
56
C HAPTER 3 N E-C OMMER C E
We then loop over the _]packneao array variable with a bkn loop and assign a category ID and its parent ID to _]pE` and l]najpE`, respectively. Next, using an Eb statement, we check that the current parent ID ( l]najpE`) we’ve extracted is the one for which we want to obtain children category IDs. If the l]najpE` is the same as the one passed to the cap?deh`?]packneao method as an argument ( e`), we add the current category ID to the _deh` array variable. By default, this method will use itself to perform the same tasks based on the current category ID, unless the na_qnoera argument is set to Boolean b]hoa. Lastly, this method will return the _deh` array variable containing all children category IDs of a specified category parent ID. The cap?deh`?]packneao method in Listing 3-7 is used within the Lnk`q_po?kjpnkhhan class method called heopo to provide this function with a list of category IDs. This function uses the list of category IDs, formatted as w-(.(/y, to fetch a list of all products that belong to these category IDs, presented in the middle column of the web page. The Lnk`q_po?kjpnkhhan is discussed a little later in this chapter.
The Categories Controller Now that we’ve created the ?]packnu model, let’s delve into the ?]packneao?kjpnkhhan class, as shown in Listing 3-8. Listing 3-8. The CategoriesController Class (app/controllers/categories_controller.php) 8;ldl _h]oo?]packneao?kjpnkhhanatpaj`o=ll?kjpnkhhanw r]n j]ia9#?]packneao#7 bqj_pekjcap=hh$%w napqnjpdeo):?]packnu):cap?]packneao$%7 y bqj_pekjiajq$%w _]packneao9pdeo):cap=hh$%7 napqnjpdeo):?]packnu):^qeh`?]packneao$ _]packneao( pdeo):l]ooa`=ncoW#_#Y%7 y y ;: In the ?]packneao?kjpnkhhan, we make use of the services provided by the methods defined in the ?]packnu model, discussed in the previous section. Additionally, one of the properties inherited from the =ll?kjpnkhhan is the qoao array. This array includes an element that provides a reference to the ?]packnu model. Next, we declare the cap=hh method, which simply provides the list of all the categories information from the _]packneao table. This method returns the result of the pdeo):?]packnu): cap?]packneao$% statement when called by another object. The last method provided by the ?]packneao?kjpnkhhan is iajq. As the name implies, it helps with the creation of the categories navigation (menu) shown in the left column of the web site. The first statement of this method stores the categories information in the _]packneao array using the cap=hh method previously defined in this class. This array variable, as well as the current category ID (supplied by pdeo):l]ooa`=ncoW#_’]), is then passed to the ^qeh`_]packneao$% method to create an array formatted for the menu view. The array returned by the ^qeh`_]packneao method on the home page of the web site is structured as follows:
CHAPTER 3 N E-COMMERCE
=nn]u $ W,Y9:=nn]u $ We`Y9:-. Wl]najp[e`Y9:, Wj]iaY9:?h]ooe_]h W`ao_nelpekjY9:BnkiIa`ear]hpk?kjpailkn]nu Wei]caY9: % W-Y9:=nn]u $ We`Y9:-3 Wl]najp[e`Y9:, Wj]iaY9:F]vv W`ao_nelpekjY9:Aranupdejcbnki-45,o Wei]caY9: % % This array structure contains two first-level category records; that is, categories with their parent IDs equal to an integer value of , (zero). Next, we call the iajq method of the ?]packneao?kjpnkhhan within the ]ll+reaso+ahaiajpo+ iajq*_pl file to supply this view script with the categories information required to display the category navigation menu. The view takes the category list (stored in _]packneao) and displays its content in Cascading Style Sheets (CSS) 8qh: and 8he: tags in the left column of the layout. Since the category navigation is central to this application—that is, it must always be displayed—we’ve placed the following code snippet in the `ab]qhp*_pl layout file to include this element of the web site at all times: 8`ere`9habpj]r:8;ldla_dkpdeo):ahaiajp$iajq#%7;:8+`er: The content of the iajq*_pl view file is shown in Listing 3-9. Listing 3-9. The Category Navigation View for the Left Column (app/views/elements/menu.ctp) 8qh: 8he:8;9 dpih):hejg$#=hh?]packnu#(#+#%7;:8+he: 8;ldl _]packneao9pdeo):namqaop=_pekj$+_]packneao+iajq+_6 _]pE`+l6 l`E`+%7 bkna]_d$ _]packneao]o _]packnu%w atpn]_p$ _]packnu%7 harah9$ l]najp[e`99,%;-6.7 qnh9#+_]npo+ej`at+_]p[e`6#* e`7 eb$ harah99.%w j]ia9zzz* j]ia7 y heopE`9##7
57
58
C HAPTER 3 N E-C OMMER C E
eb$ e`99 _]pE`%w heopE`9#e`9_qnnajp#7 y ;: 8he8;ldla_dk heopE`7;::8;9 dpih):hejg$ j]ia( qnh%7;:8+he: 8;ldl y ;: 8+qh: In Listing 3-9, the first line begins with the 8qh: opening tag. Next, using the dpih helper object, we created a link to display the message #=hh?]packnu#. The first line of the 8;ldl opening tag uses Cake’s namqaop=_pekj function. It requests the iajq method of the ?]packneao?kjpnkhhan object to provide a list of categories information (stored in the _]packneao array variable) that match the category and the product IDs passed to this method using the _6 _]pE` and l6 l`E` parameters, respectively. Finally, using a bkna]_d loop, the _]packneao array content is extracted to generate the category navigation that appears in the left column throughout the application. Figure 3-2 shows the application response when the Classical menu item is selected by a user, with the submenu item Mozart displayed.
Figure 3-2. The screen showing levels of categories
CHAPTER 3 N E-COMMERCE
The Product Model Our next task is to implement the Lnk`q_p model class, which as the name suggests, provides product information from the lnk`q_po table. We will begin with the class properties, as shown in Listing 3-10. Listing 3-10. The Beginning of the Product Model (app/models/product.php) 8;ldl _h]ooLnk`q_patpaj`o=llIk`ah w r]n j]ia9#Lnk`q_p#7 r]n ^ahkjcoPk9]nn]u$#?]packnu#%7 In the j]ia property, the ^ahkjcoPk array variable tells the application that a product record belongs to a category. (The j]ia property works as described in the earlier discussion of the ?]packnu model.) Next is the heopo method, shown in Listing 3-11. This method provides all the product records based on one or more category IDs in the ascending order of product category ID. Listing 3-11. The Product Model’s lists() Method bqj_pekjheopo$ _]pE`o9JQHH%w eb$eo[]nn]u$ _]pE`o%%w naoqhpo9pdeo):bej`$#]hh#(]nn]u$ #_kj`epekjo#9:]nn]u$#Lnk`q_p*_]packnu[e`#9: _]pE`o%( #kn`an#9:#Lnk`q_p*_]packnu[e`=O?#%%7 napqnj naoqhpo7 y y
The Products Controller Now that we’ve created the Lnk`q_p model class, we need an object to provide some data and services to other objects in our shop application. The Lnk`q_po?kjpnkhhan class, shown in Listing 3-12, defines the heopo and reas methods to provide these services when called by other objects or scripts within the application. Listing 3-12. The ProductsController Class (app/controllers/products_controller.php) 8;ldl _h]ooLnk`q_po?kjpnkhhanatpaj`o=ll?kjpnkhhanw r]n j]ia9#Lnk`q_po#7 bqj_pekjheopo$%w _]packneao9pdeo):?]packnu):bej`$#]hh#( ]nn]u$#kn`an#9:#?]packnu*e`=O?#%%7 _]packneao9pdeo):?]packnu):^qeh`?]packneao$ _]packneao( pdeo):l]ooa`=ncoW#_#Y%7 _deh`naj[e`o9pdeo):?]packnu):cap?deh`?]packneao$ _]packneao( pdeo):l]ooa`=ncoW#_#Y(pnqa%7
59
60
C HAPTER 3 N E-C OMMER C E
]hh?]pE`o9]nn]u[ianca$]nn]u$ pdeo):l]ooa`=ncoW#_#Y%( _deh`naj[e`o%7 napqnjpdeo):Lnk`q_p):heopo$ ]hh?]pE`o%7 y bqj_pekjreas$%w naoqhp9pdeo):Lnk`q_p):bej`$#]hh#( ]nn]u$#_kj`epekjo#9:]nn]u$#Lnk`q_p*e`#9: pdeo):l]ooa`=ncoW#l`[e`#Y%%%7 eb$ailpu$ naoqhp%%w pdeo):na`ena_p$]nn]u$#_kjpnkhhan#9:#+a_kiian_a#(#]_pekj#9:#ej`at#%%7 y pdeo):oap$#lnk`q_p#( naoqhp%7 y y ;: The heopo method in Listing 3-12 returns a list of products based on one or more category IDs. The first statement in this method retrieves all the categories information and stores the result in the _]packneao array. This array is then used as the first argument of the ^qeh`?]packneao method of the ?]packnu model to provide a filtered set of category information, stored in the _]packneao array variable. The second argument, pdeo) :l]ooa`=ncoW#_#], of this method determines the family of category information that is required. Next is the cap?deh`?]packneao method, which accepts the newly created _]packneao array as the first argument, the current category ID (stored in pdeo):l]ooa`=ncoW#_’]), and a Boolean pnqa. We talked about this method earlier in the discussion of the ?]packnu model class (Listing 3-7). Here, this method returns the list of all children category IDs that belong to the category ID supplied as second argument of the method. Next, using the PHP ]nn]u[ianca function, the current category ID and that of the children are merged together and stored in the ]hh?]pE`o array variable. Finally, the ]hh?]pE`o variable is passed to the heopo method of the Lnk`q_p model, which returns a list of all products that belong to the category IDs available in the ]hh?]pE`o variable. The heopo method of the Lnk`q_po?kjpnkhhan is called or triggered when a user clicks a category. This method is called in the ]ll+reaso+ahaiajpo+lnk`q_po*_pl view file, which is shown in Listing 3-13. Listing 3-13. The Product List View for the Center Column (app/views/elements/products.ctp) 8;ldl lnk`q_po9pdeo):namqaop=_pekj$+lnk`q_po+heopo+_6 _]pE`+%7 bkna]_d$ lnk`q_po]o lnk`q_p%6;: 8`er_h]oo9lnk`q_p[_kjp]ejan: 8;ldl eb$ lnk`q_pW#Lnk`q_p#YW#pdqi^j]eh#Y%w pdqi^j]eh9#+eic+lnk`q_po+#* lnk`q_pW#Lnk`q_p#YW#pdqi^j]eh#Y7 yahoaw pdqi^j]eh9#jk)ei]ca)oi]hh*ljc#7 y
CHAPTER 3 N E-COMMERCE
a$dpih):hejg$ dpih):ei]ca$ pdqi^j]eh(]nn]u$#^kn`an#9:#,#%%( ]nn]u$#_kjpnkhhan#9:#lnk`q_po#( #]_pekj#9:reas+_]p[e`6 _]pE`+l`[e`6* lnk`q_pW#Lnk`q_p#YW#e`#Y %( ]nn]u$#ao_]la#9:b]hoa%%%7;: 8^n: 8;a_dkdpih):hejg$ lnk`q_pW#Lnk`q_p#YW#j]ia#Y(£ +lnk`q_po+reas+_]p[e`6 _]pE`+l`[e`6* lnk`q_pW#Lnk`q_p#YW#e`#Y%7;: 8^n:Lne_a6 8;9 lnk`q_pW#Lnk`q_p#YW#lne_a#Y7 ;: 8+`er: 8;aj`bkna]_d7;: In Listing 3-13, the first line uses Cake’s namqaop=_pekj function to request the heopo method of the Lnk`q_po?kjpnkhhan object to provide a list of product information that matches the category ID passed to this method via the _6 _]pE` parameter. The resulting list of products is stored in the lnk`q_po array variable. Finally, using a bkna]_d loop, the content of the lnk`q_po array is displayed in the center of the web page. The resulting view is shown in Figure 3-3.
Figure 3-3. The view rendered when a category ID is selected
61
62
C HAPTER 3 N E-C OMMER C E
The last method in Listing 3-12, reas, uses Cake’s oap method to automagically pass a full product description to the ]ll+reaso+lnk`q_po+reas*_pl file. Using the default bej` method against the Lnk`q_p object, our reas method retrieves a record of a product from the lnk`q_po table based on a specified product ID (retrieved from pdeo):l]ooa`=ncoW#l`[e`’]). This product information is stored in the naoqhp array variable. If naoqhp is empty, then the application redirects the user to the home page; otherwise, the current product information is displayed on the page. If the product ID is set, then the content of the product details view stored in the ]ll+ reaso+ahaiajpo+lnk`q_p[`ap]eho*_pl file will be displayed in the center column. This view file is shown in Listing 3-14. Listing 3-14. The Product Details View for the Center Column (app/views/elements/product_ details.ctp) 8;ldl eb$ lnk`q_pW,YW#Lnk`q_p#YW#ei]ca#Y%w l`[pdqi^j]eh9#+eic+lnk`q_po+#* lnk`q_pW,YW#Lnk`q_p#YW#ei]ca#Y7 yahoaw l`[pdqi^j]eh9#jk)ei]ca)oi]hh*ljc#7 y a$ dpih):ei]ca$ l`[pdqi^j]eh( ]nn]u$#^kn`an#9:#,#(#se`pd#9:#-1,#(#daecdp#9:#-1,#%%%7 ;: 8^n+: 8opnkjc:8;ldla_dk lnk`q_pW,YW#Lnk`q_p#YW#j]ia#Y7;:8+opnkjc: 8^n+: Lne_a68;ldla_dk?kjbecqna66na]`$#Odkl*_qnnaj_u#%7;: 8;ldla_dk lnk`q_pW,YW#Lnk`q_p#YW#lne_a#Y7;:8^n: 8;ldl eb$ lnk`q_pW,YW#Lnk`q_p#YW#mpu#Y:,%w a$ dpih):hejg$#=``pkOdkllejc>]ogap#( #+_]npo+]``+_]p[e`6#* _]pE`*#+l`[e`6#* lnk`q_pW,YW#Lnk`q_p#YW#e`#Y%%7 yahoaw a$#KqpKbOpk_g#%7 y ;: 8^n+: 8;ldl a$jh.^n$ lnk`q_pW,YW#Lnk`q_p#YW#`ao_nelpekj#Y%%7 ;: In Listing 3-14, we use the lnk`q_p array variable passed in using pdeo):oap$#lnk`q_p#( naoqhp% from the reas method of the Lnk`q_po?kjpnkhhan. It displays the product details in the center of the web page, as shown in the example in Figure 3-4.
CHAPTER 3 N E-COMMERCE
The Lnk`q_po?kjpnkhhan and other controllers employ the services of the Lnk`q_p and ?]packnu models. Reference to these models is available in the =ll?kjpnkhhan class, as discussed a little later in the chapter.
Figure 3-4. The index view rendered when a product is selected
The Cart Model Next, we’ll implement the ?]np model class. Along with supplying the content of the shopping cart to the application, the ?]np model provides functionalities to manipulate the cart, as well as the checkout section of the application. We will begin with the class properties, shown in Listing 3-15. Listing 3-15. The Beginning of the Cart Model (app/models/cart.php) 8;ldl _h]oo?]npatpaj`o=llIk`ah w r]n j]ia9#?]np#7 r]n d]oI]ju9]nn]u$#Lnk`q_p#%7 The ?]np class starts with the class definition, as usual, and then establishes its relationship with the Lnk`q_p model object using r]n d]oI]ju9]nn]u$#Lnk`q_p#%7. This simply states that a cart can contain one or more products.
63
64
C HAPTER 3 N E-C OMMER C E
We’ll create the following methods in the ?]np model: Ê
UÊ cap?]np
Ê
UÊ eo?]npAilpu
Ê
UÊ ]``?]np
Ê
UÊ ql`]pa?]np
Ê
UÊ _ha]jQl
Ê
UÊ ailpu>]ogap
Ê
UÊ `kQl`]pa
Ê
UÊ cap?]np?kjpajp
The cap?]np method supplies all the information stored in the _]npo table. This method is shown in Listing 3-16. Listing 3-16. The Cart Model’s getCart() Method bqj_pekjcap?]np$ le`( oe`%w napqnjpdeo):bej`$#]hh#(]nn]u$ #_kj`epekjo#9:]nn]u$#?]np*lnk`q_p[e`#9: le`( #?]np*_p[oaooekj[e`#9: oe`%( #kn`an#9:#?]np*e`=O?#%%7 y The cap?]np method takes two arguments: le` (product ID) and oe` (session ID). These are used in Cake’s bej` method to filter the provided _]npo table records. The product ID is extracted from a user’s URL request. The session ID is obtained from Cake’s Oaooekj object. These variables are extracted in the ^abknaBehpan method of the =ll?kjpnkhhan object, as you will see later in this chapter. Next is the eo?]npAilpu method, which checks whether there is at least one record in the _]npo table. This method is shown in Listing 3-17. Listing 3-17. The Cart Model’s isCartEmpty() Method bqj_pekjeo?]npAilpu$ oe`9JQHH%w naoqhp9pdeo):bej`$#benop#( ]nn]u$#_kj`epekjo#9:]nn]u$#?]np*_p[oaooekj[e`#9: oe`%( #na_qnoera#9:,%%7 eb$ailpu$ naoqhp%%w napqnjpnqa7 y napqnjb]hoa7 y The eo?]npAilpu method accepts oe` (session ID) as its argument and uses the bej` method to check whether the session ID exists in the _]npo table. If this session ID exists in the _]npo table, pnqa is returned; otherwise, b]hoa is returned, and the message “Shopping Basket is empty” is displayed. The status of the basket section, on the right side of the web site, depends on the value returned by this method.
CHAPTER 3 N E-COMMERCE
Next, we need the function to add a product to the _]npo table, ]``?]np, as shown in Listing 3-18. Listing 3-18. The Cart Model’s addCart() Method bqj_pekj]``?]np$ lnk`q_p[e`( oe`%w pdeo):`]p]W#?]np#YW#lnk`q_p[e`#Y9 lnk`q_p[e`7 pdeo):`]p]W#?]np#YW#mpu#Y9-7 pdeo):`]p]W#?]np#YW#_p[oaooekj[e`#Y9 oe`7 pdeo):o]ra$%7 y The ]``?]np method takes a product ID and the session ID as its arguments. Here, we use Cake’s model method o]ra to add the product ID, quantity, and session ID. We do not need to fill in the cart table’s _na]pa` field (see Listing 3-3), as this is one of Cake’s magic fields and will be filled in automatically by the model when the o]ra method is called. Now we need a function to update the quantity of a product in the _]npo table. This is the ql`]pa?]np, as shown in Listing 3-19. Listing 3-19. The Cart Model’s updateCart() Method bqj_pekjql`]pa?]np$ lnk`q_p[e`( oe`%w omh9QL@=PA_]npo OAPmpu9mpu'SDANA_p[oaooekj[e`9# oe`#=J@lnk`q_p[e`9 lnk`q_p[e`7 pdeo):mqanu$ omh%7 y This is triggered when a user clicks the Add to Shopping Basket link on the product description page. If the displayed product ID and session ID match that of the argument, the record quantity is incremented by one. Next, we include a function to remove old cart records, _ha]jQl. Sometimes customers add products to the shopping basket, but don’t bother to either complete the transaction or empty the basket. The method shown in Listing 3-20 will handle cleaning up abandoned carts' records. Listing 3-20. The Cart Model’s cleanUp() Method bqj_pekj_ha]jQl$%w pdnaa@]uo=ck9`]pa$#U)i)`D6e6o#( igpeia$,(,(,(`]pa$#i#%( `]pa$#`#%)/( `]pa$#U#%%%7 `ahapa[_kj`epekj9?]np*_na]pa`8# pdnaa@]uo=ck#7 pdeo):`ahapa=hh$ `ahapa[_kj`epekj(b]hoa%7 y The _ha]jQl method deletes records that were added to the basket three days ago from the _]npo table.
65
66
C HAPTER 3 N E-C OMMER C E
Next is the ailpu>]ogap method, shown in Listing 3-21. This method is used as the delete function that is triggered when a user clicks the Delete button on the checkout page. Listing 3-21. The Cart Model’s emptyBasket() Method bqj_pekjailpu>]ogap$ _]npE`9JQHH%w eb$ _]npE`%w pdeo):`ahapa$ _]npE`%7 y y The ailpu>]ogap method deletes a record from the _]npo table based on the cart ID. We also need to be able to update the quantity of products a customer wants to buy. This functionality is provided by the `kQl`]pa method, as shown in Listing 3-22. Listing 3-22. The Cart Model’s doUpdate() Method bqj_pekj`kQl`]pa$ jasMpu( _]pE`%w ++Ql`]palnk`q_pmq]jpepu pdeo):`]p]W#?]np#YW#mpu#Y9 jasMpu7 pdeo):e`9 _]pE`7 pdeo):o]ra$%7 y The `kQl`]pa method accepts two arguments: jasMpu supplies the new quantity of products the customer wants to purchase, and _]pE` specifies the ID of the _]npo table record to be updated. Finally, we add the cap?]np?kjpajp method to find out what’s in the cart, as shown in Listing 3-23. Listing 3-23. The Cart Model’s getCartContent() Method bqj_pekjcap?]np?kjpajp$ oe`%w _]np?kjpajp9]nn]u$%7 omh9OAHA?P_p*e`(_p*lnk`q_p[e`(_p*mpu(l`*j]ia(l`*`ao_nelpekj( l`*lne_a(l`*pdqi^j]eh(l`*_]packnu[e` BNKI_]npo_p(lnk`q_pol`(_]packneao_]p SDANA _p[oaooekj[e`9# oe`#=J@ _p*lnk`q_p[e`9l`*e`=J@ _]p*e`9l`*_]packnu[e`7 naoqhpo9pdeo):mqanu$ omh%7 bkna]_d$ naoqhpo]o naoqhp%w _]np?kjpajpWY9 naoqhp7 y napqnj _]np?kjpajp7 y y ;:
CHAPTER 3 N E-COMMERCE
The cap?]np?kjpajp method returns the contents of the _]npo table where the session ID field value matches the argument oe`. The result, stored in the _]np?kjpajp array, contains additional product and category information relating to the retrieved cart’s contents. If there is no match, an empty array variable is returned.
Handling User Requests Now that we have created all the functionality needed to interact with our database, we can begin the process of building our application’s controllers to handle user requests. We will need to decipher which action should be invoked and employ Cake’s MVC tricks to tie these actions to views that render the appropriate application display. We will start by creating the master controller, called the =ll?kjpnkhhan class. Our online shop controllers will borrow some properties and methods from this master controller to save the time of creating them in each individual controller.
The AppController Class By default, a Cake controller class extends the =ll?kjpnkhhan class. You’ve already been introduced to the ]ll+]ll[_kjpnkhhan*ldl class file in Chapter 1. Our shop =ll?kjpnkhhan class will contain all the common functionality required to centralize request handling and extract session IDs. The global properties and methods defined here are automatically available to the objects of requests or other classes that extend =ll?kjpnkhhan. Listing 3-24 shows the =ll?kjpnkhhan class properties. Listing 3-24. The Beginning of the AppController Class (app/app_controller.php) 8;ldl _h]oo=ll?kjpnkhhanatpaj`o?kjpnkhhanw r]n l]caPepha9#?d]lpan/)A_kiian_a#7 r]n oe`7 r]n _]pE`7 r]n l`E`7 r]n qoao9]nn]u$#Lnk`q_p#(#?]packnu#(#?]np#%7 r]n dahlano9]nn]u$#Bkni#(#Dpih#(#Oaooekj#(#F]r]o_nelp#%7 r]n _kilkjajpo9]nn]u$#Oaooekj#(#NamqaopD]j`han#(#Odkl#%7 The l]caPepha property sets an initial page title for this application, which can change according to a user’s page request. The oe` property will contain a session ID to identify our application user. As we mentioned earlier, to avoid repetition of code within this book, we’ve decided not to address authentication in this application. Hence, we’ve used the oe` variable to represent and track a user from page to page. The _]pE` and l`e` properties will be assigned the user-requested category and product values, respectively, when available. If these values are not available, we set the properties to an integer value of ,. Next in Listing 3-24, we include some of Cake’s built-in properties, such as the qoao array to require the Lnk`q_p, ?]packnu, and ?]np model objects. Then we include some helpers and components, which were introduced in Chapter 1.
67
68
C HAPTER 3 N E-C OMMER C E
Next, we override the ^abknaBehpan method, which is triggered first by default, before any other action in our =ll?kjpnkhhan, as shown in Listing 3-25. Listing 3-25. The AppController’s beforeFilter() Method bqj_pekj^abknaBehpan$%w eb$eooap$pdeo):l]ooa`=ncoW#_]p[e`#Y%"" $ejp% pdeo):l]ooa`=ncoW#_]p[e`#Y9-%w pdeo):_]pE`9$ejp% pdeo):l]ooa`=ncoW#_]p[e`#Y7 yahoaw pdeo):_]pE`9,7 y eb$eooap$pdeo):l]ooa`=ncoW#l`[e`#Y%"" pdeo) :l]ooa`=ncoW#l`[e`#Y9##%w pdeo):l`E`9$ejp% pdeo):l]ooa`=ncoW#l`[e`#Y7 yahoaw pdeo):l`E`9,7 y `]p]9pdeo):Oaooekj):na]`$%7 pdeo):oe`9 `]p]W#?kjbec#YW#qoan=cajp#Y7 pdeo):oap$#_]pE`#(pdeo):_]pE`%7 pdeo):oap$#l`E`#(pdeo):l`E`%7 pdeo):oap$#oe`#(pdeo):oe`%7 pdeo):oapL]caPepha$%7 y In Listing 3-25, the overridden method checks the URL of a user request to extract and then determine the category ID and product ID values. The ^abknaBehpan method ensures that during the course of the application, the _]pE` and l`E` variables contain some integer values. For example, with this URL: dppl6++hk_]hdkop+_]ga+[[_d]lpano[[+a_kiian_a+ the values of these two properties will default to ,. If the URL is as follows the value of the current category ID will be -. and the product ID will be ,. dppl6++hk_]hdkop+_]ga+[[_d]lpano[[+a_kiian_a+a_kiian_a+ej`at+_]p[e`6-. In this case, the pdeo):l]ooa`=ncoW#_]p[e`#Y array stores the requested value of _]p[e`6-. of the URL. The last statement of the ^abknaBehpan method, pdeo):oapL]caPepha(), ensures that as a user moves from page to page, the current page title is reflected. The oapL]caPepha method, which sets the current page title, is shown in Listing 3-26.
CHAPTER 3 N E-COMMERCE
Listing 3-26. The AppController’s setPageTitle() Method bqj_pekjoapL]caPepha$%w eb$pdeo):l`E`:,%w naoqhp9pdeo):Lnk`q_p):bej`$#]hh#( ]nn]u$#_kj`epekjo#9:]nn]u$#Lnk`q_p*e`#9:pdeo):l`E`%%%7 pdeo):l]caPepha9 naoqhpW,YW#Lnk`q_p#YW#j]ia#Y7 yahoaeb$pdeo):_]pE`:,%w naoqhp9pdeo):?]packnu):bej`$#]hh#( ]nn]u$#_kj`epekjo#9:]nn]u$#?]packnu*e`#9:pdeo):_]pE`%%%7 pdeo):l]caPepha9 naoqhpW,YW#?]packnu#YW#j]ia#Y7 y y If the current product ID value $ pdeo) :l`E`) is greater than zero, the value of pdeo):l`E` is used to query the lnk`q_po table in order to extract the corresponding product name. The l]caPepha property is then assigned the appropriate product name. If the current category ID value is greater than zero, then the category ID $ pdeo):_]pE` ) is used to pull the corresponding category name from the _]packneao table, and finally, this category name is used as the page title. If the function fails to set a page title, the l]caPepha remains ?d]lpan/ÌA_kiian_a, as defined in the property section of the =ll?kjpnkhhan. Now that we have defined the properties and functionalities that we require in our =ll?kjpnkhhan class, let’s tie things together to enable users to navigate our shop.
The Home Page The starting point is the definition of our preferred home page. The ]ll+_kjbec+nkqpao*ldl file contains information about handling user requests for our application. We use the following line in our nkqpao file: Nkqpan66_kjja_p$#+#(]nn]u$#_kjpnkhhan#9:#_]npo#(#]_pekj#9:#ej`at#%%7 This defines a controller (_]npo) and an action (ej`at) within the ?]npo?kjpnkhhan class that serve as our shop’s home page. When a user types the URL dppl6++hk_]hdkop+_]ga+ [[_d]lpano[[+a_kiian_a+ in a web browser address bar or clicks the Home link on the web site, the request is handled by the ej`at action of the ?]npo?kjpnkhhan object. We’ll create the ?]npo?kjpnkhhan in the following section.
The Carts Controller Listing 3-27 shows the carts controller for our shop application. Listing 3-27. The CartsController Class (app/controllers/carts_controller.php) 8;ldl _h]oo?]npo?kjpnkhhanatpaj`o=ll?kjpnkhhanw r]n j]ia9#?]npo#7 bqj_pekjej`at$%w y y ;:
69
70
C HAPTER 3 N E-C OMMER C E
The ?]npo?kjpnkhhan class begins by extending the =ll?kjpnkhhan class. It then sets its j]ia property to ?]npo to ensure backward compatibility with PHP 4. By default, the ej`at action is mapped to the ]ll+reaso+_]npo+ej`at*_pl view file. Remember that the layout of our shop contains a middle section, or center column. Our ej`at*_pl view handles what is rendered in this area of the web site layout. The content of this view file is shown is shown in Listing 3-28. Listing 3-28. The View for the Center Column Content (app/views/carts/index.ctp) 8;ldl a$#Sah_kiapkpdah]j`kbiqoe_ 8^n+: Lha]oaoaha_pukqnksjiqoe_lnk`q_p 8^n+: Pd]jgobkn`nkllejc^u8^n+:#%7 eb$ l`E`%w a$pdeo):ahaiajp$#lnk`q_p[`ap]eho#%%7 yahoaeb$ _]pE`%w a$pdeo):ahaiajp$#lnk`q_po#%%7 yahoaw a$pdeo):ahaiajp$#_]packneao#%%7 y ;: In Listing 3-28, we first render our welcome message using the a$% function, which is a Cake convenience method for PHP’s a_dk statement. You can change this message to suit your needs. Next, we use the current values of the product ID ( l`E`) and category ID ( _]pE`), as defined in the ^abknaBehpan method of the =ll?kjpnkhhan class (Listing 3-25), to logically control the view that is finally displayed to web surfers. When a user visits our shop for the first time, the values of the product ID and category ID will be set to zero, as defined in the ^abknaBehpan method of the =ll?kjpnkhhan, since the user has not yet selected a category or a product from the home page. For this scenario, the view logic will render the content stored at ]ll+reaso+ahaiajpo+_]packneao*_pl. We will also have this scenario when a user clicks the Home link of the web site. The content of this view is shown in Listing 3-29. Listing 3-29. The Category List View for the Center Column (app/views/elements/categories.ctp) 8;ldl _]packneao9pdeo):namqaop=_pekj$+_]packneao+cap=hh%7 bkna]_d$ _]packneao]o _]packnu%6;: 8`er_h]oo9lnk`q_p[_kjp]ejan: 8; eb$ _]packnuW#?]packnu#YW#ei]ca#Y%w _]p[pdqi^j]eh9#+eic+lnk`q_po+#* _]packnuW#?]packnu#YW#ei]ca#Y7 yahoaw _]p[pdqi^j]eh9#jk)ei]ca)oi]hh*ljc#7 y
CHAPTER 3 N E-COMMERCE
a$dpih):hejg$ dpih):ei]ca$ _]p[pdqi^j]eh(]nn]u$#^kn`an#9:#,#%%( ]nn]u$#]_pekj#9:#+ej`at+_]p[e`6#* _]packnuW#?]packnu#YW#e`#Y%( ]nn]u$#ao_]la#9:b]hoa%%%7;: 8^n: 8;a$dpih):hejg$ _]packnuW#?]packnu#YW#j]ia#Y(£ +_]npo+ej`at+_]p[e`6* _]packnuW#?]packnu#YW#e`#Y%%7;: 8+`er: 8;aj`bkna]_d7;: In Listing 3-29, we also use Cake’s namqaop=_pekj method to trigger the cap=hh method of the ?]packneao?kjpnkhhan. The result of this method is a list of all the category information, which is stored in the _]packneao array variable. This variable is then looped over to display the list of categories, as shown in Figure 3-5.
Figure 3-5. The view showing the list of categories Now, we’re finished with the left column content—the category navigation—and the center column content. Let’s move to the right column, which displays the current shopping basket (cart) content (see Figure 3-5). If there is no information in the _]npo table, we’ll display the message “Shopping Basket is empty” in this area of the page. The view code to generate the basket content is stored at ]ll+reaso+ahaiajpo+^]ogap*_pl. This code is shown in Listing 3-30.
71
72
C HAPTER 3 N E-C OMMER C E
Listing 3-30. The Basket View for the Right Column (app/views/elements/basket.ctp) 8p]^hae`9ieje_]np: 8;ldl _]np?kjpajpo9pdeo):namqaop=_pekj$+_]npo+capIeje?]np+o6 oe`%7 eb$ailpu$ _]np?kjpajpo%""eo[]nn]u$ _]np?kjpajpo%%w Pkp]h9,7 ;: 8pn: 8p`_khol]j9.:?]np?kjpajp8+p`: 8+pn: 8;ldl bkna]_d$ _]np?kjpajpo]o _]np?kjpajp%w Pkp]h'9 _]np?kjpajpW#l`#YW#lne_a#Y& _]np?kjpajpW#_p#YW#mpu#Y7 ;: 8pn: 8p`: 8;9 _]np?kjpajpW#_p#YW#mpu#Y7;:T 8;9 dpih):hejg$ _]np?kjpajpW#l`#YW#j]ia#Y( #+lnk`q_po+reas+l`[e`6#* _]np?kjpajpW#_p#YW#lnk`q_p[e`#Y*£ #_]p[e`6#* _]np?kjpajpW#l`#YW#_]packnu[e`#Y%7;:8+p`: 8p`se`pd9/,!]hecj9necdp:8;9?kjbecqna66na]`$#Odkl*_qnnaj_u#%7;: 8;9 _]np?kjpajpW#l`#YW#lne_a#Y& _]np?kjpajpW#_p#YW#mpu#Y7;:8+p`: 8+pn: 8;ldl y;: 8pn: 8p`]hecj9necdp:Pkp]h8+p`: 8p`se`pd9/,!]hecj9necdp: 8;9?kjbecqna66na]`$#Odkl*_qnnaj_u#%7;: 8;9 Pkp]h7;: 8+p`: 8+pn: 8pn: 8p`_khol]j9.:"j^ol78+p`:8+pn: 8pn: 8p`_khol]j9.]hecj9_ajpan:8;9 dpih):£ hejg$#CkPkOdkllejc?]np#(#+_]npo+reas#%7;:8+p`: 8+pn: 8;ldl yahoaw a_dk#8pn:8p`se`pd9-1,:Odkllejc>]ogapeoailpu8+p`:8+pn:#7 y ;: 8+p]^ha:
CHAPTER 3 N E-COMMERCE
Before we delve into the code in Listing 3-30, let’s add the product titled Mozart for Lovers to our basket by clicking the Add to Shopping Basket link on the product description page. Adding a product to the basket triggers the ?]npo?kjpnkhhan’s ]`` method, shown in Listing 3-31. Listing 3-31. The CartsController’s add() Method bqj_pekj]``$%w `]p]9pdeo):Lnk`q_p):bej`>uE`$pdeo):l`E`%7 eb$ailpu$ `]p]%%w pdeo):na`ena_p$#+#%7 yahoaw eb$ `]p]W#Lnk`q_p#YW#mpu#Y89,%w pdeo):Oaooekj):oapBh]od$#Pdalnk`q_pukqnamqaopa`eo£ jkhkjcanejopk_g#%7 pdeo):na`ena_p$#+#%7 y y oaooekj@]p]9pdeo):?]np):cap?]np$ pdeo):l`E`(pdeo):oe`%7 eb$ailpu$ oaooekj@]p]%%w pdeo):?]np):]``?]np$ pdeo):l`E`(pdeo):oe`%7 yahoaw pdeo):?]np):ql`]pa?]np$ pdeo):l`E`(pdeo):oe`%7 y pdeo):?]np):_ha]jQl$%7 pdeo):na`ena_p$]nn]u$#_kjpnkhhan#9:#Lnk`q_po#( #]_pekj#9:reas+_]p[e`6 pdeo):_]pE`+l`[e`6 pdeo):l`E`%%7 y In Listing 3-31, the first statement uses Cake’s bej`>uE` model method, which accepts a product ID $ pdeo):l`Id) to retrieve a product’s information from the lnk`q_po table. This product information is stored in the `]p] array variable. If `]p] is empty, the application redirects to the home page. Next, the script checks if the product quantity is less than or equal to zero. If so, the application uses the Cake Oaooekj object’s oapBh]od method to hold the message #Pdalnk`q_pukq namqaopa`eojkhkjcanejopk_g# in the Oaooekj object, and then redirects to the home page. Then the application calls the cap?]np method of the ?]npo model object in order to check the existence of this product in the shopping basket. The result of this method is stored in the oaooekj@]p] array variable. If the product does not exist in the shopping basket against the current session ID, the ]``?]np$% method of the ?]npo model object is called to insert a new product into the _]npo table. If the product does exist in the shopping basket, the ql`]pa?]np method is called to increment the quantity of the product in the _]npo table by one. Next, the script seizes the opportunity to do some housecleaning by calling the _ha]jQl method to remove records that are few days old from the _]npo table. Finally, the application redirects the user to the current product’s description page.
73
74
C HAPTER 3 N E-C OMMER C E
Now that we’ve added a product to the _]npo table, let’s jump back into the basket view code in Listing 3-30. First, we use the namqaop=_pekj method to get all the content of the cart from the _]npo table. This Cake method accepts the string that triggers the ?]npo?kjpnkhhan’s capIeje?]np method, which accepts the session ID (via o6 oe`) as an argument and assigns the result to the _]np?kjpajpo variable. Next, if the _]np?kjpajpo variable is an array and is not empty, the Pkp]h variable is assigned a value of zero. Finally, using the bkna]_d loop over _]np?kjpajpo, the current content of the basket now includes the newly added product Mozart for Lovers, costing $15, as shown in Figure 3-6.
Figure 3-6. The view showing Mozart for Lovers in the shopping basket in the right column Checking our user journey, our next action is to proceed to the shopping basket, to check that we are happy with our current product selections. If not, we will amend the selections or completely remove unwanted products from our shopping basket. This interface allows for cart-manipulation processes, such as updating the product quantity, deleting the product, and so on. Let’s suppose the user has clicked the Go to Shopping Cart link in Figure 3-6, and modified the cart content by removing the Mozart for Lovers and Dizzy 1990s items from her shopping basket. The new content of the shopping basket is shown in Figure 3-7.
CHAPTER 3 N E-COMMERCE
Figure 3-7. The view showing a sample cart content
The Order Model Next, we’re going to deal with the orders. We’ll start by creating the Kn`an model class, shown in Listing 3-32. Note that this class does not interact with any database table. Listing 3-32. The Order Model Class (app/models/order.php) 8;ldl _h]ooKn`anatpaj`o=llIk`ah w r]n j]ia9#Kn`an#7 r]n qoaP]^ha9b]hoa7 r]n r]he`]pa9]nn]u$ #j]ia#9:]nn]u$#nqha#9:]nn]u$#^apsaaj#(.(.11%( #namqena`#9:pnqa( #iaoo]ca#9:#Lha]oaajpan]j]ia*#%( #]``naoo#9:]nn]u$#nqha#9:]nn]u$#^apsaaj#(0(.11%( #namqena`#9:pnqa( #iaoo]ca#9:#Lha]oaajpan]j]``naoo*#%( #_kiiajp#9:]nn]u$#nqha#9:]nn]u$#^apsaaj#(1(.11%( #namqena`#9:pnqa( #iaoo]ca#9:#Lha]oaajpan]_kiiajp*#%%7 y ;:
75
76
C HAPTER 3 N E-C OMMER C E
In Listing 3-32, we define the r]he`]pa array to ensure input data integrity for the j]ia, ]``naoo, and _kiiajp form elements. Next, we’re going to click the Proceed to Checkout button. Our action will trigger the _da_gkqp method of the Kn`ano?kjpnkhhan object, which is defined in Listing 3-33. Listing 3-33. The OrdersController Class (app/controllers/orders_contoller.php) 8;ldl _h]ooKn`ano?kjpnkhhanatpaj`o=ll?kjpnkhhanw r]n j]ia9#Kn`ano#7 r]n qoao9]nn]u$#Kn`an#%7 bqj_pekj_da_gkqp$%w `]p]9pdeo):l]ooa`=ncoW#_po#Y7 pdeo):oap$#`]p]#( `]p]%7 pdeo):h]ukqp9#_da_g^]oa#7 y bqj_pekj_kjbeni$%w pdeo):h]ukqp9#_da_g^]oa#7 eb$ailpu$ pdeo):`]p]%%w kn`ano9pdeo):`]p]7 _]npo9pdeo):?]np):bej`$#]hh#(]nn]u$ #_kj`epekjo#9:]nn]u$#?]np*e`#9: pdeo):`]p]W#Kn`an#YW#_po#Y%( #na_qnoera#9:-%%7 pdeo):oap$_kil]_p$#_]npo#(#kn`ano#%%7 y y y ;: As usual, the Kn`ano?kjpnkhhan class references the Kn`ano model object using the qoao9 ]nn]u$Kn`an%7 statement. Next, we define the _da_gkqp method, which first extracts the current cart’s content IDs from pdeo):l]ooa`=ncoW#_po#Y array, and then stores the result in the `]p] array. The `]p] array is passed to the ]ll+reaso+kn`ano+_da_gkqp*_pl view file, shown in Listing 3-34, for display. The last statement of this method changes the default layout to _da_g^]oa. Listing 3-34. The Checkout View (app/views/orders/checkout.ctp) 8beah`oap: 8hacaj`: 8;ldla_dkpdeo):l]caPepha9[[$#8opnkjc:?da_gkqp8+opnkjc:#(pnqa%7;: 8+hacaj`: 8;ldl a_dkbkni):_na]pa$#Kn`an#(]nn]u$#qnh#9:#+kn`ano+_kjbeni+#%%7 a_dkbkni):annkn$#Kn`an*j]ia#%7
CHAPTER 3 N E-COMMERCE
a_dkbkni):ejlqp$#Kn`an*j]ia#(]nn]u$#e`#9:#kn`anj]ia#( #h]^ah#9:#J]ia6#(#oeva#9:#/,#( #i]thajcpd#9:#.11#(#annkn#9:b]hoa%%7 a_dkbkni):annkn$#Kn`an*]``naoo#%7 a_dkbkni):ejlqp$#Kn`an*]``naoo#(]nn]u$#e`#9:#kn`an]``naoo#( #pula#9:#patp]na]#(#h]^ah#9:#=``naoo6#( #nkso#9:#/#(#annkn#9:b]hoa%%7 a_dkbkni):annkn$#Kn`an*_kiiajp#%7 a_dkbkni):ejlqp$#Kn`an*_kiiajp#(]nn]u$#e`#9:#kn`an_kiiajp#( #pula#9:#patp]na]#(#h]^ah#9:#?kiiajp6#( #nkso#9:#1#(#annkn#9:b]hoa%%7 a_dkbkni):annkn$#Kn`an*l]uiajp#%7 a_dkbkni):ejlqp$#Kn`an*l]uiajp#(]nn]u$#pula#9:#n]`ek#( #klpekjo#9:]nn]u$-9:#Ckkcha#(.9:#L]ul]h#%%%7 a_dkbkni):ejlqp$#_po#(]nn]u$#pula#9:#de``aj#(#r]hqa#9: `]p]%%7 a_dkbkni):aj`$]nn]u$#h]^ah#9:#?kjbeniKn`an#%%7 ;: 8+beah`oap: 8^n+: 8;ldla_dkpdeo):ahaiajp$#_da_gkqp#%7;: In Listing 3-34, the code is divided into two main parts. The first part uses Cake’s bkni object to render the checkout form. The second part renders the current content of the shopping basket, similar to the basket view code (Listing 3-30). Listing 3-34 produces the checkout form shown in Figure 3-8.
Figure 3-8. The checkout page
77
78
C HAPTER 3 N E-C OMMER C E
When the user clicks the Confirm Order button on the checkout page, the _kjbeni method of the Kn`ano?kjpnkhhan is triggered. If the submitted form data (stored in pdeo):`]ta) is not empty, then the form data is stored in the kn`ano array. Next, we retrieve the current cart content based on the cart ID (extracted from pdeo):`]p]W#Kn`an#YW#_po’]), and the result is stored in the _]npo array variable.
NNote It is important to include the statement r]n ^ahkjcoPk9]nn]u$#Lnk`q_p#%7 in the ?]np model class, because we’ll need to submit some vital product information (such as product name, price, and description) to our chosen payment gateway. This statement will ensure that the application includes related product information whenever _]npo table records are retrieved.
Finally, using Cake’s oap and _kil]_p functions, the two array variables are passed on to the ]ll+reaso+kn`ano+_kjbeni*_pl file. The structure of the array variables is shown in Listing 3-35. Listing 3-35. The Structure of $order and $cart Array Variables =nn]u $ WKn`anY9:=nn]u $ Wj]iaY9:Ie_da]hOq__ao W]``naooY9:-Oq__aoo=rajqa Oq__aoo Oq__aooOp]pa QO= W_kiiajpY9:Pdeokn`anodkqh`^a`aherana`sepd]^kpphakbNa`Seja* I]jupd]jgobknukqn]jpe_el]pa`nalkjoa* Wl]uiajpY9:W_poY9:1/ % % =nn]u $ W,Y9:=nn]u $ W?]npY9:=nn]u $ We`Y9:1/ Wlnk`q_p[e`Y9:.. WmpuY9:. W_p[oaooekj[e`Y9:/._]^^5`2b^/-0,0]1^3/24/,a]_2]]/ W_na]pa`Y9:.,,4),5).1./6/,6-4 %
CHAPTER 3 N E-COMMERCE
WLnk`q_pY9:=nn]u $ We`Y9:.. W_]packnu[e`Y9:-/ Wj]iaY9:@evvu]j`Op]j W`ao_nelpekjY9:Herasepd@evvuCehhaolea]j`Op]jCapv* Wlne_aY9:-/*,, WmpuY9:-, Wei]caY9:-*flc Wpdqi^j]ehY9:-*flc W_na]pa`Y9:,,,,),,),,,,6,,6,, Wik`ebea`Y9:,,,,),,),,,,6,,6,, % % % The array structures in Listing 3-35 are passed on to the _kjbeni*_pl view file, as shown in Listing 3-36. Listing 3-36. The OrdersController’s confirm() Method View 8;ldl eb$ kn`anW#Kn`an#YW#l]uiajp#Y99-%w a_dkpdeo):ahaiajp$#ckkcha[_da_gkqp#%7 yahoaw a_dkpdeo):ahaiajp$#l]ul]h[_da_gkqp#%7 y ;: If the order payment method selected equals -, then the Google Checkout payment form is displayed. If not, the PayPal form is presented.
The Google Checkout Button Google needs no introduction when it comes to the world of Internet. However, you should be aware that to use Google services, you need to have a Google account, which you probably already do if you use Google Mail, Google Docs, Google Adwords, or any of the numerous other Google offerings. Shoppers are often frustrated by needing to fill out lengthy online forms before making payments. It is easy to encourage users to check out via the Google Checkout button, which is secure and convenient. When this button is selected, the _]npo and kn`an array variables are passed to the ]ll+reaso+ahaiajpo+ckkcha[_da_gkqp*_pl file, shown in Listing 3-37.
79
80
C HAPTER 3 N E-C OMMER C E
Listing 3-37. Google Checkout View (app/views/elements/google_checkout.ctp) 8;ldl a_dkbkni):_na]pa$#Kn`an#( ]nn]u$#qnh#9:#dpplo6++_da_gkqp*ckkcha*_ki+]le+_da_gkqp+r.+£ _da_gkqpBkni+Ian_d]jp+ttttttttttttttt#( #]__alp)_d]noap#9:#qpb)4#%%7 e9,7 bkna]_d$ _]npo]o _]np%w t9 e'-7 a_dkbkni):de``aj$epai[j]ia[ t( ]nn]u$#r]hqa#9: _]npW#Lnk`q_p#YW#j]ia#Y%%7 a_dkbkni):de``aj$epai[`ao_nelpekj[ t( ]nn]u$#r]hqa#9: _]npW#Lnk`q_p#YW#`ao_nelpekj#Y%%7 a_dkbkni):de``aj$epai[mq]jpepu[ t( ]nn]u$#r]hqa#9: _]npW#?]np#YW#mpu#Y%%7 a_dkbkni):de``aj$epai[lne_a[ t( ]nn]u$#r]hqa#9: _]npW#Lnk`q_p#YW#lne_a#Y%%7 a_dkbkni):de``aj$epai[_qnnaj_u[ t( ]nn]u$#r]hqa#9:?kjbecqna66na]`$#Epai*_qnnaj_u#%%%7 a_dkbkni):de``aj$odel[iapdk`[j]ia[ t( ]nn]u$#r]hqa#9:?kjbecqna66na]`$#Kn`an*odeliapdk`#%%%7 a_dkbkni):de``aj$odel[iapdk`[lne_a[ t( ]nn]u$#r]hqa#9:?kjbecqna66na]`$#Kn`an*odellne_a#%%%7 a_dkbkni):de``aj$p]t[n]pa( ]nn]u$#r]hqa#9:?kjbecqna66na]`$#Kn`an*p]tn]pa#%%%7 a_dkbkni):de``aj$p]t[qo[op]pa( ]nn]u$#r]hqa#9:?kjbecqna66na]`$#Kn`an*p]top]pa#%%%7 y a_dkbkni):de``aj$#[_d]noap[#%7 a_dkdpih):ei]ca$#dppl6++_da_gkqp*ckkcha*_ki+^qppkjo+_da_gkqp*ceb;£ ian_d]jp[e`9ttttttttttttttt"s9-4,"d902"opuha9sdepa"r]ne]jp9patp"hk_9aj[QO#( ]nn]u$#j]ia#9:#Ckkcha?da_gkqp#( #]hp#9:#B]op_da_gkqppdnkqcdCkkcha#( #daecdp#9:#02#( #se`pd#9:#-4,#%%7 a_dkbkni):aj`$]nn]u$#h]^ah#9:#?kjbeniKn`an#%%7 ;: The view file shown in Listing 3-37 generates a page similar to the one shown in Figure 3-9.
CHAPTER 3 N E-COMMERCE
Figure 3-9. The Google checkout page Clicking the Google Checkout button will redirect the user to the Google sandbox environment, as shown in Figure 3-10.
Figure 3-10. The Google test sandbox
81
82
C HAPTER 3 N E-C OMMER C E
For more information about processing online sales using Google’s payment system, Google Checkout buttons, and the Google sandbox, visit the following sites: Ê
UÊ dpplo6++_da_gkqp*ckkcha*_ki+oahh+
Ê
UÊ dppl6++_k`a*ckkcha*_ki+]leo+_da_gkqp+`arahklan+Ckkcha[?da_gkqp[>]oe_[DPIH[ Ckkcha[?da_gkqp[>qppkjo*dpih
Ê
UÊ dppl6++_k`a*ckkcha*_ki+]leo+_da_gkqp+`arahklan+Ckkcha[?da_gkqp[TIH[=LE[?na`ep[ ?]n`[Paop[?]oao*dpih.
The PayPal Submit Button PayPal can be regarded as the father of online payment gateways. It’s ubiquitous when it comes to online payment choices. Here, we are going to create the payment form, in the ]ll+reaso+ahaiajpo+l]ul]h[_da_gkqp*_pl file, as shown in Listing 3-38. You can use the Buy-it-Now button form generator on PayPal to create a similar payment form. Listing 3-38. The PayPal View (app/views/elements/paypal_checkout.ctp) 8;ldl a_dkbkni):_na]pa$#Kn`an#( ]nn]u$#qnh#9:#dpplo6++sss*l]ul]h*_ki+_ce)^ej+sa^o_n#( #e`#9:#l]uL]hBkni#%%7 a_dkbkni):de``aj$epai[jqi^an(]nn]u$#r]hqa#9:PdaIqoe_?hq^%%7 a_dkbkni):de``aj$_i`(]nn]u$#r]hqa#9:[t_he_g%%7 a_dkbkni):de``aj$jk[jkpa(]nn]u$#r]hqa#9:-%%7 a_dkbkni):de``aj$^qoejaoo(]nn]u$#r]hqa#9:o]hao
CHAPTER 3 N E-COMMERCE
Summary In this chapter, we went through the process of building an online shop. For this demonstration, we’ve kept its features to a minimum. However, users can navigate the product category menu, add products to a shopping basket, review the basket contents, and proceed to the checkout form. We started this chapter by looking at a typical shop layout, the shop user journey, and the creation of the database and tables needed for the shop application. We then proceeded to build all the model classes that interact with the database tables, such as those for categories, products, carts, and so on. To handle user requests, we built the =ll?kjpnkhhan class with properties and methods that provide functionality common to other controller classes. This helps us to avoid “reinventing the wheel” and to extend other controller classes, such as the category controller of the application. We then built the other controller classes to handle the application requests. Finally, we created checkout forms to handle payment transactions. We used a Google sandbox as our payment test environment and also created a PayPal form as an alternative payment option. Building a comprehensive e-commerce web site obviously involves more than what we covered in this chapter. For example, you would want to add an administration area to the web site to facilitate management.
83
CHAPTE R
4
A Message Forum Web Service I
n this chapter, we’ll build a Cake-based forum. While there are a lot of popular open source forums such as phpBB, we like the idea of rolling our own. It’s a fantastic learning process, and we get to deal with a lot of new subjects that don’t come up in our day jobs. So that our forum will stand out from the crowd, we need a unique selling point. That will be a web service API for our forum. Web services are quite a common feature in many modern web applications, but not that common in many of the forums.
Our Take on Web Services The term web services can have many meanings. For example, simply entering the URL dppl6++sss*_]galdl*knc+ into your browser can be called a web service request, since you’re using HTTP CAP. The World Wide Web Consortium (W3C) has an official definition for web services (dppl6++sss*s/*knc+.,,.+so+=_perepu): Web services provide a standard means of interoperating between different software applications, running on a variety of platforms and/or frameworks. This meaning is quite general, indicating that web services are a way for computers to talk to each other, which may or may not include the Web. Before we get to creating our forum, let’s clear up what we mean by web services.
Web Service Elements There are many elements relating to web services. We start with a short explanation of each element to provide a foundation for our particular angle on web services.
85
86
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
Ê
UÊ API: This acronym stands for application programming interface. In the world of computers, much like the definition of web services, this is quite a general term. For us, it means a set of published functions or methods that can be directly accessed via URLs on the server, such as dppl6++sss*at]ilha*_ki+capSe`capo*fokj or dppl6++sss* at]ilha*_ki+capSe`capo+fokj.
Ê
UÊ SOAP: This stands for Simple Object Access Protocol, and is one of the main protocols of a web service. It piggybacks onto the HTTP protocol, considered too complex and verbose by many developers. Taking the previous example, our SOAP request would look something like this:
LKOP+DPPL+-*Dkop6sss*at]ilha*_ki ?kjpajp)Pula6patp+tih7_d]noap9qpb)4 ?kjpajp)Hajcpd6jjjj OK=L=_pekj6Okia)QNE 8OK=L)AJR6Ajrahkla tihjo6OK=L)AJR9dppl6++o_dai]o*tihok]l*knc+ok]l+ajrahkla+ OK=L)AJR6aj_k`ejcOpuha9dppl6++o_dai]o*tihok]l*knc+ok]l+aj_k`ejc+: 8OK=L)AJR6>k`u: 8i6capSe`capotihjo6i9Okia)QNE: 8pula:omq]na8+pula: 8+i6capSe`capo: 8+OK=L)AJR6>k`u: 8+OK=L)AJR6Ajrahkla: Ê
UÊ HTTP: In a sense, HTTP (Hypertext Transfer Protocol) and SMTP (Simple Mail Transfer Protocol) are the two main protocols people use on the Internet. They surf the Web using the HTTP protocol and read e-mail messages using the SMTP protocol. Many developers have adopted HTTP as their protocol for developing their web services; specifically, just the CAP and LKOP methods within that protocol. This is how we will be developing our API in this chapter’s application.
Ê
UÊ XML-RPC: The Extensible Markup Language Remote Procedure Call was created by David Winer, one of the pioneers in modern web services and blogging. It’s similar to SOAP, but simpler. This protocol is not frequently used by developers. The major web applications that support XML-RPC include Flickr and Amazon S3.
Ê
UÊ REST: This stands for representational state transfer. The term was coined by Roy Fielding, one of the main authors of HTTP. It is not a protocol, but a set of statements about how distributed media should be organized, with the Web being a key example.
Figure 4-1 illustrates how many web developers see web services. On the left side, we have the clients. They can be applications on other servers; desktop applications, which include browsers; and other devices, like mobile cell phones. These clients will most often use HTTP (CAP, LKOP, and so on) to send and request data to and from the server, shown on the right. The way the clients talk to the server is the protocol: XML-RPC, SOAP, RSS, and so on. These protocols are quite specific, since there are standards attached. For example, in RSS, you must end all requests with the *noo extension. The format you will receive will be in a specific format, as defined by the official standards body.
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
Figure 4-1. Elements of web services However, you can develop your own proprietary protocol using the features of HTTP. Many web service providers have gone this route—Google Maps, Flickr, and Twitter, to name a few. Mashing different services together is never an easy task. For example, an image will have a different context depending on the application. The use of REST complicates the picture. In Figure 4-1, it sits within generic HTTP, as we regard it as a specific way of using that protocol.
REST and HTTP Many people have advocated the use of the REST principle with HTTP for web services. In particular, the use of the HTTP methods should conform to the W3C standard. For example, CAP methods should not alter any data. Something like dppl6++sss*at]ilha*_ki+ a`epQoan+;e`9-"j]ia9jas[j]ia should not be allowed. In REST, this would look like dppl6++ sss*at]ilha*_ki+Qoan+-. The new name would be supplied as a key/value pair— j]ia9jas[j]ia—in the request body, and instead of using CAP, we would be using LQP. In a sense, we see HTTP as being a diluted form of REST. It can get quite confusing, as some meanings can overlap. We see the confusion surrounding REST much as we see the complexity of SOAP. Many developers find REST difficult to use. To use REST in its full meaning, you must conform to the correct use of the HTTP methods such as CAP, LKOP, LQP, and @AHAPA. Most browsers support only CAP and LKOP. You can use the other methods via TIHDpplNamqaop in Ajax scripting, but that just won’t be enough to conform to the principles of REST. To end our discussion of REST, the web services that we’re going to write in this chapter will not conform to the principles of REST.
87
88
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
NNote The four most important HTTP requests are CAP, LKOP, LQP, and @AHAPA. CAP requests a resource, such as an HTML file. (Requesting the resource must not cause a side effect, such as deleting a record). LKOP sends data to the resource to be processed. It creates a new resource, updates the resource, or both. We’ll consider the LQP operation to be the same as the LKOP. Elliotte Rusty Harold gives a good explanation at dppl6++sss*ahd]nk*_ki+^hkc+okbps]na)`arahkliajp+sa^)`arahkliajp+.,,1+-.+,4+ lkop)ro)lqp+. Finally, @AHAPA just deletes a resource, such as a user account.
We’ve always liked the principle of KISS, which means we prefer to Keep It Short and Simple. Logical standards like SOAP sometimes are just too logical. As such, like many web APIs, we’re just opting for plain old HTTP CAP and LKOP as the two modes that developers can use to access the innards of our forum. Referring to our diagram in Figure 4-1, we’re using the generic HTTP method.
Result Return Formats When we make a web service request, the result can be returned in many formats depending on the client request, including HTML, JSON, XML, and RSS. You can even specify the result to be returned as a comma-separated list or as a JavaScript-ready document output. For our application, we’ll use JSON. Developers can easily use our web services via Ajax or on the server using _qnh or scap.
Application Requirements One of the main focal points of our forum application is the API. Therefore, we’ll start by thinking about the methods that we will be exposing to the public. The API requests will come from other applications, not individuals using the application via an interface, so we must consider that in our planning. In our forum application, we want to include some common features that are found in all forums: Ê
UÊ *ÃÌÊiÃÃ>}iÃ
Ê
UÊ ,i«ÞÊÌÊiÃÃ>}iÃ
Ê
UÊ 6iÜÊiÃÃ>}iÃ
Ê
UÊ -i>ÀV
ÊiÃÃ>}iÃ
Since our own interface is basically also a client, we need to look at our own front end as if it were a third-party client application that is calling the processing scripts from a distant server. We know there are several ways to make a URL request. The following are the ones we are interested in: Ê
UÊ ,iµÕiÃÌ}Ê>Ê1,ÊÛ>Ê_qnh or scap; they may also want it returned in a particular format
Ê
UÊ ,iµÕiÃÌ}Ê>Ê1,ÊÛ>Ê>ÊLÀÜÃiÀÊÕÃ}Ê//*ÊCAP or LKOP
Ê
UÊ ,iµÕiÃÌ}Ê>Ê1,ÊÛ>Ê>ÝÊÃVÀ«Ì}ÊÕÃ}Ê//*ÊCAP or LKOP; that is, the TIHDpplNamqaop (TDN) within the browser makes the HTTP call
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
Some of our actions may support only CAP and/or LKOP. This is similar to many of the other web APIs. We must have a standard way to respond to requests. For example, in HTTP, the response code 200 is OK, and the response code 404 is resource or page not found. Furthermore, some requests return data, while others carry out particular actions like saving data. We must be able to return data in a consistent manner given different requests.
Threads and Posts For our forum, we need to decide how discussions should be organized. We simply define a discussion (commonly called a thread) as the messages and the subsequent messages (replies) to those messages. Let’s look at some of the ways in which messages could be organized.
Organized by Date Using this method, each message is organized by date order, regardless of to which message it is replying. Users can then see the most up-to-date messages as they come in. Organizing by date is problematic because we won’t know which message a message is replying to. We can overcome this by including the message, but do we include the whole message or just part of it? Perhaps we could allow the users to select the parts they are replying to? Listing 4-1 shows a code snippet that allows a user to quote part of a message so other users can refer to it. Listing 4-1. JavaScript Code to Get Selected Text -68ejlqppula9^qppkjkjikqoa`ksj9`eolh]uOaha_pa`$%7: .6 /68o_nelp: 06 16bqj_pekj`eolh]uOaha_pa`$%w 26 36r]noaha_pa`Patp9##7 46 56eb$sej`ks*capOaha_pekj%w -,6oaha_pa`Patp9sej`ks*capOaha_pekj$%7 --6y -.6ahoaeb$`k_qiajp*oaha_pekj%w -/6oaha_pa`Patp9`k_qiajp*oaha_pekj*_na]paN]jca$%*patp7 -06y -16 -26napqnjoaha_pa`Patp7 -36 -46y -56 .,68+o_nelp:
89
90
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
You might want to use the code in Listing 4-1 if you decide to extend the forum application. It simply retrieves the text a user has selected with the mouse. On line 9, we check whether the user has selected any text on Firefox/Gecko-based browsers. If so, we place the text in to the oaha_pa`Patp variable. Line 12 checks for text selection in Internet Explorer/ Trident-based browsers.
Organized by First Post Using the by-first-post organization, we show only new messages, essentially the first messages, as they come in. We won’t show the replies. If users want to read the replies, they need to drill down; that is, make another request to a different Cake action. If a user replies to an old message, it won’t be displayed. It will remain relative to that old message. Users will know of that reply only when they drill down to it. An obvious problem is that active discussions will be followed only by those who participated in it at the early stages, when the discussions were at the top of the page. When new messages arrive, the old messages get pushed down. This is not desirable, since we want many people contributing to a discussion as long as possible. After all, if people are just asking questions on the forum, very few people would bother to read the forum.
Organized by Replies You can also organize the threads by the number of replies, so the thread that has the greatest number of replies is shown first. If no one has contributed to the thread for a while, it will still stay at the top. The problem with this organization is that current and active threads are not given priority. So we end up putting too much emphasis on a topic that may no longer be of interest to most people.
Organized by Last Post Another organization possibility is a slightly different take on the by-first-post organization. Using this method, we order a discussion by its last post, but we show the first post. This approach has a number of advantages. If a new message comes in, it floats to the top. However, if a reply to an old message is posted, the first post of that discussion floats to the top. In this way, we encourage people to participate in ongoing discussions. If the discussion has been exhausted, it simply floats back down the page. Organizing the posts by the last post seems to be the most sensible method. In fact, most forums organize discussions this way, and so will our application.
Web Service Requests Our application will support the following five API requests: Ê
UÊ iÌV
ÊiÊiÃÃ>}i
Ê
UÊ iÌV
Ê>ÞÊiÃÃ>}iÃ
Ê
UÊ iÌV
ÊÌ
Ài>`Ã
Ê
UÊ *ÀViÃÃÊ>ÊiÃÃ>}i
Ê
UÊ -i>ÀV
ÊvÀÊiÃÃ>}iÃ
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
These requests will be the ones that we will be publishing in our API documentation. They will all support Ajax from a browser, ordinary HTTP requests from a browser, and _qnh or scap calls from a server.
Layout The API is an important component to the application. However, we’re not just publishing an API. The application will also be a working forum where users can post messages. In that sense, we’re building a client as well as the API services within the same application. Our application front end will have all the essential features that any forum user will expect. A user can post messages, reply to messages, view topics, and search for messages. The layout of the interface will be similar to that of the other projects in this book. We start with a header, followed by a navigation bar, and then the main content area. When a user goes straight to the site, they will be presented with a view of the current threads, as shown in Figure 4-2.
Figure 4-2. A rough sketch of the forum application layout
Application Structure In the previous section, we talked about how we’re going to organize our messages according to threads. This relationship is a one-to-many association, where one thread ties together many messages. The two main database tables in our application are iaoo]cao and pdna]`o. The fields in these tables are shown in Tables 4-1 and 4-2.
91
92
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
Table 4-1. The Fields in the messages Table
Field Name
Data Type
Description
e`
_d]n$/2%
Primary key (note it’s a UUID and not an auto-increment field; see the action class IbIaoo]caLnk_aoo for an explanation)
j]ia
r]n_d]n$.11%
Name of the user posting the message
ai]eh
r]n_d]n$.11%
E-mail address of the user
iaoo]ca
ia`eqipatp
The message body itself
nalhu[pk
_d]n$/2%
If the message is a reply to another message, this is the reply message ID
Oq^fa_p
r]n_d]n$.11%
The subject of the message (replies will have a NA6 prefix)
p[_na]pa`[]p
`]papeia
Automagic field (but we fill this in ourselves; see the action class IbIaoo]caLnk_aoo for an explanation)
pdna]`[e`
_d]n$/2%
The thread this message belongs to
Table 4-2. The Fields in the threads Table
Field Name
Data Type
Description
e`
_d]n$/2%
Primary key (again, UUID and not an auto-increment field)
benop[iaoo]ca[e`
_d]n$/2%
Used so we can easily pick out the first message when it needs to be displayed in the threads listing
h]op[iaoo]ca[`]pa
`]papeia
Used so we can order the threads correctly according to the last message date
iaoo]ca[jqi
ejp$--%
The number of messages within this thread
NNote As we haven’t used database transactions in our application, the number in the iaoo]ca[jqi field of the pdna]`o table may not always be completely accurate, but that’s not critical at this stage, as it’s used only for display purposes.
You’ll notice that the pdna]`o table includes a few metadata fields to contain data that describes the thread. This helps us to cut down on the number of SQL calls needed to list our threads. Each time a new message is added to a thread, the code updates the h]op[iaoo]ca[ `]pa field with the date of that message. This way, we don’t need to look in the iaoo]cao table for the last message that was posted within a thread.
JSON Web Services We have specified that we’ll be using the JSON format for all our API returns. To get this part of our application working, we need to do some setup.
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
The Nkqpan class must be aware of the JSON extension. We take care of this by adding the following line in the +]ll+_kjbec+nkqpao*ldl file: Nkqpan66l]noaAtpajoekjo$#fokj#%7 Following this, we need to include the NamqaopD]j`han component by adding it into our _kilkjajpo variable in our +]ll+]ll[_kjpnkhhan*ldl file, so that it’s available to all controllers. We also need the Oaooekj component. Our _kilkjajpo variable will look like this: r]n _kilkjajpo9]nn]u$#Oaooekj#(#NamqaopD]j`han#%7 Now the NamqaopD]j`han will automatically map JSON requests to the correct layout and view. When a JSON request comes in, it will pick the layout file within the folder +]ll+reaso+ h]ukqp+fokj instead of +]ll+reaso+h]ukqp. Our JSON layout file is shown in Listing 4-2. Listing 4-2. JSON Layout File (/app/views/layout/base.ctp) 8;ldl ++Pdeoeoqoa`sdajpda_]hhatpajoekjeo*fokj da]`an$Ln]ci]6jk)_]_da%7 da]`an$#?kjpajp)Pula6patp+t)fokj#%7 da]`an$T) FOKJ6* _kjpajp[bkn[h]ukqp%7
_kjpnkhhan9pdeo):j]ia7 ]_pekj9pdeo):]_pekj7 `]papeia9`]pa$U[I[f[[C[e[o[P%7 beha[j]ia9 _kjpnkhhan*#[#* ]_pekj*#[#* `]papeia7
da]`an$#?kjpajp)@eolkoepekj6]pp]_diajp7behaj]ia9#* beha[j]ia*#*fokj#%7 a_dk _kjpajp[bkn[h]ukqp7 ;: Any script that requests a JSON return can use the HTTP headers ?kjpajp) Pula and T)FOKJ to identify whether or not it is a JSON return. However, the _kjpajp[bkn[h]ukqp isn’t guaranteed to be JSON. We must manually put that into a JSON format. Once the layout has been picked out, the NamqaopD]j`han will look in a fokj folder within the reaso folder that corresponds to the controller, similar to how the h]ukqp folder structure works. In our application, all the API methods have the same fokj view. For example, in the reas file ]ll+reaso+ib[bap_d[pdna]`o+fokj+ej`at*_pl for the IbBap_dPdna]`o controller, we just make use of JavaScript helper to format our result in a JSON format, as shown here: 8;ldl a_dkf]r]o_nelp):k^fa_p$ naoqhp%7 ;:
93
94
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
Our Application Controller Throughout the development of the forum application, we’ve placed a lot of attention on our web services API. We’re going to continue with that line of thought by using a version of the Command design pattern, adapted for the Web, in our controller. The Command pattern is an object-oriented class design, where we encapsulate each action in a class of its own. Class names are usually nouns, because they represent objects. However, in the Command pattern, the names are verbs, and each class must implement an execution method, traditionally called ata_qpa$%; in our case, it will be called ej`at$%. (See dppl6++aj*segela`e]*knc+sege+?kii]j`[l]ppanj for more information about the traditional implementation of the Command pattern.) Why implement the actions as a class? The advantages are not so obvious in a web environment or with small applications. The following are the advantages of this approach: Ê
UÊ >V
Ê>VÌÊV>ÃÃÊÃÊÌÀi>Ìi`Ê>ÃÊ>Ê*ÊÀiµÕiÃÌÊ>V̰ÊÌÊ
>ÃÊ>Ê
}
iÀÊÃÌ>ÌÕÃÊÌ
>ÊÌ
iÊÛiÀLÊ object.
Ê
UÊ /
iÊV>ÃÃiÃÊ>ÀiÊÃ>iÀÊ>`ÊÀiÊ>>}i>Li°
Ê
UÊ -Õ««ÀÌ}ÊvÕVÌÃÊvÀÊi>V
Ê>VÌÊ>ÀiÊiV>«ÃÕ>Ìi`ÊÜÌ
ÊÌ
iÊ>V̽ÃÊÜÊV>ÃÃ°Ê For example, each class has its own validation method.
To use the Command pattern in our controller, we start with a base controller, which acts as the parent class of all the action classes. Code that is common to all the action classes can be placed here. This base class is shown Listing 4-3. Listing 4-3. Base Controller (mf_controller.php) -68;ldl .6 /6_h]ooIb?kjpnkhhanatpaj`o=ll?kjpnkhhanw 06 16r]n j]ia9#Ib#7 26 36r]n qoao9]nn]u$#Pdna]`#(#Iaoo]ca#(£ #IbOa]n_dLnk_aoo#(#IbBap_dIaoo]ca#( 46#IbBap_dIaoo]cao#(#IbBap_dPdna]`o#%7 56 -,6bqj_pekj[_da_g=f]t$%w --6 -.6eb$pdeo):NamqaopD]j`han):eo=f]t$%%w -/6 -06++Pdeoiqopateop6]ll+reaso+h]ukqpo+fokj+]f]t*_pl -16pdeo):NamqaopD]j`han):naj`an=o$ pdeo(#fokj#%7 -26y -36y -46 -56bqj_pekjej`at$%w .,6 .-6y ..6y ./6;:
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
The [_da_g=f]t method on line 10 is used by the action classes to check whether the API call is an Ajax request. If it is an Ajax request, we render the Ajax layout file in ]ll+reaso+ h]ukqpo+fokj+]f]t*_pl. Following this, each action class extends the Ib?kjpnkhhan class. An example is shown in Listing 4-4. There are quite often other supporting functions that help the main ej`at function. Listing 4-4. Example of an Action Class That Extends the Main MfController Class -68;ldl .6 /6ej_hq`a[kj_a$#ib[_kjpnkhhan*ldl#%7 06 16_h]ooIbAt]ilha?kjpnkhhanatpaj`oIb?kjpnkhhanw 26 36r]n j]ia9#IbAt]ilha#7 46 56r]n naoqhp9]nn]u$#naoqhp#9:##( -,6#iaoo]ca#9:##( --6#annkno#9:##( -.6#`]p]#9:## -/6%7 -06 -16bqj_pekj^abknaBehpan$%w -26 -36pdeo):[_da_g=f]t$%7 -46y -56 .,6bqj_pekj[r]he`]pekj$%w .-6 ..6 naoqhp9pnqa7 ./6 .06++@kr]he`]pekj .16 .26napqnj naoqhp7 .36y .46 .56bqj_pekjej`at$%w /,6 /-6eb$pdeo):[r]he`]pekj$%%w /.6 //6++@k^qoejaoohkce_ /06y /16y /26 /36bqj_pekj^abknaNaj`an$%w /46 /56pdeo):oap$#naoqhp#(pdeo):naoqhp%7 0,6y 0-6y 0.6 0/6;:
95
96
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
When the controller is called, it first checks whether it’s an Ajax call in the ^abknaBehpan on line 15. Next, we go in to the main ej`at action on line 29. If the API call validates, we do the business logic. When we’re ready to display the results, we package it in the ^abknaNaj`an action on line 37. Taking this hypothetical example, when we request a Cake URL, it will look like this: dppl6++sss*at]ilha+IbAt]ilha+ej`at+l]n]i6-+ Now that we’ve talked about how the controllers are set up, we can show you how we’ve actually implemented it. Table 4-3 shows all the controller actions we have written. Table 4-3. Our Application Controllers
Controller File
Description
ib[_kjpnkhhan*ldl
Parent controller to all action classes
ib[bap_d[lkop[_kjpnkhhan*ldl
Fetch a message
ib[bap_d[lkopo[_kjpnkhhan*ldl
Fetch several messages
ib[bap_d[pdna]`o[_kjpnkhhan*ldl
Fetch the threads
ib[lkop[bkni[_kjpnkhhan*ldl
Post messages
ib[lkop[lnk_aoo[_kjpnkhhan*ldl
Process a message
ib[oa]n_d[lnk_aoo[_kjpnkhhan*ldl
Process a search request
Now, if we were to do this in the traditional Cake way, there would be only two files: iaoo]ca[ _kjpnkhhan*ldl, to contain all actions relating to messages, and pdna]`[_kjpnkhhan*ldl, to contain all actions relating to threads. The Iaoo]ca?kjpnkhhan would contain actions that fetch one or more posts and handle message posting, and it would contain various supporting functions. In this case, by separating out the actions as classes, we gain better management. All the action classes have their own corresponding model, as shown in Table 4-4. Table 4-4. Action Controllers and the Corresponding Models
Controller
Model
File
Ib?kjpnkhhan
No associated model
IbBap_dIaoo]ca?kjpnkhhan
IbBap_dIaoo]ca
]ll+ik`aho+ib[bap_d[iaoo]ca*ldl
IbBap_dIaoo]cao?kjpnkhhan
IbBap_dIaoo]cao
]ll+ik`aho+ib[bap_d[iaoo]cao*ldl
IbBap_dPdna]`o?kjpnkhhan
IbBap_dPdna]`o
]ll+ik`aho+ib[bap_d[pdna]`o*ldl
IbIaoo]caBkni?kjpnkhhan
IbIaoo]caBkni
]ll+ik`aho+ib[iaoo]ca[bkni*ldl
IbIaoo]caLnk_aoo?kjpnkhhan
IbIaoo]caLnk_aoo
]ll+ik`aho+ib[iaoo]ca[lnk_aoo*ldl
IbOa]n_dLnk_aoo?kjpnkhhan
IbOa]n_dLnk_aoo
]ll+ik`aho+ib[oa]n_d[lnk_aoo*ldl
Next, we’ll dive right into the heart of each controller and its view, starting with the IbBap_dIaoo]ca?kjpnkhhan controller.
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
Fetch a Message The IbBap_dIaoo]ca?kjpnkhhan is used to find a particular message using its e`. The bej`>uE` method executes a simple SQL OAHA?P statement to the database and fetches the single message record. The code is shown in Listing 4-5. Listing 4-5. Fetch One Message Controller (mf_fetch_message_controller.php) -68;ldl .6 /6ej_hq`a[kj_a$#ib[_kjpnkhhan*ldl#%7 06 16_h]ooIbBap_dIaoo]ca?kjpnkhhanatpaj`oIb?kjpnkhhanw 26 36r]n j]ia9#IbBap_dIaoo]ca#7 46 56r]n naoqhp9]nn]u$#naoqhp#9:##( -,6#iaoo]ca#9:##( --6#annkno#9:##( -.6#`]p]#9:## -/6%7 -06 -16bqj_pekj^abknaBehpan$%w -26 -36pdeo):[_da_g=f]t$%7 -46y -56 .,6bqj_pekj[r]he`]pekj$%w .-6 ..6 naoqhp9pnqa7 ./6 .06pdeo):`]p]9]nn]u$#IbBap_dIaoo]ca#%7 .16pdeo):`]p]W#IbBap_dIaoo]ca#YW#iaoo]caE`#Y9##7 .26 .36eb$eooap$pdeo):l]ooa`=ncoW#iaoo]caE`#Y%%w .46pdeo):`]p]W#IbBap_dIaoo]ca#YW#iaoo]caE`#Y9£ pdeo):l]ooa`=ncoW#iaoo]caE`#Y7 .56y /,6 /-6pdeo):IbBap_dIaoo]ca):oap$pdeo):`]p]%7 /.6 //6eb$ pdeo):IbBap_dIaoo]ca):r]he`]pao$%%w /06 /16 naoqhp9b]hoa7 /26 /36pdeo):naoqhpW#naoqhp#Y9#,#7 /46pdeo):naoqhpW#iaoo]ca#Y9£ Pdana]naokialnk^haiosepdukqnnamqaop*7
97
98
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
/56pdeo):naoqhpW#annkno#Y9£ pdeo):IbBap_dIaoo]ca):r]he`]pekjAnnkno7 0,6y 0-6 0.6napqnj naoqhp7 0/6y 006 016bqj_pekjej`at$%w 026 036eb$pdeo):[r]he`]pekj$%%w 046 056 iaoo]ca9pdeo):Iaoo]ca):bej`>uE`$£ pdeo):`]p]W#IbBap_dIaoo]ca#YW#iaoo]caE`#Y%7 1,6 1-6eb$ iaoo]ca99##%w 1.6pdeo):naoqhpW#naoqhp#Y9#,#7 1/6pdeo):naoqhpW#iaoo]ca#Y9£ #Pdanas]o]lnk^haibap_dejcpdaiaoo]ca*Lha]oapnu]c]ejh]pan*#7 106y 116ahoaw 126 136pdeo):naoqhpW#naoqhp#Y9#-#7 146pdeo):naoqhpW#iaoo]ca#Y9£ #Iaoo]cabap_da`oq__aoobqhhu*#7 156pdeo):naoqhpW#`]p]#Y9 iaoo]ca7 2,6y 2-6y 2.6y 2/6 206bqj_pekj^abknaNaj`an$%w 216 226pdeo):oap$#naoqhp#(pdeo):naoqhp%7 236y 246y 256 3,6;: Each action class returns a set of key/value pairs to the caller. On lines 9 through 12, there are four standard ones that will always be returned: Ê
UÊ naoqhp: Whether the request was successful or not: - or ,
Ê
UÊ iaoo]ca: The human-readable message that goes with the result
Ê
UÊ annkno: A set of key/value pairs, where the key is the parameter and the value is the error message
Ê
UÊ `]p]: If the request is for data, where to look for it
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
Most of the action classes have three methods: ^abknaBehpan, ej`at, and ^abknaNaj`an. The ^abknaBehpan method checks whether it’s an Ajax call. The parent [_da_g=f]t$% method looks like this: bqj_pekj[_da_g=f]t$%w eb$pdeo):NamqaopD]j`han):eo=f]t$%%w ++Pdeoiqopateop6X]llXreasoXh]ukqpoXfokjX]f]t*_pl pdeo):NamqaopD]j`han):naj`an=o$ pdeo(#fokj#%7 y y We have manually mapped Ajax calls to JSON returns. If it is an Ajax call, the layout X]llX reasoXh]ukqpoXfokjX]f]t*_pl is used. We would like to return data to callers in a standard way, of course. However, this is not always possible, Ajax and server-side calls can handle formatted data like JSON, but then HTML messes up the format. The main ej`at$% method is where all the action happens. We have created a [r]he`]pekj$% method. Any parameter coming into the action class must be validated, and we make use of Cake’s validation as much as we can. To do this, we create a model specifically for our action class called IbBap_dIaoo]ca, as shown in Listing 4-6. Listing 4-6. MfFetchMessageController Model (app/models/mf_fetch_message.php) 8;ldl _h]ooIbBap_dIaoo]caatpaj`o=llIk`ah w ++I]ejhubknLDL0qoano r]n j]ia9#IbBap_dIaoo]ca#7 r]n qoaP]^ha9b]hoa7 r]n r]he`]pa9]nn]u$#iaoo]caE`#9:]nn]u$ #nqha#9:]nn]u$#^apsaaj#(/2(/2%( #namqena`#9:pnqa( #iaoo]ca#9:#Lha]oalnkre`a]iaoo]cae`£ kb/2_d]n]_panoejhajcpd*#% %7 y ;: Once the validation has passed, we go ahead and carry out the business logic within ej`at. When that’s done, the ^abknaNaj`an$% method is called. This method sets the naoqhp variable for the views. Of course, we could have just as easily set the result in ej`at$%, but following the idea of the Command pattern, we’re keeping code decoupled and standardized. Our view for the IbBap_dIaoo]ca?kjpnkhhan is quite simple. It takes naoqhpW#`]p]#Y and formats it in HTML, ready to be displayed. It’s worth highlighting that the JSON output
99
100
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
also makes use of the naoqhp variable, but in that case, it formats the output in JSON notation instead of HTML. Our HTML view is shown in Listing 4-7. Listing 4-7. Fetch One Message View (app/views/mf_fetch_message/index.ctp) 8;ldl eb$eooap$ naoqhpW#`]p]#YW#Iaoo]ca#Y%%w a_dk#8`er_h]oo9iaoo]ca:#7 a_dk#8`er_h]oo9iaoo]ca[da]`an:#7 a_dk#8d/:#* naoqhpW#`]p]#YW#Iaoo]ca#Y£ Woq^fa_pY*#8+]:8+d/:#7 a_dk#"j^ol78d2:>u#* naoqhpW#`]p]#YW#Iaoo]ca#Y£ Wai]ehY*#8+d2:#7 a_dk#8+`er:#7 iaoo]ca[e`9 naoqhpW#`]p]#YW#Iaoo]ca#YWe`Y7 pdna]`[e`9 naoqhpW#`]p]#YW#Iaoo]ca#YWpdna]`[e`Y7 a_dk#8`er_h]oo9iaoo]caodknp[iaoo]ca9£ bqhh[iaoo]ca9bap_da`9,e`9iaoo]ca[#* iaoo]ca[e`*#:#7 a_dk naoqhpW#`]p]#YW#Iaoo]ca#YWiaoo]caY7 nalhu[hejg9dpih):hejg$#Nalhu#(£ #+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+IbIaoo]caBkni+ej`at+£ nalhu[pk6#* iaoo]ca[e`*#+pdna]`[e`6#* pdna]`[e`*#+#%7 a_dk#8`er:#* nalhu[hejg*#8+`er:#7 a_dk#8+`er:#7 a_dk#8+`er:#7 y ;:
Fetch Several Messages At present, our IbBap_dIaoo]cao?kjpnkhhan, shown in Listing 4-8, helps us to find queries based on the thread ID.
NNote We can quite easily see the fetch several messages action being deprecated, in favor of using the IbIaoo]caLnk_aoo?kjpnkhhan as a generic “find any messages” class. We’ll leave that as an exercise for
the reader.
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
Listing 4-8. Fetch Several Messages Controller (mf_fetch_messages_controller.php) -68;ldl .6 /6ej_hq`a[kj_a$#ib[_kjpnkhhan*ldl#%7 06 16_h]ooIbBap_dIaoo]cao?kjpnkhhanatpaj`oIb?kjpnkhhanw 26 36r]n j]ia9#IbBap_dIaoo]cao#7 46 56++Pdaiaoo]canaoqhpo -,6r]n iaoo]cao9]nn]u$%7 --6 -.6++Pdaiaoo]canaoqhpokn`ana`sepdej`ajpej`e_]pkn -/6r]n iaoo]caoKn`ana`9]nn]u$%7 -06 -16r]n naoqhp9]nn]u$#naoqhp#9:##( -26#iaoo]ca#9:##( -36#annkno#9:##( -46#`]p]#9:## -56%7 .,6 .-6bqj_pekj^abknaBehpan$%w ..6 ./6pdeo):[_da_g=f]t$%7 .06y .16 .26bqj_pekj[r]he`]pekj$%w .36 .46 naoqhp9pnqa7 .56 /,6pdeo):`]p]9]nn]u$#IbBap_dIaoo]cao#%7 /-6pdeo):`]p]W#IbBap_dIaoo]cao#YW#pdna]`E`#Y9##7 /.6 //6eb$eooap$pdeo):l]ooa`=ncoW#pdna]`E`#Y%%w /06pdeo):`]p]W#IbBap_dIaoo]cao#YW#pdna]`E`#Y9£ pdeo):l]ooa`=ncoW#pdna]`E`#Y7 /16y /26 /36pdeo):IbBap_dIaoo]cao):oap$pdeo):`]p]%7 /46 /56eb$ pdeo):IbBap_dIaoo]cao):r]he`]pao$%%w 0,6 0-6 naoqhp9b]hoa7 0.6 0/6pdeo):naoqhpW#naoqhp#Y9#,#7 006pdeo):naoqhpW#iaoo]ca#Y9£ Pdana]naokialnk^haiosepdukqnnamqaop*7
101
102
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
016pdeo):naoqhpW#annkno#Y9£ pdeo):IbBap_dIaoo]ca):r]he`]pekjAnnkno7 026y 036 046napqnj naoqhp7 056y 1,6 1-6bqj_pekjej`at$%w 1.6 1/6eb$pdeo):[r]he`]pekj$%%w 106 116 _kj`epekjo9]nn]u$%7 126 136eb$eooap$pdeo):`]p]W#IbBap_dIaoo]cao#Y£ W#pdna]`E`#Y%%w 146 156 pdna]`E`9£ pdeo):`]p]W#IbBap_dIaoo]cao#YW#pdna]`E`#Y7 2,6 _kj`epekjoWY9]nn]u$£ Iaoo]ca*pdna]`[e`99: pdna]`E`%7 2-6y 2.6 2/6 iaoo]cao9pdeo):Iaoo]ca):bej`$#]hh#( 206]nn]u$#_kj`epekjo#9: _kj`epekjo( 216jqhh( 226#kn`an#9:#Iaoo]ca*p[_na]pa`[]p=O?# 236%%7 246 256eb$ iaoo]cao99##%w 3,6 3-6pdeo):naoqhpW#naoqhp#Y9#,#7 3.6pdeo):naoqhpW#iaoo]ca#Y9£ #Pdanas]o]lnk^haibap_dejcpdapdna]`o*Lha]oapnu]c]ejh]pan*#7 3/6y 306ahoaw 316 326pdeo):iaoo]cao9 iaoo]cao7 336 346pdeo):[oknpIaoo]cao$%7 356 4,6pdeo):naoqhpW#naoqhp#Y9#-#7 4-6pdeo):naoqhpW#iaoo]ca#Y9£ #Iaoo]caobap_da`oq__aoobqhhu*#7 4.6pdeo):naoqhpW#`]p]#Y9pdeo):iaoo]caoKn`ana`7 4/6y 406y 416
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
426pdeo):oap$#naoqhp#(pdeo):naoqhp%7 436y 446 456bqj_pekj^abknaNaj`an$%w 5,6 5-6pdeo):oap$#naoqhp#(pdeo):naoqhp%7 5.6y 5/6 506bqj_pekj[oknpIaoo]cao$ op]np[e`9##( harah9##%w 516 526op]pe_ opklNqj9-7 536 546++Ukqjarangjks 556eb$ opklNqj'':-,,,%w -,,6napqnj7 -,-6y -,.6 -,/6bkn$ e`t9,7 e`t8oevakb$pdeo):iaoo]cao%7 e`t''%w -,06 -,16eb$eooap$pdeo):iaoo]caoW e`tYW#@kja#Y%%w -,26 -,36++Bkqj`]nkkpiaoo]ca -,46eb$pdeo):iaoo]caoW e`tYW#Iaoo]ca#Y£ W#nalhu[pk#Y99 op]np[e`%w -,56 --,6pdeo):iaoo]caoW e`tYW#Iaoo]ca#Y£ W#ej`ajp#Y9 harah7 ---6 --.6pdeo):iaoo]caoKn`ana`WY9£ pdeo):iaoo]caoW e`tY7 --/6 --06 iaoo]ca[e`9£ pdeo):iaoo]caoW e`tYW#Iaoo]ca#YW#e`#Y7 --16 --26++Pd]p#o`kja7hap#onaikraep --36pdeo):iaoo]caoW e`tYW#@kja#Y9#-#7 --46 --56pdeo):[oknpIaoo]cao$ iaoo]ca[e`( harah*#*#%7 -.,6y -.-6y -..6y -./6y -.06y -.16 -.26;:
103
104
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
As you can see, the structure of this class is similar to that of the IbBap_dIaoo]ca?kjpnkhhan. In this case, we have an extra private method [oknpIaoo]cao$% on line 94, which sorts our message recursively according to replies. We have used a simple full stop to signify the depth of the message within the thread. For peace of mind, we’ve placed a stopper on line 99 just in case the method goes into an infinite recursive loop. Again, our view for the IbBap_dIaoo]cao controller simply takes the naoqhpW#`]p]#Y, loops through the data, and formats the output for HTML display. We’ve used the indent indicator to indent our messages from the left side of the browser. As we are multiplying the number of full stops by pixel value, the greater the depth, the further away it will be displayed from the left margin (see lines 7 and 9 in Listing 4-9). You can just as easily use ai, as that probably makes it more accessible. But hey, we just wanted to make it obvious that you can use pixels instead. Our view is shown in Listing 4-9. Listing 4-9. Fetch Multiple Messages View (app/views/mf_fetch_messages/index.ctp) -68d.:Iaoo]cao8+d.: .6 /68;ldl 06 16bkna]_d$ naoqhpW#`]p]#Y]o _qnnajp[iaoo]ca%w 26 36 ej`ajp9opnhaj$ _qnnajp[iaoo]caW#Iaoo]ca#YWej`ajpY%&.,7 46 56a_dk#8`eropuha9i]ncej)habp6#* ej`ajp*#lt7:#7 -,6 --6a_dk#8`er_h]oo9iaoo]ca[da]`an:#7 -.6a_dk#8d/:#* _qnnajp[iaoo]caW#Iaoo]ca#YWoq^fa_pY*#8+]:8+d/:#7 -/6a_dk#"j^ol78d2:>u£ #* _qnnajp[iaoo]caW#Iaoo]ca#YWai]ehY*#8+d2:#7 -06 -16a_dk#8+`er:#7 -26 -36 iaoo]ca[e`9 _qnnajp[iaoo]caW#Iaoo]ca#YWe`Y7 -46 pdna]`[e`9 _qnnajp[iaoo]caW#Iaoo]ca#YWpdna]`[e`Y7 -56 .,6a_dk#8`er_h]oo9iaoo]ca[iaoo]ca£ odknp[iaoo]ca9bqhh[iaoo]ca9bap_da`9,e`9iaoo]ca[#* iaoo]ca[e`*#:#7 .-6a_dk _qnnajp[iaoo]caW#Iaoo]ca#YWiaoo]caY7 ..6a_dk#8`er:£ 8]dnab9+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+IbIaoo]caBkni+ej`at+£ nalhu[pk6#* iaoo]ca[e`*#+pdna]`[e`6#* pdna]`[e`*#+:Nalhu8+]:8+`er:#7 ./6a_dk#8+`er:#7 .06 .16a_dk#8+`er:#7 .26y .36 .46;:
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
Fetch the Threads We have used the pagination feature within Cake to separate the threads into pages. Listing 4-10 shows the IbBap_dPdna]`o?kjpnkhhan. Listing 4-10. Fetch the Threads Controller (mf_fetch_threads_controller.php) -68;ldl .6 /6ej_hq`a[kj_a$#ib[_kjpnkhhan*ldl#%7 06 16_h]ooIbBap_dPdna]`o?kjpnkhhanatpaj`oIb?kjpnkhhanw 26 36r]n j]ia9#IbBap_dPdna]`o#7 46 56r]n l]cej]pa9]nn]u$#heiep#9:.,( -,6#kn`an#9:]nn]u$£ #Pdna]`*h]op[iaoo]ca[`]pa#9:#@AO?#%%7 --6 -.6r]n naoqhp9]nn]u$#naoqhp#9:##( -/6#iaoo]ca#9:##( -06#annkno#9:##( -16#`]p]#9:## -26%7 -36 -46bqj_pekj^abknaBehpan$%w -56 .,6pdeo):[_da_g=f]t$%7 .-6y ..6 ./6bqj_pekj[r]he`]pekj$%w .06 .16 naoqhp9pnqa7 .26 .36napqnj naoqhp7 .46y .56 /,6bqj_pekjej`at$%w /-6 /.6eb$pdeo):[r]he`]pekj$%%w //6 /06 pdna]`o9pdeo):l]cej]pa$#Pdna]`#%7 /16 /26eb$ pdna]`o99##%w /36pdeo):naoqhpW#naoqhp#Y9#,#7 /46pdeo):naoqhpW#iaoo]ca#Y9£ #Pdanas]o]lnk^haibap_dejcpdapdna]`o*Lha]oapnu]c]ejh]pan*#7 /56y
105
106
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
0,6ahoaw 0-6pdeo):naoqhpW#naoqhp#Y9#-#7 0.6pdeo):naoqhpW#iaoo]ca#Y9£ #Pdna]`obap_da`oq__aoobqhhu*#7 0/6pdeo):naoqhpW#`]p]#Y9 pdna]`o7 006y 016y 026y 036 046bqj_pekj^abknaNaj`an$%w 056 1,6pdeo):oap$#naoqhp#(pdeo):naoqhp%7 1-6y 1.6y 1/6 106;: Adding pagination is essentially a three-step process. Referring to our controller class, first you set up the options in the controller via the l]cej]pa variable on line 9. In our case, we limit the number of records returned to 20. We also order the threads according to the h]op[iaoo]ca[`]pa field in descending order. Next, instead of using the Cake bej`>uWBeah` J]iaY method, we use the l]cej]pa method. This takes the model name as its main parameter (see line 34). The third and final step in using pagination is at the bottom of the view, shown in Listing 4-11. Cake provides a paginate helper (see line 43), which is included by default once the l]cej]pa method is used. We simply call the lnar and jatp methods, which will generate the necessary HTML links. Listing 4-11. Fetch the Threads View (app/views/mf_fetch_threads.php) -68d.:Pdna]`o8+d.: .6 /68;ldl 06a_dkf]r]o_nelp):hejg$#ib[bap_d[pdna]`o+ej`at#%7 16;: 26 368;ldl 46 56bkna]_d$ naoqhpW#`]p]#Y]o _qnnajp[iaoo]ca%w -,6 --6 iaoo]ca[e`9 _qnnajp[iaoo]caW#Iaoo]ca#YWe`Y7 -.6 pdna]`[e`9 _qnnajp[iaoo]caW#Iaoo]ca#YWpdna]`[e`Y7 -/6 iaoo]ca[jqi9 _qnnajp[iaoo]caW#Pdna]`#YWiaoo]ca[jqiY7 -06 -16a_dk#8`er_h]oo9pdna]`[da]`an:#7 -26a_dk#8d/:#* _qnnajp[iaoo]caW#Iaoo]ca#Y£ Woq^fa_pY*#8+]:8+d/:#7
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
-36a_dk#"j^ol78d2:Op]npa`>u£ #* _qnnajp[iaoo]caW#Iaoo]ca#YWj]iaY*#8+d2:#7 -46 -56++Jqi^ankbiaoo]cao .,6a_dk#"j^ol7x"j^ol7#* iaoo]ca[jqi*#iaoo]cao#7 .-6 ..6++Klajhejg ./6a_dk#"j^ol7x"j^ol78ol]je`9klaj[hejg[#* iaoo]ca[e`*#:£ 8]dnab9f]r]o_nelp6rke`$,%7£ kj_he_g9capIaoo]ca$X##* iaoo]ca[e`*#X#(X##* pdna]`[e`*#X#%7:Klaj8+]:8+ol]j:#7 .06 .16++Reaspdna]` .26a_dk#"j^ol7x"j^ol78]£ dnab9+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+IbBap_dIaoo]cao+ej`at+pdna]`E`6#*£ pdna]`[e`*#+:HeopIaoo]cao8+]:#7 .36 .46++Hk]`ejc .56a_dk#"j^ol7x"j^ol78`ere`9hk]`ejc[#* iaoo]ca[e`*#:8+`er:#7 /,6 /-6a_dk#8+`er:#7 /.6 //6++@eolh]ubenopiaoo]ca /06a_dk#8`er_h]oo9pdna]`[iaoo]caodknp[iaoo]ca9 /16bqhh[iaoo]ca9bap_da`9,e`9iaoo]ca[#* iaoo]ca[e`*#:8+`er:#7 /26y /36 /46;: /56 0,68dn_h]oo9l]cej]pkn[heja: 0-6 0.68;ldl 0/6eb$eooap$ l]cej]pkn%%w 006 016a_dkl]cej]pkn):lnar$#ÃLnarekqo#(jqhh( 026jqhh(]nn]u$#_h]oo#9:#`eo]^ha`#%%7 036a_dk#"j^ol7#7 046a_dkl]cej]pkn):jatp$#JatpÄ#(jqhh( 056jqhh(]nn]u$#_h]oo#9:#`eo]^ha`#%%7 1,6a_dk#"j^ol7#7 1-6a_dkl]cej]pkn):_kqjpan$%7 1.6y 1/6;: Line 4 in Listing 4-11 creates an HTML o_nelp tag, which will call the JavaScript file located at ]ll+sa^nkkp+fo+ib[bap_d[pdna]`o+ej`at*fo. This file is shown in Listing 4-12.
107
108
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
Listing 4-12. JavaScript File Used to Support the Fetching of Threads (app/webroot/js/mf_fetch_ threads/index.js) -6bqj_pekjcapIaoo]ca$iaoo]ca[e`(pdna]`[e`%w .6 /6r]nbqhh[iaoo]ca9£ $#iaoo]ca[#'iaoo]ca[e`%*cap=ppne^qpa$#bqhh[iaoo]ca#%7 06 16eb$bqhh[iaoo]ca9##%w 26 $#iaoo]ca[#'iaoo]ca[e`%*ejjanDPIH9bqhh[iaoo]ca7 36napqnjb]hoa7 46y 56 -,6jas=f]t*Namqaop$#+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+IbBap_dIaoo]ca+£ ej`at+iaoo]caE`6#'iaoo]ca[e`'#+*fokj#( --6w]ouj_dnkjkqo6pnqa( -.6ar]hO_nelpo6pnqa( -/6kj?kilhapa6bqj_pekj$naolkjoa(fokj%w -06 -16eb$fokjW#naoqhp#Y99#-#%w -26 -36iaoo]ca[hejgo9£ fokjW#`]p]#YW#Iaoo]ca#YW#iaoo]ca#Y7 -46iaoo]ca[hejgo'9#8`er:#7 -56iaoo]ca[hejgo'9#8]dnab9f]r]o_nelp6£ rke`$,%7kj_he_g9_hkoaIaoo]ca$X##'iaoo]ca[e`'#X#%7:?hkoa8+]:x#7 .,6iaoo]ca[hejgo'9£ #8]dnab9+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+IbBap_dIaoo]cao+ej`at+£ pdna]`E`6#'pdna]`[e`'#+:HeopIaoo]cao8+]:x#7 .-6iaoo]ca[hejgo'9£ #8]dnab9+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+IbIaoo]caBkni+ej`at+£ nalhu[pk6#'iaoo]ca[e`'#+pdna]`[e`6#'pdna]`[e`'#+:Nalhu8+]:#7 ..6iaoo]ca[hejgo'9#8+`er:#7 ./6 .06 $#iaoo]ca[#'iaoo]ca[e`%*ejjanDPIH9iaoo]ca[hejgo7 .16 .26 $#iaoo]ca[#'iaoo]ca[e`%*oap=ppne^qpa$£ #bqhh[iaoo]ca#( $#iaoo]ca[#'iaoo]ca[e`%*ejjanDPIH%7 .36 $#hk]`ejc[#'iaoo]ca[e`%*ejjanDPIH9##7 .46y .56y( /,6kjHk]`ejc6bqj_pekj$%w /-6 /.6 $#iaoo]ca[#'iaoo]ca[e`%*oap=ppne^qpa$£ #odknp[iaoo]ca#( $#iaoo]ca[#'iaoo]ca[e`%*ejjanDPIH%7 //6 $#hk]`ejc[#'iaoo]ca[e`%*ejjanDPIH9£ #8eicon_9+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+eic+]f]t)hk]`an*ceb]hp9+:£ hk]`ejc(lha]oas]ep*#7 /06y /16y%7 /26y
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
/36 /46bqj_pekj_hkoaIaoo]ca$iaoo]ca[e`%w /56 0,6r]nodknp[iaoo]ca9 $#iaoo]ca[#'iaoo]ca[e`%*£ cap=ppne^qpa$#odknp[iaoo]ca#%7 0-6 $#iaoo]ca[#'iaoo]ca[e`%*ejjanDPIH9odknp[iaoo]ca7 0.6y When a user clicks the Open link (see line 23 in Listing 4-11), the capIaoo]ca function on line 1 in Listing 4-12 is called. This function makes an Ajax call to the IbBap_dIaoo]ca method (see line 10). That method will return the message in JSON format. We start the parsing on line 13. We finish the process by displaying the message together with some links, on line 24. A sample of the parsed output is shown in Figure 4-3.
Figure 4-3. Displaying threads
109
110
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
Post Messages Our message posting form, shown in Listing 4-13, posts messages using Ajax by default. However, if the user has JavaScript turned off, it will degrade to standard HTML posting. Because it's not part of our API method calls, its structure doesn’t mirror the API action classes. However, in the future we may include this as an API method call where users can use our form instead of writing their own. They can conveniently include the form in the sidebar of their web site—in a blog, for example. Listing 4-13. Post a Message Controller (mf_message_form_controller.php) 8;ldl ej_hq`a[kj_a$#ib[_kjpnkhhan*ldl#%7 _h]ooIbIaoo]caBkni?kjpnkhhanatpaj`oIb?kjpnkhhanw r]n j]ia9#IbIaoo]caBkni#7 bqj_pekjej`at$%w eb$eooap$pdeo):l]ooa`=ncoW#nalhu[pk#Y%%w pdeo):`]p]W#Iaoo]ca#YW#nalhu[pk#Y9pdeo):l]ooa`=ncoW#nalhu[pk#Y7 nalhuIaoo]caE`9pdeo):`]p]W#Iaoo]ca#YW#nalhu[pk#Y7 nalhuIaoo]ca9pdeo):Iaoo]ca):bej`>uE`$ nalhuIaoo]caE`%7 eb$ailpu$ nalhuIaoo]ca%%w pdeo):`]p]W#Iaoo]ca#YW#oq^fa_p#Y9£ #NA6#* nalhuIaoo]caW#Iaoo]ca#YW#oq^fa_p#Y7 y y eb$eooap$pdeo):l]ooa`=ncoW#pdna]`[e`#Y%%w pdeo):`]p]W#Iaoo]ca#YW#pdna]`[e`#Y9pdeo):l]ooa`=ncoW#pdna]`[e`#Y7 y y y ;: The view for the form is pretty sparse, as shown here: 8;ldl a_dkpdeo):ahaiajp$#iaoo]ca[bkni#%7 ;:
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
As you can see, the view for the message form is separated off into an element, as shown in Listing 4-14. Listing 4-14. Message Posting Form as an Element (/app/views/elements/message_form.ctp) -68;ldl .6eb$oaooekj):_da_g$#Iaoo]ca*bh]od#%%w /6a_dk#8`er_h]oo9dehecdp:#7 06oaooekj):bh]od$%7 16a_dk#8+`er:#7 26y 36;: 46 568))PejuI?A)): -,68;ldl --6a_dk f]r]o_nelp£ ):hejg$#fo_nelpo+pejui_a+fo_nelpo+peju[i_a+peju[i_a#%7 -.6;: -/68o_nelppula9patp+f]r]o_nelp: -06pejuI?A*ejep$w -16ik`a6patp]na]o( -26pdaia6]`r]j_a`( -36pdaia[]`r]j_a`[^qppkjo-6£ ^kh`(ep]he_(qj`anheja(oal]n]pkn(opnegapdnkqcd(fqopebuhabp(£ fqopebu_ajpan(fqopebunecdp(fqopebubqhh(^qhheop(jqiheop(£ qj`k(na`k(hejg(qjhejg( -46pdaia[]`r]j_a`[^qppkjo.6( -56pdaia[]`r]j_a`[^qppkjo/6( .,6pdaia[]`r]j_a`[pkkh^]n[hk_]pekj6pkl( .-6pdaia[]`r]j_a`[pkkh^]n[]hecj6habp( ..6pdaia[]`r]j_a`[op]pqo^]n[hk_]pekj6^kppki( ./6atpaj`a`[r]he`[ahaiajpo6£ ]Wj]iaxdnabxp]ncapxpephaxkj_he_gY(eicW_h]ooxon_x^kn`an9,x]hpx£ pephaxdol]_axrol]_axse`pdxdaecdpx]hecjxkjikqoakranxkjikqoakqpxj]iaY(£ dnW_h]ooxse`pdxoevaxjkod]`aY(bkjpWb]_axoevax_khknxopuhaY(£ ol]jW_h]oox]hecjxopuhaY( .06_kjpajp[_oo6+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+_oo+chk^]h*_oo .16 .26y%7 .368+o_nelp: .468))+PejuI?A)): .56 /,68;ldl /-6a_dkf]r]o_nelp):hejg$#ahaiajpo+iaoo]ca[bkni#%7 /.6;: //6 /068`ere`9l]ca[iaoo]ca:8+`er: /16
111
112
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
/268`ere`9iaoo]ca[bkni: /36 /468;ldl /56a_dk]f]t):bkni$jqhh( 0,6#lkop#( 0-6]nn]u$#qnh#9:#+IbIaoo]caLnk_aoo+ej`at#( 0.6#_kilhapa#9:#ql`]paBkni$namqaop(fokj%7#( 0/6#^abkna#9:#^abknaIaoo]ca$%7# 006% 016%7 026;: 036 0468beah`oapopuha9^kn`an6-ltokhe`^h]_g7^]_gcnkqj`)ei]ca6qnh$£ #+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+]ll+sa^nkkp+eic+^]_gcnkqj`*ceb#%7: 0568hacaj`:8;ldl[[$#=``]Iaoo]ca#%7;:8+hacaj`: 1,6Lha]oabehhej]hhbeah`o*Naiai^an(ol]i]j`^a`]ija` 1-68;ldl 1.6a_dkbkni):annkn$#Iaoo]ca*j]ia#%7 1/6a_dkbkni):ejlqp$#Iaoo]ca*j]ia#(]nn]u$£ #e`#9:#iaoo]caj]ia#(#h]^ah#9:#J]ia6#(#oeva#9:#1,#(£ #i]thajcpd#9:#.11#(#annkn#9:b]hoa%%7 106 116a_dkbkni):annkn$#Iaoo]ca*ai]eh#%7 126a_dk#Ukqnai]ehsehhjkp^a`eolh]ua`*#7 136a_dkbkni):ejlqp$#Iaoo]ca*ai]eh#(]nn]u$£ #e`#9:#iaoo]caai]eh#(#h]^ah#9:#Ai]eh6#(#oeva#9:#1,#(£ #i]thajcpd#9:#.11#(#annkn#9:b]hoa%%7 146 156a_dkbkni):annkn$#Iaoo]ca*oq^fa_p#%7 2,6a_dkbkni):ejlqp$#Iaoo]ca*oq^fa_p#(]nn]u$£ #e`#9:#iaoo]caoq^fa_p#(#h]^ah#9:#Oq^fa_p6#(#oeva#9:#1,#(£ #i]thajcpd#9:#.11#(#annkn#9:b]hoa%%7 2-6 2.6a_dkbkni):annkn$#Iaoo]ca*iaoo]ca#%7 2/6a_dkbkni):ejlqp$#Iaoo]ca*iaoo]ca#(]nn]u$£ #e`#9:#iaoo]caiaoo]ca#(#pula#9:#patp]na]#(#h]^ah#9:#Iaoo]ca6#(£ #nkso#9:#.,#(#annkn#9:b]hoa%%7 206 216a_dkbkni):de``aj$#Iaoo]ca*nalhu[pk#%7 226a_dkbkni):de``aj$#Iaoo]ca*pdna]`[e`#%7 236;: 2468+beah`oap: 256 3,68;ldla_dkbkni):aj`$]nn]u$#h]^ah#9:#Oq^iepIaoo]ca#%%7;: 3-6 3.68+`er:
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
There are a number of interesting things happening in this element. Before we go any further, we have to admit that this simple form turned out to be quite complex because of the use of Ajax and how it can degrade in the absence of JavaScript. First, we have included the TinyMCE what you see is what you get (WYSIWYG) web editor. It was either that or the FCKeditor, which is just as good.
NNote TinyMCE (dppl6+pejui_a*iktea_k`a*_ki) and FCKeditor (dppl6+sss*b_ga`epkn*jap) are two of the most popular web-based open source text editors. You will generally use them to replace the HTML patp]na] tag. They provide editing capabilities much like any desktop word processing editor.
Installing TinyMCE is quite easy. First, download the package from the TinyMCE web site (dppl6++pejui_a*iktea_k`a*_ki). Then unzip the code into the folder +]ll+sa^nkkp+fo+ fo_nelpo+. Next, we need to initialize the editor. This is done from lines 10 to 27 in Listing 4-14. Later, when the form is saved, we also need to trigger TinyMCE to save the content. This is done in the JavaScript helper file (see Listing 4-15, line 47). In Listing 4-14, we include our JavaScript helper file in the line a_dkf]r]o_nelp):hejg$ #ahaiajpo+iaoo]ca[bkni#%7 on line 31. The helper file, shown in Listing 4-15, is stored in + ]ll+sa^nkkp+fo+ahaiajpo+iaoo]ca[bkni*fo. We have created an ahaiajpo folder within +]ll+ sa^nkkp+fo. This allows us to easily pick out which JavaScript file goes with which *_pl file, as we have named the JavaScript file to be the same as the *_pl file that included it. Listing 4-15. JavaScript Helper Functions for the Message Posting Form (/app/webroot/js/elements/message_form.js) -6 .6bqj_pekjql`]paBkni$namqaop(fokj%w /6 06r]nioc[naj`an9##7 16r]nnaoqhp9fokjW#naoqhp#Y7 26r]niaoo]ca9fokjW#iaoo]ca#Y7 36r]nannkno9fokjW#annkno#Y7 46 56 $#l]ca[iaoo]ca#%*ejjanDPIH9#8`er_h]oo9dehecdp:#'iaoo]ca'#8+`er:#7 -,6 --6 $#*oq^iepejlqp#%W,Y*`eo]^ha`9b]hoa7 -.6 -/6++Naikra]hhlnarekqoannkniaoo]cao -06r]n_qnnajp[annkno9 $#*annkn)iaoo]ca#%7 -16 -26bkn$r]ne9,7e8_qnnajp[annkno*hajcpd7e''%w -36r]nannkn[p]c9_qnnajp[annknoWeY7 -46annkn[p]c*l]najpJk`a*naikra?deh`$annkn[p]c%7 -56y .,6
113
114
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
.-6++Ebpdana]naannkniaoo]cao(ql`]papdaoa ..6eb$annkno9##%w ./6 .06bkn$r]nbeah`[e`ejannkno%w .16 .26++Pdaannkniaoo]caej]`er .36r]n`er[annkn9`k_qiajp*_na]paAhaiajp$`er%7 .46`er[annkn*_h]ooJ]ia9annkn)iaoo]ca7 .56`er[annkn*ejjanDPIH9annknoWbeah`[e`Y7 /,6 /-6bkni[aha9 $#iaoo]ca#'beah`[e`%7 /.6l]najp[bkni[aha9bkni[aha*l]najpJk`a7 //6 /06l]najp[bkni[aha*ejoanp>abkna$`er[annkn( /16bkni[aha*lnarekqoOe^hejc%7 /26y /36y /46 /56++Ebeps]ooq__aoobqh(pdajsa_ha]npdabkni 0,6eb$naoqhp99-%w 0-6 $#iaoo]ca[bkni#%*ejjanDPIH9##7 0.6y 0/6y 006 016bqj_pekj^abknaIaoo]ca$%w 026 036pejuI?A*pneccanO]ra$%7 046 056 $#l]ca[iaoo]ca#%*ejjanDPIH9£ #8`er_h]oo9dehecdp:Lkopejc(lha]oas]ep***£ 8eicon_9+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+eic+]f]t)hk]`an*ceb]hp9+:£ 8+`er:#7 1,6 1-6 $#*oq^iepejlqp#%W,Y*`eo]^ha`9pnqa7 1.6y 1/6 In the ql`]paBkni method in Listing 4-15, we basically parse the form process return results, which is in the standard format we described earlier, where the results contain the keys, message, errors, and data. We first remove any errors that were previously there. We do this simply by using the naikra?deh` method on line 18.
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
We then display any errors by looping through the annkno key value and displaying it before the field. The Cake Ajax form can easily do this step for us, but it displays the error messages after the field, and we want the user to read the error first and then see the field to which it refers. We use the DOM method ejoanp>abkna to insert a @ER tag, which contains the error message before the field. The web page dppl6++`arahklan*ikvehh]*knc+aj+`k_o+ @KI6ahaiajp*ejoanp>abkna has some good information about DOM methods. If there are no errors, the form has been posted successfully, and we then clear the form to avoid any trigger-happy user. Additionally, we can display some other useful information there at a later date—maybe some advertisement relating to the posted message, for example. Before posting the Ajax request, we call the ^abknaIaoo]ca$% method on line 45. In that function, we need to manually get TinyMCE to save our message. This happens only because we are using Ajax. If you’re using ordinary HTML to post, you won’t need to carry out this step with TinyMCE. After that, we simply add some user interface sugar, starting with a spinning Ajax loader image. Then we disable the submit button. Returning to our message posting form in Listing 4-14, our next step simply uses the Ajax and form helpers to add some form elements (see line 38). An important point to note is the handling of the error messages relating to the fields. This point applies only when the user browser is not running JavaScript; that is, HTML is being used to post messages. We have turned off the automatic display of the error messages relating to Cake’s rendering of the input tags. This is because we want the error messages to be displayed before the input fields. To add the error messages before the field, we manually display the error messages, as in this example: a_dkbkni):annkn$#Iaoo]ca*j]ia#%7 A sample form for adding a message is shown in Figure 4-4.
NNote The Ajax helper is a wrapper for Prototype’s methods. In the Ajax helper, you can use the ql`]pa option to specify which `er container to update the return result. If you don’t use the ql`]pa option, Cake will use Prototype’s Namqaop method. If you do specify an ql`]pa option, it will use the Ql`]pan method.
115
116
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
Figure 4-4. Posting a message
Process a Message In the processing or saving of a message, we use a model that directly maps to a table. This is unlike some of the earlier controller actions, where the models were essentially wrapper classes for validation, as IbIaoo]caLnk_aoo is an API method that we’re exposing. As shown in Listing 4-16, it follows the same class structure as the other API methods.
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
Listing 4-16. Save a Message Controller (mf_message_process_controller.php) -68;ldl .6 /6ej_hq`a[kj_a$#ib[_kjpnkhhan*ldl#%7 06 16_h]ooIbIaoo]caLnk_aoo?kjpnkhhanatpaj`oIb?kjpnkhhanw 26 36r]n j]ia9#IbIaoo]caLnk_aoo#7 46 56r]n naoqhp9]nn]u$#naoqhp#9:##( -,6#iaoo]ca#9:##( --6#annkno#9:##( -.6#`]p]#9:## -/6%7 -06 -16bqj_pekj^abknaBehpan$%w -26 -36pdeo):[_da_g=f]t$%7 -46y -56 .,6bqj_pekj[r]he`]pekj$%w .-6 ..6 naoqhp9pnqa7 ./6 .06eb$ailpu$pdeo):`]p]%%w .16 .26pdeo):Iaoo]ca):oap$pdeo):`]p]%7 .36 .46eb$ pdeo):Iaoo]ca):r]he`]pao$%%w .56 /,6 naoqhp9b]hoa7 /-6 /.6eb$pdeo):NamqaopD]j`han):namqaopa`Sepd$%99#bkni#%w //6pdeo):Oaooekj):oapBh]od$[[$£ #Oknnu(pdanas]o]lnk^haisepdukqnbkni`ap]eho(oaa^ahks*#(pnqa%%7 /06y /16 /26pdeo):naoqhpW#naoqhp#Y9#,#7 /36pdeo):naoqhpW#iaoo]ca#Y9£ Pdana]naokialnk^haiosepdukqnnamqaop*7 /46pdeo):naoqhpW#annkno#Y9£ pdeo):Iaoo]ca):r]he`]pekjAnnkno7 /56y 0,6y 0-6 0.6napqnj naoqhp7 0/6y 006
117
118
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
016bqj_pekjej`at$%w 026 036eb$pdeo):[r]he`]pekj$%%w 046 056 iaoo]ca[`]pa9`]pa$#u)i)`D6e6o#(igpeia$%%7 1,6 iaoo]ca[e`9Opnejc66qqe`$%7 1-6 pdna]`[e`9Opnejc66qqe`$%7 1.6 knecPdna]`E`9pdeo):`]p]W#Iaoo]ca#YW#pdna]`[e`#Y7 1/6 106eb$pdeo):`]p]W#Iaoo]ca#YW#pdna]`[e`#Y%w 116 pdna]`[e`9pdeo):`]p]W#Iaoo]ca#YW#pdna]`[e`#Y7 126y 136 146pdeo):Iaoo]ca):_na]pa$%7 156 2,6pdeo):`]p]W#Iaoo]ca#YW#p[_na]pa`[]p#Y9 iaoo]ca[`]pa7 2-6pdeo):`]p]W#Iaoo]ca#YW#pdna]`[e`#Y9 pdna]`[e`7 2.6pdeo):`]p]W#Iaoo]ca#YW#e`#Y9 iaoo]ca[e`7 2/6 206eb$pdeo):Iaoo]ca):o]ra$pdeo):`]p]%%w 216 226eb$pdeo):NamqaopD]j`han):namqaopa`Sepd$%99#bkni#%w 236pdeo):Oaooekj):oapBh]od$£ [[$#Ukqniaoo]cad]o^aajlkopa`#(pnqa%%7 246y 256 3,6pdeo):naoqhpW#iaoo]ca#Y9£ #Ukqniaoo]cad]o^aajlkopa`#7 3-6pdeo):naoqhpW#naoqhp#Y9#-#7 3.6pdeo):naoqhpW#annkno#Y9##7 3/6 306++O]rapdapdna]` 316eb$ailpu$ knecPdna]`E`%%w 326 336 pdna]`@]p]9]nn]u$%7 346 pdna]`@]p]W#benop[iaoo]ca[e`#Y9 iaoo]ca[e`7 356 pdna]`@]p]W#h]op[iaoo]ca[`]pa#Y9 iaoo]ca[`]pa7 4,6 pdna]`@]p]W#e`#Y9 pdna]`[e`7 4-6 pdna]`@]p]W#iaoo]ca[jqi#Y9#-#7 4.6 4/6pdeo):Pdna]`):_na]pa$%7 406 pdna]`[o]ra9pdeo):Pdna]`):o]ra$ pdna]`@]p]%7 416 426eb$ pdna]`[o]ra%w 436 446pdeo):Iaoo]ca):`ahapa$pdeo):`]p]%7 456y 5,6y
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
5-6ahoaw 5.6pdeo):Pdna]`):pdna]`LhqoKja$ knecPdna]`E`%7 5/6y 506 516++AranupdejcoaaioKG*Hap#o`k]na`ena_p]bpaniaoo]ca* 526++Eoep_kiejcbnki]op]j`]n`DPIHiaoo]cabkni 536eb$pdeo):NamqaopD]j`han):namqaopa`Sepd$%99#bkni#%w 546pdeo):na`ena_p$]nn]u$£ #_kjpnkhhan#9:#IbIaoo]caBkni#( 556#]_pekj#9:#ej`at#%%7 -,,6atep$%7 -,-6y -,.6y -,/6ahoaw -,06 -,16eb$pdeo):NamqaopD]j`han):namqaopa`Sepd$%99#bkni#%w -,26pdeo):Oaooekj):oapBh]od$£ [[$#Oknnu(pdanas]o]lnk^haisepdukqnbkni`ap]eho(oaa^ahks*#(pnqa%%7 -,36y -,46 -,56pdeo):naoqhpW#iaoo]ca#Y9£ #Oknnu(pdanas]o]lnk^haisepdukqnbkni`ap]eho*#7 --,6pdeo):naoqhpW#naoqhp#Y9#,#7 ---6pdeo):naoqhpW#annkno#Y9£ pdeo):Iaoo]ca):r]he`]pekjAnnkno7 --.6y --/6y --06y --16 --26bqj_pekj^abknaNaj`an$%w --36 --46pdeo):oap$#naoqhp#(pdeo):naoqhp%7 --56y -.,6y -.-6 -..6;: There isn’t much in the process view for HTML requests. It simply displays the message form again, as follows: 8;ldl a_dkpdeo):naj`anAhaiajp$#iaoo]ca[bkni#%7 ;: In Listing 4-16, we start off with some Cake validation. Once validation is all good (on line 47), we do the business stuff of saving the message. If a user is posting a new message, we also need to create a thread record for it. However, if the message wasn’t saved for some reason, we need to remove it from the thread record that we have already saved.
119
120
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
We have used UUIDs to help us with two coding tasks (see lines 50 and 51): Ê
UÊ ÌÊ>ÜÃÊÕÃÊÌÊi>ÃÞÊ`iiÌiÊÌ
iÊÌ
Ài>`ÊÀiVÀ`ÊvÊÌ
iÊiÃÃ>}iÊÀiVÀ`Ê``½ÌÊÃ>Ûi°Ê This isn’t strictly needed, as we can always get the ID quite easily once the thread has been saved.
Ê
UÊ -ViÊ>ÊÌ
Ài>`Êii`ÃÊÌ
iÊ ÊvÊÌ
iÊ>ÃÌÊiÃÃ>}iÊ«ÃÌi`ÊÀÊÌ
iÊvÀÃÌÊiÊvÊ̽ÃÊ>ÊvÀiÃ
Ê post), and a message needs the ID of the thread, it makes sense for us to generate the IDs within the code, rather than rely on the database to give us IDs. We save on the number of queries we make and have fewer lines of code.
Another feature within the code in Listing 4-16 is the redirect after the post, which is quite a common practice nowadays in order to avoid repeated posts. However, this feature isn’t needed in an Ajax call, so we filter this out on lines 32, 66, and 105.
Process a Search Request Again, the IbOa]n_dLnk_aoo?kjpnkhhan follows the structure of the other API methods, as shown in Listing 4-17. Listing 4-17. Search for Messages Controller (mf_search_process_controller.php) -68;ldl .6 /6ej_hq`a[kj_a$#ib[_kjpnkhhan*ldl#%7 06 16_h]ooIbOa]n_dLnk_aoo?kjpnkhhanatpaj`oIb?kjpnkhhanw 26 36r]n j]ia9#IbOa]n_dLnk_aoo#7 46 56r]n l]cej]pa9]nn]u$#heiep#9:.,( -,6#kn`an#9:]nn]u$£ #Iaoo]ca*p[_na]pa`[]p#9:#]o_#%%7 --6 -.6r]n naoqhp9]nn]u$#naoqhp#9:##( -/6#iaoo]ca#9:##( -06#annkno#9:##( -16#`]p]#9:##( -26#oa]n_d[pani#9:## -36%7 -46 -56r]n oa]n_d[pani9##7 .,6 .-6bqj_pekj[r]he`]pekj$%w ..6 ./6 naoqhp9pnqa7 .06 .16eb$eooap$pdeo):`]p]W#IbOa]n_dLnk_aoo#YW#oa]n_d[pani#Y%%w .26
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
.36 oa]n_d[pani9£ pdeo):`]p]W#IbOa]n_dLnk_aoo#YW#oa]n_d[pani#Y7 .46y .56ahoaeb$pdeo):Oaooekj):na]`$oa]n_d[pani%%w /,6 /-6 oa]n_d[pani9pdeo):Oaooekj):na]`$oa]n_d[pani%7 /.6pdeo):`]p]W#IbOa]n_dLnk_aoo#YW#oa]n_d[pani#Y9£ oa]n_d[pani7 //6y /06ahoaeb$eooap$pdeo):l]ooa`=ncoW#oa]n_d[pani#Y%%w /16 /26 oa]n_d[pani9pdeo):l]ooa`=ncoW#oa]n_d[pani#Y7 /36pdeo):`]p]W#IbOa]n_dLnk_aoo#YW#oa]n_d[pani#Y9£ oa]n_d[pani7 /46y /56 0,6pdeo):IbOa]n_dLnk_aoo):oap$pdeo):`]p]%7 0-6 0.6++Jks_da_goa]n_dpani 0/6eb$ pdeo):IbOa]n_dLnk_aoo):r]he`]pao$%%w 006 016 naoqhp9b]hoa7 026 036pdeo):naoqhpW#naoqhp#Y9#,#7 046 056++Oej_apdana#okjhu-beah`(bknpdaiejqpa 1,6++sa#hh]ooqiaep#obnkipdaoa]n_dpani 1-6 r]he`]pekj[annkn9##7 1.6 1/6eb$eooap$pdeo):IbOa]n_dLnk_aoo£ ):r]he`]pekjAnnknoW#oa]n_d[pani#Y%%w 106 r]he`]pekj[annkn9 pdeo£ ):IbOa]n_dLnk_aoo):r]he`]pekjAnnknoW#oa]n_d[pani#Y7 116y 126 136 iaoo]ca9[[$£ #Oknnu(pdanas]o]lnk^haisepdukqnoa]n_dbkni*#* r]he`]pekj[annkn(pnqa%7 146 156pdeo):naoqhpW#iaoo]ca#Y9 iaoo]ca7 2,6pdeo):naoqhpW#annkno#Y9£ pdeo):IbOa]n_dLnk_aoo):r]he`]pekjAnnkno7 2-6y 2.6 2/6pdeo):naoqhpW#oa]n_d[pani#Y9£ pdeo):`]p]W#IbOa]n_dLnk_aoo#YW#oa]n_d[pani#Y7 206pdeo):oa]n_d[pani9£ pdeo):`]p]W#IbOa]n_dLnk_aoo#YW#oa]n_d[pani#Y7 216
121
122
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
226napqnj naoqhp7 236y 246 256bqj_pekjej`at$%w 3,6 3-6 iaoo]cao9]nn]u$%7 3.6 3/6eb$pdeo):[r]he`]pekj$%%w 306 316 _kj`epekjo9]nn]u$%7 326 336eb$pdeo):oa]n_d[pani%w 346 356pdeo):Oaooekj):snepa$oa]n_d[pani(£ pdeo):oa]n_d[pani%7 4,6 4-6 oa]n_d[pani9pdeo):oa]n_d[pani7 4.6 4/6 _kj`epekjoWY9£ ]nn]u$I=P?D$ai]eh(oq^fa_p(iaoo]ca%=C=EJOP$# oa]n_d[pani#%%7 406y 416 426 iaoo]cao9pdeo):l]cej]pa$£ #Iaoo]ca#(]nn]u$kn9: _kj`epekjo%%7 436 446eb$ iaoo]cao99##%w 456pdeo):naoqhpW#naoqhp#Y9#,#7 5,6pdeo):naoqhpW#iaoo]ca#Y9£ #Pdanas]o]lnk^haisepdpdaoa]n_d*Lha]oapnu]c]ejh]pan*#7 5-6y 5.6ahoaw 5/6 506pdeo):naoqhpW#naoqhp#Y9#-#7 516pdeo):naoqhpW#iaoo]ca#Y9£ #Oa]n_dnaoqhpobap_da`oq__aoobqhhu*#7 526pdeo):naoqhpW#`]p]#Y9 iaoo]cao7 536y 546y 556y -,,6 -,-6bqj_pekj^abknaNaj`an$%w -,.6 -,/6pdeo):oap$#naoqhp#(pdeo):naoqhp%7 -,06y -,16y -,26 -,36;:
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
We first validate to see whether we have a search term on line 73. Once that passes, we use Cake’s pagination feature to split the results into pages. We have used a dummy model IbOa]n_dLnk_aoo as a way to validate our search term using Cake’s validation function. We need to store the search term in session so it can be used during the pagination when a user selects a different page. On line 83, we have used MySQL’s I=P?D=C=EJOP operator in our queries. This gives a wider range of accurate matches. To use the MySQL I=P?D=C=EJOP operator in your code, you will need to create a full-text index on the database fields using this command: ?NA=PABQHHPATPEJ@ATbqhh[patp[-KJiaoo]cao$ai]eh(oq^fa_p(iaoo]ca%7
NNote Avoid using the HEGA operator. The HEGA operator is quite expensive, as it must scan all the fields where the operator is used. Also, it doesn’t match variations of the word. For example, if the search term is running, it won’t search for run or runner. MySQL has many other search methods besides the standard HEGA. See dppl6++`ar*iuomh*_ki+`k_+nabi]j+1*,+aj+bqhhpatp)oa]n_d*dpih for details.
The view for our search function is shown in Listing 4-18. Listing 4-18. View Template for the Search Action Class (/app/views/mf_search_process/index.ctp) 8d.:Oa]n_dNaoqhpo8+d.: 8;ldl eb$ naoqhpW#naoqhp#Y99#,#%w a_dk#8`er_h]oo9dehecdp:#7 a_dk naoqhpW#iaoo]ca#Y7 a_dk#8+`er:#7 y ;: 8;ldl eb$ naoqhpW#`]p]#Y%w bkna]_d$ naoqhpW#`]p]#Y]o _qnnajp[iaoo]ca%w a_dk#8`er_h]oo9iaoo]ca[da]`an:#7 a_dk#8d/:#* _qnnajp[iaoo]caW#Iaoo]ca#Y£ Woq^fa_pY*#8+]:8+d/:#7 a_dk#"j^ol78d2:>u#* _qnnajp[iaoo]caW#Iaoo]ca#Y£ Wai]ehY*#8+d2:#7 a_dk#8+`er:#7
123
124
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
iaoo]ca[e`9 _qnnajp[iaoo]caW#Iaoo]ca#YWe`Y7 pdna]`[e`9 _qnnajp[iaoo]caW#Iaoo]ca#YWpdna]`[e`Y7 a_dk#8`er_h]oo9iaoo]ca[iaoo]caodknp[iaoo]ca9£ bqhh[iaoo]ca9bap_da`9,e`9iaoo]ca[#* iaoo]ca[e`*#:#7 a_dkpatp):decdhecdp$ _qnnajp[iaoo]caW#Iaoo]ca#Y£ Wiaoo]caY( naoqhpW#oa]n_d[pani#Y( #8ol]j_h]oo9decdhecdp[oa]n_d:X-8+ol]j:# %7 a_dk#8`er:8ol]je`9klaj[hejg[#* iaoo]ca[e`*#:£ 8]dnab9+_]ga+[[_d]lpano[[+iaoo]ca[bknqi+IbBap_dIaoo]cao+ej`at+£ pdna]`E`6#* pdna]`[e`*#+:Reasejpdna]`8+]:8+`er:#7 a_dk#8+`er:#7 y y ahoaw a_dk#8`er_h]oo9dehecdp:#7 a_dk#JkNaoqhpo#7 a_dk#8+`er:#7 y ;: 8dn_h]oo9l]cej]pkn[heja: 8;ldl eb$eooap$ l]cej]pkn%%w l]cej]pkn):klpekjo$]nn]u$£ #ql`]pa#9:#i]ej[_kjpajp[_kjp]ejan#(#ej`e_]pkn#9:#hk]`ejc#%%7 a_dkl]cej]pkn):lnar$#ÃLnarekqo#(jqhh(jqhh(£ ]nn]u$#_h]oo#9:#`eo]^ha`#%%7 a_dk#"j^ol7#7 a_dkl]cej]pkn):jatp$#JatpÄ#(jqhh(jqhh(£ ]nn]u$#_h]oo#9:#`eo]^ha`#%%7 a_dk#"j^ol7#7 a_dkl]cej]pkn):_kqjpan$%7 y ;:
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
The view loops through the message results and formats the output. In the pagination helper methods at the bottom, we have used Ajax for the jatp and lnar links. In Cake, we simply add the following line to Ajaxify the links: l]cej]pkn):klpekjo$]nn]u$#ql`]pa#9:#i]ej[_kjpajp[_kjp]ejan#( #ej`e_]pkn#9:#hk]`ejc#%%7
NTip We have used the text helper to highlight our search term. But don’t forget there are also some other basic functions in the Cake API that may help you out in other situations. For example, in parts of this application, we have used the [[$% helper function instead of using a_dk or even the Cake shorthand a_dk function bqj_pekja$%. See dppl6++]le*_]galdl*knc+^]oe_o[4ldl*dpih for details.
Writing the API Documentation For many developers, writing documentation is never a pleasant experience. But for this chapter, you could say it’s the end point. Without the documentation, there’s no reason for writing the API. Why bother to structure our actions around the Command pattern? It would have been easier to just write each action within one or two controllers. The following shows the documentation for the Web Forum API as it would appear on our forum web site. Welcome to our message forum API. We provide five different methods for you to use. All our method returns use the JSON format. Each request will return four or more keys. The standard four keys returned on every request are as follows: Ê
UÊ naoqhp: If 1 is returned, this means the request was processed successfully. If 0 is returned, then see iaoo]ca and annkno key.
Ê
UÊ iaoo]ca: A human-friendly return message. This will complement the naoqhp key.
Ê
UÊ annkno: This will contain error messages relating to the result. It will be given in key/ value pairs, where the gau is the name of the parameter and r]hqa contains the error message.
Ê
UÊ `]p]: If the request returns data, it will be held in this key. You must end all URL requests in *fokj. An example request would look like this:
dppl6++sss*at]ilha*_ki+IbBap_dIaoo]ca+£ ej`at+E`6044^^,02).]a4)0/_.)]b0-),-10_^``12_^+*fokj
125
126
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
And the return response may look like this: wnaoqhp6-(iaoo]ca6Iaoo]cabap_da`oq__aoobqhhu*(annkno6(`]p]6£ wIaoo]ca6we`6044^^,02).]a4)0/_.)]b0-),-10_^``12_^(j]ia6FkdjPepkn(£ ai]eh6obo`(iaoo]ca68l:Peiapn]rahi]_deja*Ckk`skngejckn`an*Qoa`£ kjhukj_a*-,(,,,Jejceokjk*ebukq#naejpanaopa`(lha]oa_]hhia]p£ /-0-15.21/*8X+l:(nalhu[pk6(oq^fa_p6Dulan@eiajoekj]hNaokj]pkn£ bkno]ha(p[_na]pa`[]p6.,,4),3).3,,6-26..(£ pdna]`[e`6044^^,02)1,,0)00a.)5`^`),-10_^``12_^y(£ Pdna]`6we`6044^^,02)1,,0)00a.)5`^`),-10_^``12_^(£ benop[iaoo]ca[e`6044^^,02).]a4)0/_.)]b0-),-10_^``12_^(£ h]op[iaoo]ca[`]pa6.,,4),3).3,,6-26..(iaoo]ca[jqi6-yyy The API documentation follows.
MfFetchMessage Get one forum message.
Arguments iaoo]caE` (Required)—The message ID
HTTP Method LKOP
Syntax dppl6++W`ki]ejY+IbBap_dIaoo]ca+ej`at+iaoo]caE`6Wiaoo]ca[e`Y+*fokj
Return Standard return keys
MfFetchMessages Note the extra letter o. Get more than one message from the forum. At present, we filter only by the thread ID.
Arguments pdna]`E` (Required)—The thread ID
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
HTTP Method LKOP
Syntax dppl6++W`ki]ejY+IbBap_dIaoo]cao+ej`at+pdna]`E`6Wpdna]`[e`Y+*fokj
Return Standard return keys
MfFetchThreads Get the forum threads. This returns a set of paginated messages. The messages are sorted according to the date of the last message that was posted within a thread. Each page has 20 messages. At present, this amount is fixed.
Arguments l]ca (Optional)—The page number starting from 1. Numeric.
HTTP Method LKOP
Syntax dppl6++W`ki]ejY+IbBap_dIaoo]cao+ej`at+l]ca6Wl]ca[jqi^anY+*fokj
Return Standard return keys
MfMessageProcess Post a message onto the forum.
Arguments Ê
UÊ j]ia (Optional)—Sender’s name. Maximum 255 characters.
Ê
UÊ ai]eh (Required)—Sender’s e-mail address. Maximum 255 characters.
Ê
UÊ oq^fa_p (Required) —Message subject. Maximum 255 characters.
Ê
UÊ iaoo]ca (Optional) —Message body. Maximum 16,777,215 characters.
127
128
C HAPTER 4 N A M ES S A G E FOR U M W EB S ER VIC E
HTTP Method LKOP
Syntax dppl6++W`ki]ejY+IbIaoo]caLnk_aoo+ej`at*fokj
Return Standard return keys
MfSearchProcess Search for messages. The results are paginated with 20 messages per page.
Arguments Ê
UÊ oa]n_dPani (Required)—Search term. Maximum 255 characters.
Ê
UÊ l]ca (Optional)—Page number. Numeric.
HTTP Method LKOP
Syntax dppl6++W`ki]ejY+IbOa]n_dLnk_aoo+ej`at*fokj
Return Standard return keys, plus extra oa]n_d[pani key, which contains the search term
C H A P T E R 4 N A M E S S A G E F O R U M W E B S E R V I C E
Summary In this application, we’ve covered several Cake topics, including the use of Ajax, JSON web service returns, Cake pagination, the use of validation in a model with no associated table, and how to include the TinyMCE browser editor. The highlight of the chapter, however, was the building of a web service API. The application still has a number of features we should add before it can be used in a commercial environment. Here are some suggestions that you can use to further develop the forum application: Ê
UÊ /
ÃÊÃÊ>ÊLÛÕÃÊi°Ê-Ì«Êë>iÀÃtÊ/
iÀiÊ>ÀiÊ>ÞÊÌiV
µÕiÃÊÌÊ«ÀiÛiÌÊë>]Ê or at least keep it down to a minimum. In the final chapter in this book, we implement a Captcha authentication test. You can easily integrate this into the forum application. However, this puts the responsibility on every user. If you don’t like interrupting the usability of the site, you can employ a filter, like a Bayesian text filter, instead. There are several PHP versions of this filter floating about. For other techniques, see dppl6++ aj*segela`e]*knc+sege+Bknqi[ol]i.
Ê
UÊ 1ÃiÀÃÊÃ
Õ`ÊLiÊ>LiÊÌÊÕ«>`Ê>}iÃÊÌÊÌ
iÊvÀÕ°
Ê
UÊ 1ÃiÀÃÊÃ
Õ`ÊLiÊ>LiÊÌÊÊi>ÊÕ«`>ÌiÃÊÊ>ÊÌ
Ài>`°Ê/
ÃÊÃÊ«>ÀÌVÕ>ÀÞÊ«ÀÌ>ÌÊvÀÊ users who have posted questions.
Ê
UÊ ``ÊÕÃiÀÊ>VVÕÌðÊ9ÕÊVÕ`ÊÌ
iÊ>ÜÊÞÊÀi}ÃÌiÀi`ÊÕÃiÀÃÊÌÊ«ÃÌÊiÃÃ>}iðÊÜever, this might be a barrier to the use of the forum, since it’s one more hurdle for users to jump over. Fewer users posting messages means fewer people will find anything interesting on the forum.
Ê
UÊ 7iÊ
>ÛiÊÌÊ>`ÌÊÌ
>ÌÊÕÀÊ>««V>ÌÊ
>ÃÊiÊÃ}
ÌÞÊÕVvÀÌ>LiÊ>Ài>°Ê"ÕÀÊViÌÊ action classes like ib[lkop[bkni[_kjpnkhhan*ldl and our API action classes like ib[ oa]n_d[lnk_aoo[_kjpnkhhan*ldl more or less live within the same domain. They share the same folders and same parent controllers. It’s as if they were part of the API, but, of course, they are not. You should separate them out, either into separate controllers or separate folders.
129
CHAPTE R
5
Google Maps and the Traveling Salesman S
ome friends of ours are about to go on a big European vacation. They talked about how they’ve used Google Maps to help them find the location of hotels, restaurants, and local attractions. They wanted to keep friends and families updated of their progress, but didn’t want to use e-mail. Furthermore, they weren’t sure in which order to visit the places. Being far more enthusiastic than they were, we said we would write an application for them that they can use to enter their destinations, make comments, and plan their journey. In our application, we’re going to be covering many topics. Since we’re going to be using Google Maps, client-side JavaScript will be employed. We also want to store the locations and the comments they make. Naturally, we’ll be creating some Cake models to represent these data entities. We will also be building a straightforward controller to hold the functions that will manage the locations and comments. One of the main features of the application relates to a classic computing puzzle called the “traveling salesman problem” (or in our case, the traveling tourist problem). Namely, a salesman needs to visit a number of cities only once, but return back to the same place as where he started. Devise an algorithm to find the shortest route for the whole trip. This part of the application will be done client side using a simple algorithm, but the calculated route will be stored on the database. Now, to get started, we’ll first talk about Google Maps.
Hello Map! Google Maps is pretty easy to use, so we can dive straight in with the Google Map equivalent of the Hello World program, as shown in Listing 5-1. Listing 5-1. A Simple Google Maps Example -68@K?PULAdpih)++S/?++@P@TDPIH-*,Opne_p++AJ£ dppl6++sss*s/*knc+PN+tdpih-+@P@+tdpih-)opne_p*`p`: .68dpihtihjo9dppl6++sss*s/*knc+-555+tdpih: /68da]`: 068iap]dppl)amqer9_kjpajp)pula_kjpajp9patp+dpih7_d]noap9qpb)4+: 168pepha:CkkchaI]loF]r]O_nelp=LEAt]ilha8+pepha: 268o_nelpon_9dppl6++i]lo*ckkcha*_ki+i]lo;beha9]le"]il7r9."]il7£ gau9ejoanp[ukqn[gau[danapula9patp+f]r]o_nelp:8+o_nelp:
131
132
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
368o_nelppula9patp+f]r]o_nelp: 46bqj_pekjejepe]heva$%w 56eb$C>nksoanEo?kil]pe^ha$%%w -,6r]ni]l9jasCI]l.$`k_qiajp*capAhaiajp>uE`$i]l[_kjp]ejan%%7 --6i]l*oap?ajpan$jasCH]pHjc$1-*055/3(),*-00.-%(-/%7 -.6y -/6y -068+o_nelp: -168+da]`: -268^k`ukjhk]`9ejepe]heva$%kjqjhk]`9CQjhk]`$%: -368`ere`9i]l[_kjp]ejanopuha9se`pd62,,lt7daecdp60,,lt:8+`er: -468+^k`u: -568+dpih: The code in Listing 5-1 displays a 600 400 pixel Google Map on the page. The location of the map is set to the coordinates for London, specified by the oap?ajpan$% function on line 11. The output from the code in Listing 5-1 is shown in Figure 5-1.
Figure 5-1. Google Maps showing London
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
To get started, we need to include the JavaScript Google Maps API file. This is done using the 8o_nelp: tag on line 6. Within the tag, you must provide your own Google Maps API key. You can get a key (which is free) at dppl6++i]lo*ckkcha*_ki.
NTip If you are developing on a local machine, you can still get a key. For example, you can use dppl6++-.3*,*,*- or dppl6++hk_]hdkop, depending on which local URL you are using.
Once the page has been loaded via the kjhk]` event on line 16, the ejepe]heva$% function is called. The ejepe]heva$% method, as defined on line 8, first checks whether we have a compatible browser. If we do, we then go ahead and create an object that will represent a map on the web page. This is done using the CI]l. class. Additionally, we also need to provide an area where it can display the map; in our case, it’s a 8`er: tag with an ID called i]l[_kjp]ejan on line 17. We’re not quite finished yet though. In order for a map to be displayed, we need to tell the map object to display a location, and this is done using the oap?ajpan$% function on line 11, which sets the location and displays the map at the same time. And that’s it! Wasn’t that easy? When the browser is closed or when a different page is brought up, the kjqjhk]` event calls CQjhk]`$%. This Google Map function closes down unwanted connections and is used to avoid memory leaks.
Google Maps Explained Google Maps is almost exclusively a client-side JavaScript API. Although you can make some server-side calls, that is not encouraged. After all, Google collects better statistics if the calls are made from the browser rather than from the server.
NNote To effectively use Google Maps, you need to have some basic knowledge of JavaScript, particularly the object-oriented areas of the language. Two sites to get you started are dppl6++sss*s/o_dkkho*_ki+ fO+fo[k^f[ejpnk*]ol and dppl6++`arahklan*ikvehh]*knc+aj+`k_o+Ejpnk`q_pekj[pk[ K^fa_p)Kneajpa`[F]r]O_nelp.
Before we start, let’s go over the main features of the API that are relevant to our application.
Geocoding Geocoding is the process of converting textual locations such as street addresses or place names into geographic coordinates. This is important for a number of reasons. We can locate exact positions on a map, enabling us to place markers accurately. It eliminates confusion where two or more places have the same name. The Google Maps API includes a class that provides us with a geocoding service. This is the C?heajpCak_k`an class.
133
134
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
Google Map Events Within browsers, JavaScript can be programmed to execute code depending on certain events. In traditional terminology, this is called event-driven programming. These events come in many forms: they can be mouse-based, keyboard-based, or even based on other events, such as when a page is completely loaded. Event-based programming is particularly important in an Ajax environment. Since calls are mostly asynchronous, things don’t happen in a nice serial fashion.
NNote Before the use of Ajax, just about every activity on a browser was synchronous. Users had to wait for feedback whenever they generated an event, such as by clicking a link or submitting a form. Essentially, you could make only one call to the server at any one time. With the use of TIHDpplNamqaop in Ajax, we can now make many calls to the server within a single web page, which makes them all asynchronous. Events can happen independently of each other.
Google Maps defines its own specialized events, which are handled by the API itself. These are separate from the DOM events within a browser, which are more generic. For example, a _he_g event can be attached to any instance of a map, so when a user clicks a map, a call is made to a function, which opens a window with information regarding the location that the user has clicked and additionally stores the coordinates of the location with a call to the server using Ajax. Map events are handled by the CArajp object. We will register events using the ]``Heopajan$% static method.
Map Interface Elements In the Google Maps API, you can add interface control elements, which allow you to interact with the map. Examples include buttons that you can use to move the map around, instead of using the mouse, and a sliding control bar to zoom in and out of the map. In Google Maps terminology, these are known as controls. All the control elements subclass the C?kjpnkh class. You can define your own control by subclassing this class. For example, you can create a button that makes an Ajax call to your database server, fetching any journey near that location.
Overlays Objects that move with the map are called overlays. These can be pushpins marking the location of a point or graphical lines that show route directions. The API provides several built-in overlay classes, which are listed in Table 5-1. Markers are interactive in that they can be dragged across the map and placed in a new location. Each marker has an icon attached to it. You can define your own icon or use the default one. A large number of markers can slow down the display of the map. As a result, the API has a marker manager class called I]nganI]j]can.
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
Table 5-1. Map Overlay Objects
Overlay
Description
CI]ngan
This class is used to mark a point on the map.
CLkhuheja
This class is used to lay down lines on the map. There is also a CLkhuckj class, where the lines form a closed area.
CCnkqj`Kranh]u
With this class, as well as drawing lines on the map, you can add images on top of them.
CPehaH]uanKranh]u With this class, the map itself is an overlay. You can modify the map itself with your own version of the map. However, you probably won’t be using this very often. The pop-up bubble speech window is a special overlay; only one can exist in any one map instance.
Driving Directions One useful feature in the Google Maps API is the ability to map out a travel route via several locations. These routes are then marked in blue on the map. This is done via the C@ena_pekjo class. A very useful feature is the ability of the class to take locations either as textual names or latitude/longitude coordinate points. As an example, Listing 5-2 shows plotting a route from New York to Anchorage. Figure 5-2 shows the route map itself. Listing 5-2. Using the Google Maps GDirections Class to Find a Route -68dpih: .6 /68da]`: 068pepha:CkkchaI]loC@ena_pekjo?h]oo8+pepha: 16 268o_nelpon_9dppl6++i]lo*ckkcha*_ki+i]lo;beha9]le"r9.*t"£ gau9ejoanp[ukqn[gau[danapula9patp+f]r]o_nelp:8+o_nelp: 36 468o_nelppula9patp+f]r]o_nelp: 56 -,6bqj_pekjejepe]heva$%w --6i]l9jasCI]l.$`k_qiajp*capAhaiajp>uE`$i]l[_]jr]o%%7 -.6`ena_pekjo9jasC@ena_pekjo$i]l%7 -/6`ena_pekjo*hk]`$bnki6JasUkng(QO=pk6=j_dkn]ca%7 -06y -16 -268+o_nelp: -36 -468+da]`: -56 .,68^k`ukjhk]`9ejepe]heva$%: .-68`ere`9i]l[_]jr]oopuha9se`pd60,,lt7daecdp604,lt7:8+`er: ..68+^k`u: ./6 .068+dpih:
135
136
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
Figure 5-2. Route from New York to Anchorage This C@ena_pekjo class will be an important component of the project. When a query is sent off to the Google servers, it returns a number of useful items of information. One of which is the distance between the locations. This is very important to us, because it will provide the distance via the roads on the map rather than the straight linear distance between locations. However, if the user were planning to fly, the straight line distance would be more appropriate. In that case, we could easily calculate the distance using the latitude/longitude information. OK, now that we have explained how Google Maps work, we can start our application by gathering the requirements.
Application Requirements Our travel application will allow our friends and also the general public to plan their travel journeys and make comments on the places they visit. It will be a web site where they can keep friends and families up-to-date on their travels.
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
So that our application can be as successful as possible, we will employ a bit of usercentered design philosophy, a term originally coined by Donald Norman. We will think of the application in terms of users or personas, rather than application functions. Sometimes these needs also correlate directly with business objectives. Using our friends as our average persona, we map out their needs and the conclusions that we can draw from them. A user interface can then be created based on these conclusions. We run through the ways in which our friends will use the site. We start the process by asking them some broad questions. These are mapped out as follows: Ê
UÊ Scenarios/Needs: We’re going to Europe soon, and we want to keep a blog or journal of some kind to keep family and friends updated.
Ê
UÊ Conclusion: Users want to be able to enter destinations and make comments about those destinations. We need to save the destinations so they can make comments while they are on their journey. We can continue to draw conclusions from this simple need, but that is probably enough for us.
Ê
UÊ Scenarios/Needs: We’re not sure which places to visit first.
Ê
UÊ Conclusion: There are numerous conclusions we can draw from this statement. For example, we can map out a user journey based on different personas: art lovers, party people, or a combination of personas. Or we could map a user journey based on age groups, such as for students. But in this case, we will simply employ the traveling salesman algorithm to find the shortest route between each destination.
We will base our interface on what we know from this brief user-centered design exercise. So far, we have covered the broad issues of the application. Next, we will map out a functional specification that we can use for our Cake application. The functions will include the following: Ê
UÊ /
iÀiÊÜÊLiÊ>ÊVÃÃÌiÌÊ>Û}>ÌÊL>ÀÊ>ÌÊÌ
iÊÌ«]ÊÃViÊÌ
iÊvVÕÃÊÃÊÊÌ
iÊ application.
Ê
UÊ 1ÃiÀÃÊÜÊLiÊ>LiÊÌÊ«>ÊÌ
iÀÊÕÀiÞÊÊÌ
iÊvÀÃÌÊ«>}iÊvÊÌ
iÊ>««V>ÌÊLÞÊ entering their destination.
Ê
UÊ /
iÀiÊÜÊLiÊ>Ê«>}iÊÜ
iÀiÊÌ
iÞÊV>ÊÀiÌÀiÛiÊ>ÊÃ>Ûi`ÊÕÀiÞÊ>`Ê>iÊViÌð
Ê
UÊ /
iÀiÊÜÊLiÊ>Ê«>}iÊÜ
iÀiÊv>iÃÊ>`ÊvÀi`ÃÊV>ÊÛiÜÊÌ
iÀÊViÌð
In addition to these functions, we will add a further twist by allowing users to add tags to their journeys. This will be a comma-separated list of strings or tags, which you can associate with a journey. From the specification, we have sketched out the simple layout of the application in Figure 5-3.
137
138
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
Figure 5-3. Sketch of our home page
Application Structure We will set out to create our journeys controller, which will represent the journeys within the application. It will be responsible for the server-side needs of the application, saving and retrieving user journeys, for example. Along with the journeys controller, there will be three related models. Remember that the model class names follow the name of the controller. The model class names we will use are Fkqnjau, Hk_]pekj, and P]c. Table 5-2 shows the relationships among the different tables. This will give us an overall picture of how the different data elements are related to each other. Table 5-2. Main Database Table Relationships
Table
Relationship
fkqnjauo
Each journey is composed of different locations. Also, each journey can have more than one tag.
hk_]pekjo
One location belongs to only one journey.
p]co
One tag belongs to only one journey.
The information in Table 5-2 can also be shown graphically in an entity-relationship diagram, as in Figure 5-4.
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
Figure 5-4. Entity-relationship diagram of application tables
NTip Data schemas are quite valuable during development. They help you to visualize the relationships among elements, identify problems, and maybe even improve on the relationships. In our example, we see that a route has one tag line associated with it, but later on, we may also want tags associated with locations. But would this add too much complexity to the application? Always be aware of feature bloat—adding unnecessary features that may be used only by a small percentage of users.
From the data schema, we can create our database tables. Listing 5-3 shows the structure of the fkqnjauo table. Listing 5-3. The journeys Table Schema ?NA=PAP=>HA\fkqnjauo\$ \e`\ejp$--%JKPJQHH]qpk[ej_naiajp( \fkqnjau[j]ia\r]n_d]n$.11%JKPJQHH( \jkpao\patpJKPJQHH( \l]ooskn`\r]n_d]n$.11%JKPJQHH( LNEI=NUGAU$\e`\% %7 The fkqnjau[j]ia field stores the journey name that the user enters. The jkpao field is for comments about the journey itself. Editing a journey can be done only when a correct password is used, so we have created a l]ooskn` field. Listing 5-4 shows the structure of the hk_]pekjo table.
139
140
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
Listing 5-4. The locations Table Schema ?NA=PAP=>HA\hk_]pekjo\$ \e`\ejp$--%JKPJQHH]qpk[ej_naiajp( \hk_]pekj[j]ia\r]n_d]n$.11%JKPJQHH( \_kiiajpo\r]n_d]n$.11%`ab]qhpJQHH( \_kkn`\r]n_d]n$.11%JKPJQHH( \fkqnjau[e`\ejp$--%JKPJQHH( LNEI=NUGAU$\e`\% %7 The name of the location that the user enters is held in hk_]pekj[j]ia. The _kiiajpo field stores the comments for a particular location. The _kkn` field is longitude and latitude information taken from geocoding the location name. Finally, fkqnjau[e` is the foreign key to the fkqnjauo table. Listing 5-5 shows the p]co table structure. Listing 5-5. The tags Table Schema ?NA=PAP=>HA\p]co\$ \e`\ejp$-,%qjoecja`JKPJQHH]qpk[ej_naiajp( \p]c[j]ia\r]n_d]n$-,,%`ab]qhpJQHH( \fkqnjau[e`\r]n_d]n$.11%JKPJQHH( LNEI=NUGAU$\e`\% %7 The p]co table is pretty simple. The p]c[j]ia field holds a particular tag name for a journey. The fkqnjau[e` field is the foreign key to the fkqnjauo table.
Cake Models From the schema in Listings 5-3, 5-4, and 5-5, we created the Cake models, as shown in Listings 5-6, 5-7, and 5-8. Listing 5-6. The Journey Model (/app/models/journey.php) _h]ooFkqnjauatpaj`o=llIk`ahw r]n j]ia9#Fkqnjau#7 ++Pdafkqnjau`]p]r]he`]pekj r]n r]he`]pa9]nn]u$#fkqnjau[j]ia#9:]nn]u$ #nqha#9:]nn]u$#^apsaaj#(-(.11%( #namqena`#9:pnqa( #iaoo]ca#9:#Ukqnfkqnjauj]ia iqop^a^apsaaj-]j`.11 _d]n]_panohkjc*#%%7
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
++A]_dfkqnjaud]oi]juhk_]pekjo]j`]hoki]jup]co* r]n d]oI]ju9]nn]u$#Hk_]pekj#9:]nn]u$#_h]ooJ]ia#9:#Hk_]pekj#%( #P]c#9:]nn]u$#_h]ooJ]ia#9:#P]c#% %7 y
Listing 5-7. The Location Model (/app/models/location.php) _h]ooHk_]pekjatpaj`o=llIk`ahw r]n j]ia9#Hk_]pekj#7 r]n ^ahkjcoPk9]nn]u$#Fkqnjau#%7 y Listing 5-8. The Tag Model (/app/models/tag.php) _h]ooP]catpaj`o=llIk`ah w r]n j]ia9#P]c#7 r]n ^ahkjcoPk9]nn]u$#Fkqnjau#%7 y All three models are quite straightforward, except Fkqnjau, which contains a d]oI]ju association that reflects the schema. A simple validation code has also been added, requiring each journey entry into the database to have a fkqnjau[j]ia within it.
The Interface Like most modern web applications, a large part of our application is written in JavaScript that sits within the browser. Most of the action will initially occur in the browser itself. The server-side code comes into play when the user decides to save or retrieve the journey information.
The Global Layout We start off by creating the global layout file in +]ll+reaso+h]ukqpo+`ab]qhp*_pl, as shown in Listing 5-9.
141
142
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
Listing 5-9. The Global Layout File (/app/views/layouts/default.ctp) -68@K?PULAdpihLQ>HE?)++S/?++@P@TDPIH-*,Opne_p++AJ .6dppl6++sss*s/*knc+PN+tdpih-+@P@+tdpih-)opne_p*`p`: /6 068dpihtihjo9dppl6++sss*s/*knc+-555+tdpih: 168da]`: 26 368iap]dppl)amqer9_kjpajp)pula_kjpajp9patp+dpih7_d]noap9qpb)4+: 46 568))l]capepha)): -,68pepha:8;ldla_dk pepha[bkn[h]ukqp7;:8+pepha: --6 -.68))l]ca_oo)): -/68;ldla_dkdpih):_oo$#oepa#%7;: -06 -168o_nelpon_9dppl6++i]lo*ckkcha*_ki+i]lo;beha9]le"]il7r9."]il7£ gau9ejoanp[ukqn[gau[danapula9patp+f]r]o_nelp:8+o_nelp: -26 -368;ldl -46a_dkf]r]o_nelp):hejg$#o_nelp]_qhkqo)fo)-*4*-+he^+lnkpkpula#%7 -56a_dkf]r]o_nelp):hejg$#£ o_nelp]_qhkqo)fo)-*4*-+on_+o_nelp]_qhkqo*fo;hk]`9abba_po#%7 .,6 .-6++ej_hq`aoepa*fo ..6a_dkf]r]o_nelp):hejg$#oepa#%7 ./6;: .06 .168+da]`: .26 .368^k`ukjhk]`9ejepe]heva$%kjqjhk]`9CQjhk]`$%: .46 .568`ere`9_ajpan[_kjpajp: /,6 /-68`er_h]oo9da]`an[sn]llan: /.68d-:Pn]rahI]lln8+d-: //68d0:8e:>a_]qoahebaeo]fkqnjau6%8+e:8+d0: /068+`er: /16 /268`er_h]oo9j]r[-: /368;ldla_dkdpih):hejg$#Dkia#(#+^]oa#%7;: /46"j^ol7x"j^ol7 /56 0,68;ldla_dkdpih):hejg$#NapnearaFkqnjau#( 0-6#+fkqnjauo+napneara[bkni#%7;: 0.6"j^ol7x"j^ol7 0/6
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
0068;ldla_dkdpih):hejg$#@eolh]uFkqnjau#( 016#+fkqnjauo+`eolh]u[fkqnjau#%7;: 0268+`er: 036 0468`ere`9i]ej[_kjpajp[_kjp]ejan: 0568;ldla_dk _kjpajp[bkn[h]ukqp7;: 1,68+`er: 1-6 1.68+`er: 1/6 1068+^k`u: 1168+dpih:
NNote You can create many layouts in the h]ukqpo folder and change between them in the same view. Just set the h]ukqp variable in the controller action. For example, if you created a new layout file +h]ukqpo+ r]jehh]*_pl, you can change the layout from within your action with this statement: pdeo):h]ukqp9 #r]jehh]#7.
Let’s go through the important lines in Listing 5-9. We start by setting the title in line 10. Next, we include our CSS file for our application in line 13. On line 15, we bring in the Google Maps API. Remember to use your own key that correlates to your domain name. Following this, we use Cake’s JavaScript inclusion method on lines 18 and 19 to include the Prototype and script.aculo.us JavaScript libraries. We also include our site-wide JavaScript file oepa*fo, located in +]ll+sa^nkkp+fo+. Within the main ^k`u tag, the page starts off with a simple header followed by a navigation bar using Cake’s HTML helper to create the HTML links, as follows: Ê
UÊ Dkia: This simply takes a user back to the home page.
Ê
UÊ NapnearaFkqnjau: Allows a user to retrieve a saved trip from the database.
Ê
UÊ @eolh]uFkqnjau: A user can display any journey.
To finish, we echo the _kjpajp[bkn[h]ukqp variable on line 49. This takes the rendered controller action view content and inserts it into that position.
Home Page Now that the global layout has been set up, we can create our home page view. We’ll use Cake’s designated default home page dkia*_pl, in the folder +]ll+reaso+l]cao+*, shown in Listing 5-10. In our application, users must be able to plot out a journey quite easily. On the home page, we will create a form where users can create their journeys the moment they enter the site.
143
144
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
Listing 5-10. The Default View (/app/views/pages/home.ctp) -68le`9fkqnjau[dahlan[iaoo]ca: .6Ukq_qnnajphud]rajkfkqnjaulh]jja`( /6ajpan]op]npejchk_]pekjpk^acej* 068+l: 16 268`er: 36 468l: 56Hk_]pekjJ]ia6 -,6 --68;ldla_dkbkni):patp$#Hk_]pekjJ]ia#%7;: -.68;ldla_dkbkni):^qppkj$#Ajpan]Hk_]pekj#( -/6]nn]u$#e`#9:#hk_]pekj[j]ia[^qppkj#%%7 -06;: -168+l: -26 -368+`er: -46 -568`ere`9i]l[bqj_pekjo[sn]llan: .,6 .-68))bej`pda^aopnkqpa)): ..68;ldl ./6a_dkbkni):^qppkj$#?]h_qh]paFkqnjau#( .06]nn]u$#e`#9:#bej`[nkqpa[^qppkj#%%7 .16;: .26 .368))op]np]jasnkqpa)): .468;ldl .56a_dkbkni):^qppkj$#Op]np=c]ej#( /,6]nn]u$#e`#9:#op]np[]c]ej[^qppkj#%%7 /-6;: /.6 //68))o]rapdankqpa)): /068;ldl /16a_dkbkni):_na]pa$jqhh(]nn]u$#e`#9:#]``[bkni#( /26#qnh#9:#+fkqnjauo+]``[bkni#( /36#_h]oo#9:#bqj_pekj[bkni# /46%%7 /56 0,6++qoa`pkopknapdahk_]pekjejfokjbkni]p 0-6a_dkbkni):de``aj$#hk_]pekjo#%7 0.6 0/6a_dkbkni):oq^iep$#O]raFkqnjau#(]nn]u$#`er#9:b]hoa%%7 006 016a_dkbkni):aj`$%7
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
026;: 036 0468+`er: 056 1,68))PdeoeosdanakqnCkkchaI]lsehh^a`eolh]ua`)): 1-68`ere`9i]l[_]jr]o:8+`er: In Listing 5-10, we haven’t included any form-posting elements apart from the Save Journey button on line 43. All of the other buttons relate entirely to client-side code and will be used in conjunction with the Google Maps functionality. The first button, Enter a Location, on line 12, will save and display the location in Google Maps locally. The Calculate Journey button, on line 23, will use the Google Maps API and JavaScript to calculate the shortest journey. The Start Again button, on line 29, will clear the slate for a new journey to be entered. When this home page code is processed with the default layout, we get the output shown in Figure 5-5 (with Paris set as the starting location in this example). The code and functionality behind all these buttons will be explained in the next section.
Figure 5-5. The application home page
145
146
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
Travel Mappr Manager Now we’re going to talk about the Travel Mappr manager class in +]ll+sa^nkkp+fo+oepa*fo. The class will be called Pn]rahI]llnI]j]can, and it will handle all the client-side functionality relating to the application. oepa*fo is called from within the default layout `ab]qhp*_pl, as shown on line 21 in Listing 5-9. We use Prototype’s API to help us with the creation of the class itself.
NNote You may have noticed that Listing 5-10 doesn’t contain any kj_he_g events. If you are used to writing kj_he_g or kj_d]jca events within HTML code, consider using Prototype’s Arajp*k^oanra method instead. It allows you to separate any JavaScript code from the content.
Listing 5-11 shows the main skeleton structure of our Pn]rahI]llnI]j]can class in the oepa*fo file. We have purposely left out many other functions, as commented on line 75. These will be described individually. Listing 5-11. The Main JavaScript File Used in Our Application (/app/webroot/js/site.js) -6r]nPn]rahI]llnI]j]can9?h]oo*_na]pa$w .6 /6++e`kb_kjp]ejan 06i]l[_kjp]ejan6##( 16 26+&pda_qnnajpi]l&+ 36i]l6jqhh( 46 56+&cak_k`ejchk_]pekj&+ -,6cak_k`an6jqhh( --6 -.6+&qoanajpana`hk_]pekjo&+ -/6qoan[fkqnjau6jas=nn]u$%( -06 -16ejepe]heva6bqj_pekj$i]l[_kjp]ejan%w -26 -36pdeo*i]l[_kjp]ejan9i]l[_kjp]ejan7 -46 -56++op]nppdai]l .,6Arajp*k^oanra$sej`ks(#hk]`#(pdeo*`eolh]uI]l*^ej`$pdeo%%7 .-6 ..6++k^oanrapdai]l^qppkjo ./6Arajp*k^oanra$`k_qiajp(#`ki6hk]`a`#(£ pdeo*ejepK^oanrano*^ej`$pdeo%%7 .06 .16y( .26 .36`eolh]uI]l6bqj_pekj$%w .46
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
.56eb$C>nksoanEo?kil]pe^ha$%%w /,6 /-6eb$ $pdeo*i]l[_kjp]ejan%%w /.6 //6++_na]pa]i]lejop]j_a /06pdeo*i]l9jasCI]l.$ $pdeo*i]l[_kjp]ejan%%7 /16 /26++]``i]l_kjpnkho /36pdeo*i]l*]``?kjpnkh$jasCH]ncaI]l?kjpnkh$%%7 /46pdeo*i]l*]``?kjpnkh$jasCI]lPula?kjpnkh$%%7 /56 0,6++_ajpanpdai]l]p]_anp]ejhk_]pekj 0-6pdeo*i]l*oap?ajpan$jasCH]pHjc$04*41212(.*/1,53%(4%7 0.6 0/6++oapql]cak_k`ejcejop]j_a 006pdeo*cak_k`an9jasC?heajpCak_k`an$%7 016 026++qjhk]`i]l 036Arajp*k^oanra$sej`ks(#qjhk]`#(CQjhk]`%7 046y 056y 1,6y( 1-6 1.6ejepK^oanrano6bqj_pekj$%w 1/6 106eb$ $#hk_]pekj[j]ia[^qppkj#%%w 116 $#hk_]pekj[j]ia[^qppkj#%*k^oanra$ 126#_he_g#(pdeo*bej`Hk_]pekj*^ej`=oArajpHeopajan$pdeo%%7 136y 146 156eb$ $#bej`[nkqpa[^qppkj#%%w 2,6 $#bej`[nkqpa[^qppkj#%*k^oanra$ 2-6#_he_g#(pdeo*bej`>aopFkqnjau*^ej`=oArajpHeopajan$pdeo%%7 2.6y 2/6 206eb$ $#op]np[]c]ej[^qppkj#%%w 216 $#op]np[]c]ej[^qppkj#%*k^oanra$ 226#_he_g#(pdeo*op]np=c]ej*^ej`=oArajpHeopajan$pdeo%%7 236y 246 256eb$ $#]``[bkni#%%w 3,6 $#]``[bkni#%*k^oanra$#oq^iep#( 3-6pdeo*o]raFkqnjau*^ej`=oArajpHeopajan$pdeo%%7 3.6y 3/6y( 306 316++kpdanbqj_pekjo* 326%
147
148
C HAPTER 5 N GO O G L E MA P S A ND THE TR A VEL ING S A LE S M A N
At the beginning of Listing 5-11 are some variables to hold various journey-related values. When ?h]oo*_na]pa is called, the ejepe]heva method on line 15 is called. This loads and displays the map and also starts observing the map buttons we have created. Once the DOM has been fully loaded using the `ki6hk]`a` event on line 23, the buttons get the _he_g event attached to them. The bej`Hk_]pekj$% function will locate the place in Google Maps and display it in the map canvas and also save the details locally in a JavaScript variable. The bej`>aopFkqnjau$%function will carry out the algorithm for finding the shortest journey using the shortest neighbor algorithm. The op]np=c]ej$%function will clear reset variables and allow a user to enter a new journey. Finally, once a journey has been plotted, the o]raFkqnjau$% function will observe the submit event in the save journey form and post the journey details to the server for saving. In the following sections, we’ll describe each of the missing functions in Listing 5-11, starting with the function that helps us to find a Google map location.
Finding Locations When a user enters a location name and clicks the Enter a Location button, the bej`Hk_]pekj$% function, shown in Listing 5-12, is fired. Listing 5-12. JavaScript to Find a Google Map Location -6bej`Hk_]pekj6bqj_pekj$%w .6 /6hk_[j]ia9 $#Hk_]pekjJ]ia#%*r]hqa7 06 16eb$hk_[j]ia99##%w 26]hanp$Lha]oaajpan]hk_]pekjj]ia*%7 36napqnj7 46y 56 -,6++sakjhu]hhks]i]teiqijqi^ankbhk_]pekjo --6eb$pdeo*qoan[fkqnjau*hajcpd:9.,%w -.6]hanp$OknnuSad]rana]_da`pdai]teiqi -/6jqi^ankbhk_]pekjo*%7 -06napqnj7 -16y -26 -36++`kcak_k`ejc7bej`pdahkjcepq`a]j`h]pepq`akbpdahk_]pekj -46eb$pdeo*cak_k`an%w -56 .,6r]n_qnnajp[k9pdeo7 .-6 ..6pdeo*cak_k`an*capH]pHjc$ ./6hk_[j]ia( .06bqj_pekj$lkejp%w .16
C HA P TER 5 N G O O G LE M A P S A N D T H E T R A V E LI N G S A LE S M A N
.26eb$lkejp%w .36]hanp$hk_[j]ia'jkpbkqj`%7 .46yahoaw .56 /,6++op