Cakephp Manual
Cakephp Manual
Table of Contents
Preface .............................................................................................................. x 1. Audience ................................................................................................................ x 2. CakePHP is Free .................................................................................................... x 3. Community ............................................................................................................. x 1. Introduction to CakePHP ................................................................................. 1 1.1. What is CakePHP? ............................................................................................... 1 1.2. Why CakePHP? ................................................................................................... 1 1.3. History of CakePHP ............................................................................................. 1 2. Basic Concepts ............................................................................................... 2 2.1. The MVC Pattern ................................................................................................. 2 2.2. Overview of the Cake File Layout .......................................................................... 2 3. Installing CakePHP ......................................................................................... 4 3.1. Requirements ...................................................................................................... 4 3.1.1. Server Requirements ......................................................................................... 4 3.2. Installing CakePHP .............................................................................................. 4 3.2.1. Getting the most recent stable version ................................................................ 4 3.2.2. Unpacking ......................................................................................................... 4 3.3. Setting Up CakePHP ............................................................................................ 4 3.3.1. Development Setup ........................................................................................... 5 3.3.2. Production Setup ............................................................................................... 5 3.3.3. Advanced Setup: Alternative Installation Options ................................................. 6 3.4. Configuring Apache and mod_rewrite .................................................................... 8 3.5. Make Sure It's Working ......................................................................................... 8 4. Configuration .................................................................................................. 9 4.1. Database Configuration ........................................................................................ 9 4.2. Global Configuration ........................................................................................... 10 4.3. Routes Configuration .......................................................................................... 10 5. Scaffolding ................................................................................................... 13 5.1. Cake's Scaffolding is Pretty Cool ......................................................................... 13 5.2. Customizing Scaffold Views ................................................................................ 13 6. Models ......................................................................................................... 15 6.1. Model Functions ................................................................................................. 15 6.1.1. User-Defined Functions ................................................................................... 15 6.1.2. Retrieving Your Data ....................................................................................... 16 6.1.3. Saving Your Data ............................................................................................ 19 6.1.4. Model Callbacks .............................................................................................. 20 6.2. Model Variables ................................................................................................. 22 6.3. Associations ...................................................................................................... 22 6.3.1. Introduction ..................................................................................................... 22 6.3.2. Defining and Querying with hasOne .................................................................. 23 6.3.3. Defining and Querying with belongsTo .............................................................. 25 6.3.4. Defining and Querying with hasMany ................................................................ 26 6.3.5. Defining and Querying with hasAndBelongsToMany .......................................... 28 6.3.6. Saving hasAndBelongsToMany ........................................................................ 32 7. Controllers .................................................................................................... 34 7.1. Controller Functions ........................................................................................... 34 7.1.1. Interacting with your Views ............................................................................... 35 7.1.2. User Redirection ............................................................................................. 35 7.1.3. Controller Callbacks ........................................................................................ 35 7.1.4. Other Useful Functions .................................................................................... 36 7.2. Controller Variables ............................................................................................ 38 7.3. Controller Parameters ........................................................................................ 39 8. Views ........................................................................................................... 41 8.1. Views ................................................................................................................ 41 8.1.1. Layouts ........................................................................................................... 41 8.1.2. Elements ........................................................................................................ 42 iv
CakePHP
9. Helpers ........................................................................................................ 44 9.1. Helpers .............................................................................................................. 44 9.1.1. HTML ............................................................................................................. 44 9.1.2. AJAX .............................................................................................................. 49 9.1.3. Javascript ....................................................................................................... 54 9.1.4. Number .......................................................................................................... 55 9.1.5. Text ................................................................................................................ 55 9.1.6. Time ............................................................................................................... 56 9.1.7. Cache ............................................................................................................. 58 9.2. Creating Your Own Helpers ................................................................................ 58 9.2.1. Extending the Cake Helper Class ..................................................................... 58 9.2.2. Including other Helpers .................................................................................... 59 9.2.3. Using your Custom Helper ............................................................................... 60 9.2.4. Contributing .................................................................................................... 60 10. Cake's Global Constants And Functions ....................................................... 61 10.1. Global Functions .............................................................................................. 61 10.2. Global Constants .............................................................................................. 64 11. Data Validation ........................................................................................... 65 11.1. Data Validation ................................................................................................. 65 12. Access Control Lists .................................................................................... 68 12.1. Understanding How ACL Works ........................................................................ 68 12.2. Defining Permissions: Cake's INI-based ACL ..................................................... 71 12.3. Defining Permissions: Cake's Database ACL ..................................................... 71 12.3.1. Getting Started .............................................................................................. 71 12.3.2. Creating Access Request Objects (AROs) and Access Control Objects (ACOs) 72 12.3.3. Assigning Permissions ................................................................................... 73 12.4. Checking Permissions: The ACL Component ..................................................... 74 13. Data Sanitization ......................................................................................... 76 13.1. Using Sanitize in Your Application ..................................................................... 76 13.2. Making Data Safe for use in SQL and HTML ...................................................... 76 14. The Cake Session Component ..................................................................... 78 14.1. Cake Session Storage Options .......................................................................... 78 14.2. Using the Cake Session Component .................................................................. 78 15. The Request Handler Component ................................................................ 80 15.1. Introduction ...................................................................................................... 80 15.2. Getting Client/Request Information .................................................................... 80 15.3. Stripping Data .................................................................................................. 82 15.4. Other Useful Functions ..................................................................................... 83 16. The Security Component ............................................................................. 84 16.1. Introduction ...................................................................................................... 84 16.2. Protecting Controller Actions ............................................................................. 84 16.3. Handling Invalid Requests ................................................................................. 85 16.4. Advanced Request Authentication ..................................................................... 85 17. View Caching .............................................................................................. 86 17.1. What is it ? ....................................................................................................... 86 17.2. How Does it Work ? .......................................................................................... 86 17.2.1. Activating the cache ....................................................................................... 86 17.2.2. The $cacheAction Controller Variable ............................................................. 86 17.2.3. Marking Content in the View ........................................................................... 87 17.2.4. Clearing the cache ......................................................................................... 87 17.3. Things To Remember ....................................................................................... 88 A. The Cake Blog Tutorial ................................................................................. 89 A.1. Introduction ....................................................................................................... 89 A.2. Getting Cake ..................................................................................................... 89 A.3. Creating the Blog Database ................................................................................ 90 A.4. Cake Database Configuration ............................................................................. 90 A.5. A Note On mod_rewrite ...................................................................................... 91 A.6. Create a Post Model .......................................................................................... 91 A.7. Create a Posts Controller ................................................................................... 92 A.8. Creating Post Views ........................................................................................... 93 A.9. Adding Posts ..................................................................................................... 95 v
CakePHP
A.10. Data Validation ................................................................................................ 96 A.11. Deleting Posts ................................................................................................. 98 A.12. Editing Posts ................................................................................................... 99 A.13. Routes .......................................................................................................... 100 A.14. Conclusion .................................................................................................... 101 B. Example: Simple User Authentication .......................................................... 102 B.1. The Big Picture ................................................................................................ 102 B.2. Authentication and Persistence ......................................................................... 102 B.3. Access Checking in your Application ................................................................. 105 C. Cake Naming Conventions .......................................................................... 107 C.1. Models ............................................................................................................ 107 C.2. Controllers ...................................................................................................... 107 C.3. Views .............................................................................................................. 107 D. Plugins ...................................................................................................... 109 E. Inflector (Custom Inflections) ....................................................................... 110
vi
List of Tables
6.1. Model::recursive options ............................................................................. 22
vii
List of Examples
3.1. Suggested Production httpd.conf ................................................................... 5 3.2. /app/webroot/index.php (partial, comments removed) ..................................... 6 4.1. app/config/database.php ............................................................................... 9 4.2. Route Pattern ............................................................................................. 10 4.3. Route Example .......................................................................................... 11 4.4. Route Handling in a Controller .................................................................... 11 4.5. Setting the Default Route ............................................................................ 12 5.1. Custom Scaffolding Views for a Single Controller ......................................... 13 5.2. Custom Scaffolding Views for an Entire Application ...................................... 14 6.1. Example User Model, saved in /app/models/user.php ................................... 15 6.2. Example Model Functions ........................................................................... 16 6.3. Custom Sql Calls with query() ..................................................................... 18 6.4. /app/models/user.php hasOne .................................................................... 23 6.5. /app/models/profile.php belongsTo .............................................................. 25 6.6. /app/models/user.php hasMany ................................................................... 26 6.7. HABTM Join Tables: Sample models and their join table names .................... 29 6.8. /app/models/post.php hasAndBelongsToMany ............................................. 30 6.9. /app/views/posts/add.thtml Form for creating posts ....................................... 32 6.10. /app/views/posts/add.thtml (Tag association code added) ........................... 32 8.1. Calling an Element without parameters ........................................................ 42 8.2. Calling an Element passing a data array ...................................................... 42 9.1. Edit Action inside of the NotesController ...................................................... 46 9.2. Edit View code (edit.thtml) sample ............................................................... 46 9.3. Concatenating time data before saving a model (excerpt from NotesController) ......................................................................................................................... 48 9.4. AjaxHelper $options Keys ........................................................................... 49 9.5. /app/views/helpers/link.php ......................................................................... 58 9.6. /app/views/helpers/link.php (logic added) ..................................................... 59 9.7. /app/views/helpers/link.php (using other helpers) .......................................... 59 11.1. /app/models/user.php ............................................................................... 65 11.2. Form-handling Action in /app/models/blog_controller.php ............................ 65 11.3. The add form view in /app/views/blog/add.thtml .......................................... 66 12.1. Initializing your database using acl.php ...................................................... 72 14.1. core.php Session Configuration ................................................................. 78 17.1. /app/config/core.php (partial) ..................................................................... 86 17.2. $cacheAction Examples ............................................................................ 86 17.3. <cake:nocache> exemple ......................................................................... 87 17.4. <cake:nocache> exemple ......................................................................... 87 A.1. /app/models/post.php ................................................................................. 91 A.2. /app/controllers/posts_controller.php ........................................................... 92 A.3. /app/controllers/posts_controller.php (index action added) ............................ 92 A.4. /app/views/posts/index.thtml ....................................................................... 93 A.5. /app/controllers/posts_controller.php (view action added) ............................. 94 A.6. /app/views/posts/view.thtml ........................................................................ 95 A.7. /app/controllers/posts_controller.php (add action added) .............................. 95 A.8. /app/views/posts/add.thtml ......................................................................... 96 A.9. /app/models/post.php (validation array added) ............................................. 97 A.10. /app/controllers/posts_controller.php (delete action only) ............................ 98 A.11. /app/views/posts/index.thtml (add and delete links added) .......................... 98 A.12. /app/controllers/posts_controller.php (edit action only) ................................ 99 A.13. /app/views/posts/edit.thtml ........................................................................ 99 A.14. /app/views/posts/index.thtml (edit links added) ......................................... 100 B.1. Table 'users', Fictional Client Management System Database ..................... 103 B.2. /app/views/users/login.thtml ...................................................................... 103 B.3. /app/controllers/users_controller.php (partial) ............................................. 103 B.4. /app/app_controller.php ............................................................................ 105 B.5. Forcing authentication before all actions in a controller ............................... 105 viii
CakePHP
ix
Preface
1. Audience
This manual is written for people who want to build web applications faster and more enjoyably. CakePHP aims to assist PHP users of all levels to create robust, maintainable applications quickly and easily. This manual expects a basic knowledge of PHP and HTML. A familiarity with the ModelView-Controller programming pattern is helpful, but we will cover that along the way for those new to MVC. While this work will attempt to aid the reader in configuring and troubleshooting their web server, a full coverage of such issues is outside the scope of this manual. Rudimentary knowledge in first aid (CPR), basic survival skills, dating & relationships is also generally recommended, though also outside the scope of this document.
2. CakePHP is Free
CakePHP is free. You don't have to pay for it, you can use it any way you want. CakePHP is developed under the MIT License [http://www.opensource.org/licenses/mit-license.php]. CakePHP is an Open Source project, this means you have full access to the source code. The best place to get the most recent version is at the CakePHP web site (http://www.cakephp.org). You can also browse the latest and greatest code there.
3. Community
CakePHP is developed by a hard working community of people. They come from different countries all over the world and joined together to create the CakePHP framework for the benefit of the widest audience possible. For more information about Cake's active developer and user communities, visit http://www.cakephp.org. Our IRC channel is always filled with knowledgable, friendly Bakers. If you're stuck on a bit of code, need a listening ear, or want to start an argument about coding conventions, drop on by: we'd love to hear from you. Visit us at #cakephp on irc.freenode.com.
10. View Helpers for AJAX, Javascript, HTML Forms and more 11. Security, Session, and Request Handling Components 12. Flexible access control lists 13. Data Sanitization 14. Flexible View Caching 15. Works from any web site subdirectory, with little to no Apache configuration involved
Basic Concepts
need to worry about overwriting something you wrote for your app. You can use the vendors directory to keep third-party libraries in. You will learn more about vendors later, but the basic idea is that you can access classes you've placed in the vendors directory using Cake's vendor() function. Let's look at the entire file layout: /app /config ACL, etc. /controllers /components /index.php DocumentRoot /models /plugins /tmp /vendors application /views /elements /errors /helpers /layouts /pages /webroot /css /files /img /js /cake here. index.php /vendors VERSION.txt using. - Used for server-wide third-party libraries. - Let's you know what version of Cake you're - Contains config files for your database, - Controllers go here - Components go here - Allows you to deploy cake with /app as the - Models go here - Plugins go here - Used for caches and logs - Contains third-party libaries for this Views go here Elements, little bits of views, go here Your custom error pages go here Helpers go here Application layout files go here Static views go here
3.1. Requirements
In order use CakePHP you must first have a server that has all the required libraries and programs to run CakePHP:
1. 2. 3.
An HTTP server (like Apache) with the following enabled: sessions, mod_rewrite (not absolutely necessary but preferred) PHP 4.3.2 or greater. Yes, CakePHP works great in either PHP 4 or 5. A database engine (right now, there is support for MySQL, PostgreSQL and a wrapper for ADODB).
3.2.2. Unpacking
Now that you've downloaded the most recent release, place that compressed package on your web server in the webroot. Now you need to unpack the CakePHP package. There are two ways to do this, using a development setup, which allows you to easily view many CakePHP applications under a single domain, or using the production setup, which allows for a single CakePHP application on the domain.
Installing CakePHP
The first way to setup CakePHP is generally only recommended for development environments because it is less secure. The second way is considered more secure and should be used in a production environment.
Note
NOTE: /app/tmp must be writable by the user that your web server runs as.
In this setup the wwwroot folder acts a the web root so your URLs will look something like this (if you're also using mod_rewrite):
www.example.com/cake/controllerName/actionName/param1/param2
Installing CakePHP
DocumentRoot /path_to_cake/app/webroot
In this setup the webroot directory is acting as the web root so your URLs might look like this (if you're using mod_rewrite): http://www.example.com/controllerName/actionName/param1/param2
Each of these directories can be located anywhere on your file system, with the exception of the webroot, which needs to be accessible by your web server. You can even move the webroot folder out of the app folder as long as you tell Cake where you've put it. To configure your Cake installation, you'll need to make some changes to / app/webroot/index.php (as it is distributed in Cake). There are three main defines that you'll need to edit: ROOT, APP_DIR, and CAKE_CORE_INCLUDE PATH. ROOT should be set to the path of the directory that contains your app folder. APP_DIR should be set to the path of your app folder. CAKE_CORE_INCLUDE_PATH should be set to the path of your Cake libraries folder.
Installing CakePHP
An example might help illustrate this better. Imagine that I wanted to set up Cake to work with the following setup: I want my Cake /usr/lib/cake. libraries shared with other applications, and placed in
My Cake webroot directory needs to be /var/www/mysite/. My application files will be stored in /home/me/mysite.
Here's what the file setup looks like: /home /me /mysite /config /controllers /index.php /models /plugins /tmp /vendors /views /var /www /mysite <-- Used to be /cake_install/app/webroot /.htaccess /css /css.php /favicon.ico /files /img /index.php /js /usr /lib /cake <-- Used to be /cake_install/cake /cake /app_controller.php /app_model.php /basics.php /bootstrap.php /config /dispatcher.php /docs /libs /scripts /vendors <-- Used to be /cake_install/app
Given this type of setup, I would need to edit my webroot index.php file (which should be at /var/www/mysite/index.php, in this example) to look like the following:
Note
It is recommended to use the 'DS' constant rather than slashes to delimit file paths. This prevents any 'missing file' errors you might get as a result of using the wrong delimiter, and it 7
Installing CakePHP
makes your code more portable. if (!defined('ROOT')) { define('ROOT', DS.'home'.DS.'me'); } if (!defined('APP_DIR')) { define ('APP_DIR', 'mysite'); } if (!defined('CAKE_CORE_INCLUDE_PATH')) { define('CAKE_CORE_INCLUDE_PATH', DS.'usr'.DS.'lib'.DS.'cake'); }
Chapter 4. Configuration
4.1. Database Configuration
Your app/config/database.php file is where your database configuration all takes place. A fresh install doesn't have a database.php, so you'll need to make a copy of database.php.default. Once you've made a copy and renamed it you'll see the following:
Replace the information provided by default with the database connection information for your application. CakePHP supports the following database drivers: mysql postgres sqlite pear-drivername (so you might enter pear-mysql, for example) adodb-drivername The 'connect' key in the $default connection allows you to specify whether or not the database connection will be treated as persistent or not. Read the comments in the database.php.default file for help on specifying connection types for your database setup. Your database tables should also follow the following conventions: Table names used by Cake should consist of English words in plural, like "users", "authors" or "articles". Note that corresponding models have singular names. Your tables must have a primary_key named 'id'. If you plan to relate tables, use foreign keys that look like: 'article_id'. The table name is singular, followed by an underscore, followed by 'id'. If you include a 'created' and/or 'modified' column in your table, Cake will automatically populate the field when appropriate.
You'll also notice that there is also a $test connection setting included in the database.php file. Fill out this configuration (or add other similarly formatted configurations) and use it in your application by placing something like: var $useDbConfig = 'test';
Configuration
Inside one of your models. You can add any number of additional connection settings in this manner.
CAKE_SESSION_SAVE: Specify how you'd like session data saved. Possible values are: cake: Session data is saved in tmp/ inside your Cake installation php: Session data saved as defined in php.ini database: Session data saved to database connection defined by the 'default' key.
10
Configuration
Where: URL is the regular expression Cake URL you wish to map, controllername is the name of the controller you wish to invoke, actionname is the name of the controller's action you wish to invoke, and firstparam is the value of the first parameter of the action you've specified. Any parameters following firstparam will also be passed as parameters to the controller action. The following example joins all the urls in /blog to the BlogController. The default action will be BlogController::index().
The 'history' from the URL was matched by :action from the Blog's route. URL elements matched by * are passed to the active controller's handling method as parameters, hence the $year and $month. Called with URL /blog/history/05, history() would only be passed one parameter, 05. The following example is a default CakePHP route used to set up a route for PagesController::display('home'). Home is a view which can be overridden by creating the file / app/views/pages/home.thtml.
11
Configuration
12
Chapter 5. Scaffolding
5.1. Cake's Scaffolding is Pretty Cool
So cool that you'll want to use it in production apps. Now, we think its cool, too, but please realize that scaffolding is... well... just scaffolding. Its a bunch of stuff you throw up real quick during the beginning of a project in order to get started. It isn't meant to be completely flexible. So, if you find yourself really wanting to customize your logic and your views, its time to pull your scaffolding down in order to write some code. Scaffolding is a great way of getting the early parts of developing a web application started. Early database schema's are volatile and subject to change, which is perfectly normal in the early part of the design process. This has a downside: a web developer hates creating forms that never will see real use. To reduce the strain on the developer, scaffolding has been included in Cake. Scaffolding analyzes your database tables and creates standard lists with add, delete and edit buttons, standard forms for editing and standard views for inspecting a single item in the database. To add scaffolding for your application, in the controller, add the $scaffold variable: <?php class CategoriesController extends AppController { var $scaffold; } ?>
One important thing to note about scaffold: it expects that any field name that ends with _id is a foreign key to a table which has a name that precedes the underscore. So, for example, if you have nested categories, you'd probably have a column called parent_id. With this release, it would be best to call this parentid. Also, when you have a foreign key in your table (e.g. titles table has category_id), and you have associated your models appropriately (see Understanding Associations, 6.2), a select box will be automatically populated with the rows from the foreign table (category) in the show/edit/new views. To set which field in the foreign table is shown, set the $displayField variable in the foreign model. To continue our example of a category having a title: <?php class Title extends AppModel { var $name = 'Title'; var $displayField = 'title'; } ?>
Scaffolding
Custom scaffolding views for a PostsController should be placed like so: /app/views/posts/scaffold/index.scaffold.thtml /app/views/posts/scaffold/show.scaffold.thtml /app/views/posts/scaffold/edit.scaffold.thtml /app/views/posts/scaffold/new.scaffold.thtml
If you find yourself wanting to change the controller logic at this point, its time to take the scaffolding down from your application and start building it. One feature you might find helpful is Cake's code generator: Bake. Bake allows you to generate a coded version of scaffolded code you can then move on to modify and customize as your application requires.
14
Chapter 6. Models
What is a model? It is the M of the MVC pattern. What does it do? It separates domain logic from the presentation, isolating application logic. A model is generally an access point to the database, and more specifically, to a certain table in the database. By default, each model uses the table who's name is plural of its own, i.e. a 'User' model uses the 'users' table. Models can also contain data validation rules, association information, and methods specific to the table it uses. Here's what a simple User model might look like in Cake:
Important
While this section will treat most of the oft-used functions in Cake's Model, its important to remember to use http://api.cakephp.org for a full reference.
Models
An example of a table-specific method in the model is a pair of methods for hiding/unhiding posts in a blog.
Models
When the $recursive option is set to a integer value between 1 and 3, the find() operation will make an effort to return the models associated to the ones found by the find(). The recursive find can go up to three levels deep. If your property has many owners who in turn have many contracts, a recursive find() on your Property model will return up to three levels deep of associated models. findAllBy<fieldName>($value); string $value; These magic functions can be used as a shortcut to search your tables for a row given a certain field, and a certain value. Just tack on the name of the field you wish to search, and CamelCase it. Examples (as used in a Controller) might be: $this->Post->findByTitle('My First Blog Post'); $this->Author->findByLastName('Rogers'); $this->Property->findAllByState('AZ'); $this->Specimen->findAllByKingdom('Animalia');
The returned result is an array formatted just as would be from find() or findAll(). findNeighbours($conditions, $field, $value); string $conditions; array $field; string $value; Returns an array with the neighboring models (with only the specified fields), specified by $field and $value, filtered by the SQL conditions, $conditions. This is useful in situations where you want 'Previous' and 'Next' links that walk users through some ordered sequence through your model entries. It only works for numeric and date based fields. class ImagesController extends AppController { function view($id) { // Say we want to show the image... $this->set('image', $this->Image->find("id = $id"); // But we also want the previous and next images... $this->set('neighbours', $this->Image->findNeighbours(null, 'id', $id); } }
This gives us the full $image['Image'] array, along with $neighbours['prev']['Image']['id'] and $neighbours['next']['Image']['id'] in our view. field($name, $conditions, $order); string $name; string $conditions; string $order; Returns as a string a single field from the first record matched by $conditions as ordered by $order.
17
Models
findCount($conditions); string $conditions; Returns the number of records that match the given conditions. generateList($conditions, $order, $limit, $keyPath, $valuePath); string $conditions; string $order; int $limit; string $keyPath; string $valuePath; This function is a shortcut to getting a list of key value pairs from list of your models - especially handy for creating a html select tag from a list of your models. Use the $conditions, $order, and $limit parameters just as you would for a findAll() request. The $keyPath and $valuePath are where you tell the model where to find the keys and values for your generated list. For example, if you wanted to generate a list of roles based on your Role model, keyed by their integer ids, the full call might look something like: $this->set( 'Roles', $this->Role->generateList(null, 'role_name ASC', null, 'id', 'role_name') ); //This would return something like: array( '1' => 'Account Manager', '2' => 'Account Viewer', '3' => 'System Manager', '4' => 'Site Visitor' );
read($fields, $id); string $fields; string $id; Use this function to get the fields and their values from the currently loaded record, or the record specified by $id. Please note that read() operations will only fetch the first level of associated models regardless of the value of $recursive in the model. To gain additional levels, use find() or findAll(). query($query); string $query; execute($query); string $query; Custom SQL calls can be made using the model's query() and execute() methods. The difference between the two is that query() is used to make custom SQL queries (the results of which are returned), and execute() is used to make custom SQL commands (which require no return value).
Models
<?php class Post extends AppModel { var $name = 'Post'; function posterFirstName() { $ret = $this->query("SELECT first_name FROM posters_table WHERE poster_id = 1"); $firstName = $ret[0]['first_name']; return $firstName; } } ?>
In order to get your data posted to the controller in this manner, its easiest just to use the HTML Helper to do this, because it creates form elements that are named in the way Cake expects. You don't need to use it however: just make sure your form elements have names that look like data[Modelname][fieldname]. Using $html->input('Model/fieldname') is the easiest, however. Data posted from forms is automatically formatted like this and placed in $this->data inside of your controller, so saving your data from web forms is a snap. An edit function for a property controller might look something like the following: function edit($id) { //Note: The property model is automatically loaded for us at $this->Property. // Check to see if we have form data... if (empty($this->data)) { $this->Property->id = $id; $this->data = $this->Property->read();//populate the form fields with the current row } else { // Here's where we try to save our data. Automagic validation checking if ($this->Property->save($this->data['Property'])) { //Flash a message and redirect. $this->flash('Your information has been saved.', 19
Models
'/properties/view/'.$this->data['Property']['id'], 2); } //if some fields are invalid or save fails the form will render } }
Notice how the save operation is placed inside a conditional: when you try to save data to your model, Cake automatically attempts to validate your data using the rules you've provided. To learn more about data validation, see Chapter 10. If you do not want save() to try to validate your data, use save($data, false). Other useful save functions: del($id, $cascade); string $id; boolean $cascade; Deletes the model specified by $id, or the current id of the model. If this model is associated to other models, and the 'dependent' key has been set in the association array, this method will also delete those associated models if $cascade is set to true. Returns true on success. saveField($name, $value); string $name; string $value; Used to save a single field value. getLastInsertId(); ; Returns the ID of the most recently created record.
Models
; Use this callback to modify model data before it is validated. It can also be used to add additional, more complex validation rules, using Model::invalidate(). In this context, model data is accessible via $this->data. This function must also return true, otherwise save( ) execution will abort. beforeSave(); ; Place any pre-save logic in this function. This function executes immediately after model data has been validated (assuming it validates, otherwise the save( ) call aborts, and this callback will not execute), but before the data is saved. This function should also return true if you want the save operation to continue, and false if you want to abort. One usage of beforeSave might be to format time data for storage in a specifc database engine: // Date/time fields created by HTML Helper: // This code would be seen in a view $html->dayOptionTag('Event/start'); $html->monthOptionTag('Event/start'); $html->yearOptionTag('Event/start'); $html->hourOptionTag('Event/start'); $html->minuteOptionTag('Event/start'); /*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-*/ // Model callback functions used to stitch date // data together for storage // This code would be seen in the Event model: function beforeSave() { $this->data['Event']['start'] = $this->_getDate('Event', 'start'); return true; } function _getDate($model, $field) { return date('Y-m-d H:i:s', mktime( intval($this->data[$model][$field intval($this->data[$model][$field null, intval($this->data[$model][$field intval($this->data[$model][$field intval($this->data[$model][$field }
afterSave(); ; Place any logic that you want to be executed after every save in this callback method. beforeDelete(); ; Place any pre-deletion logic in this function. This function should return trueif you want 21
Models
the deletion to continue, andfalse if you want to abort. afterDelete(); ; Place any logic that you want to be executed after every deletion in this callback method.
$transactional Tells Cake whether or not to enable transactions for this model (i.e. begin/commit/rollback). Set to a boolean value. Only available for supporting databases. $useTable If the database table you wish to use isn't the plural form of the model name (and you don't wish to change the table name), set this variable to the name of the table you'd like this model to use. $validate An array used to validate the data passed to this model. See chapter 10. $useDbConfig Remember the database settings you can configure in /app/config/database.php? Use this variable to switch between them - just use the name of the database connection variable you've created in your database configuration file. The default is, you guessed it, 'default'.
6.3. Associations
6.3.1. Introduction
22
Models
One of the most powerful features of CakePHP is the relational mapping provided by the model. In CakePHP, the links between tables are handled through associations. Associations are the glue between related logical units. There are four types of associations in CakePHP: hasOne hasMany belongsTo hasAndBelongsToMany
When associations between models have been defined, Cake will automagically fetch models related to the model you are working with. For example, if a Post model is related to an Author model using a hasMany association, making a call to $this->Post->findAll() in a controller will fetch Post records, as well as all the Author records they are related to. To use the association correctly it is best to follow the CakePHP naming conventions (see Appendix B). If you use CakePHP naming conventions, you can use scaffolding to visualize your application data, because scaffolding detects and uses the assocations between models. Of course you can always customize model associations to work outside of Cake's naming conventions, but we'll save those tips for later. For now, let's just stick to the conventions. The naming conventions that concern us here are the foreign keys, model names, and table names. Here's a review of what Cake expects for the names of these different elements: (see Appendix B for more information on naming) Foreign Keys: [singular model name]_id. For example, a foreign key in the "authors" table pointing back to the Post a given Author belongs to would be named "post_id". Table Names: [plural object name]. Since we'd like to store information about blog posts and their authors, the table names are "posts" and "authors", respectively. Model Names: [CamelCased, singular form of table name]. The model name for the "posts" table is "Post", and the model name for the "authors" table is "Author".
In order to illustrate how some of these associations work, let's continue using the blog application as an example. Imagine that we're going to create a simple user management system for the blog. I suppose it goes without saying we'll want to keep track of Users, but we'd also like each user to have an associated Profile (User hasOne Profile). Users will also be able to create comments and remain associated to them (User hasMany Comments). Once we have the user system working, we'll move to allowing Posts to be related to Tag objects using the hasAndBelongsToMany relationship (Post hasAndBelongsToMany Tags).
23
Models
<?php class User extends AppModel { var $name = 'User'; var $hasOne = array('Profile' => array('className' 'conditions' 'order' 'dependent' 'foreignKey' ) ); } ?>
The $hasOne array is what Cake uses to build the association between the User and Profile models. Each key in the array allows you to further configure the association: className (required): the classname of the model you'd like to associate For our example, we want to specify the 'Profile' model class name. conditions: SQL condition fragments that define the relationship We could use this to tell Cake to only associate a Profile that has a green header, if we wished. To define conditions like this, you'd specify a SQL conditions fragment as the value for this key: "Profile.header_color = 'green'". order: the ordering of the associated models If you'd like your associated models in a specific order, set the value for this key using an SQL order predicate: "Profile.name ASC", for example. dependent: if set to true, the associated model is destroyed when this one is. For example, if the "Cool Blue" profile is associated to "Bob", and I delete the user "Bob", the profile "Cool Blue" will also be deleted. foreignKey: the name of the foreign key that points to the associated model. This is here in case you're working with a database that doesn't follow Cake's naming conventions. Now, when we execute find() or findAll() calls using the Profile model, we should see our associated User model there as well: $user = $this->User->findById('25'); print_r($user); //output: Array ( [User] => Array ( [id] => 25 [first_name] => John [last_name] => Anderson [username] => psychic [pasword] => c4k3roxx ) 24
Models
[Profile] => Array ( [id] => 4 [name] => Cool Blue [header_color] => aquamarine [user_id] = 25 ) )
The $belongsTo array is what Cake uses to build the association between the User and Profile models. Each key in the array allows you to further configure the association: className (required): the classname of the model you'd like to associate For our example, we want to specify the 'User' model class name. conditions: SQL condition fragments that define the relationship We could use this to tell Cake to only associate a User that is active. You would do this by setting the value of the key to be "User.active = '1'", or something similar. order: the ordering of the associated models IF you'd like your associated models in a specific order, set the value for this key using an SQL order predicate: "User.last_name ASC", for example. foreignKey: the name of the foreign key that points to the associated model. This is here in case you're working with a database that doesn't follow Cake's naming conventions. Now, when we execute find() or findAll() calls using the Profile model, we should see our associated User model there as well: 25
Models
$profile = $this->Profile->findById('4'); print_r($profile); //output: Array ( [Profile] => Array ( [id] => 4 [name] => Cool Blue [header_color] => aquamarine [user_id] = 25 ) [User] => Array ( [id] => 25 [first_name] => John [last_name] => Anderson [username] => psychic [pasword] => c4k3roxx ) )
=> 'Comment', => 'Comment.moderated => 'Comment.created => => => => => '5', 'user_id', true, false, ''
// Here's the hasOne relationship we defined earlier... var $hasOne = array('Profile' => array('className' => 'Profile', 'conditions' => '', 'order' => '', 'dependent' => true, 'foreignKey' => 'user_id' ) ); } ?> 26
Models
The $hasOne array is what Cake uses to build the association between the User and Profile models. Each key in the array allows you to further configure the association: className (required): the classname of the model you'd like to associate For our example, we want to specify the 'User' model class name. conditions: SQL condition fragments that define the relationship We could use this to tell Cake to only associate a Comment that has been moderated. You would do this by setting the value of the key to be "Comment.moderated = 1", or something similar. order: the ordering of the associated models If you'd like your associated models in a specific order, set the value for this key using an SQL order predicate: "Comment.created DESC", for example. limit: the maximum number of associated models you'd like Cake to fetch. For this example, we didn't want to fetch *all* of the user's comments, just five. foreignKey: the name of the foreign key that points to the associated model. This is here in case you're working with a database that doesn't follow Cake's naming conventions. dependent: if set to true, the associated model is destroyed when this one is. For example, if the "Cool Blue" profile is associated to "Bob", and I delete the user "Bob", the profile "Cool Blue" will also be deleted. exclusive: If set to true, all the associated objects are deleted in one SQL statement without having their beforeBestroy callback run. Good for use for simpler associations, because it can be much faster. finderSql: Specify a complete SQL statement to fetch the association. This is a good way to go for complex associations that depends on multiple tables. If Cake's automatic assocations aren't working for you, here's where you customize it. Now, when we execute find() or findAll() calls using the User model, we should see our associated Comment models there as well: $user = $this->User->findById('25'); print_r($user); //output: Array ( [User] => Array ( [id] => 25 [first_name] => John [last_name] => Anderson [username] => psychic [pasword] => c4k3roxx ) [Profile] => Array ( 27
Models
[id] => 4 [name] => Cool Blue [header_color] => aquamarine [user_id] = 25 ) [Comment] => Array ( [0] => Array ( [id] => 247 [user_id] => 25 [body] => The hasMany assocation is nice to have. ) [1] => Array ( [id] => 256 [user_id] => 25 [body] => The hasMany assocation is really nice to have. ) [2] => Array ( [id] => 269 [user_id] => 25 [body] => The hasMany assocation is really, really nice to have. ) [3] => Array ( [id] => 285 [user_id] => 25 [body] => The hasMany assocation is extremely nice to have. ) [4] => Array ( [id] => 286 [user_id] => 25 [body] => The hasMany assocation is super nice to have. ) ) )
Note
While we won't document the process here, it would be a great idea to define the "Comment belongsTo User" association as well, so that both models can see each other. Not defining assocations from both models is often a common gotcha when trying to use scaffolding.
Models
Models that are linked together with a join table. The join table holds the individual rows that are related to each other. The difference between hasMany and hasAndBelongsToMany is that with hasMany, the associated model is not shared. If a User hasMany Comments, it is the *only* user associated to those comments. With HABTM, the associated models are shared. This is great for what we're about to do next: associate Post models to Tag models. When a Tag belongs to a Post, we don't want it to be 'used up', we want to continue to associate it to other Posts as well. In order to do this, we'll need to set up the correct tables for this association. Of course you'll need a "tags" table for you Tag model, and a "posts" table for your posts, but you'll also need to create a join table for this association. The naming convention for HABTM join tables is [plural model name1]_[plural model name2], where the model names are in alphabetical order:
Example 6.7. HABTM Join Tables: Sample models and their join table names
Posts and Tags: posts_tags Monkeys and IceCubes: ice_cubes_monkeys Categories and Articles: articles_categories
HABTM join tables need to at least consist of the two foreign keys of the models they link. For our example, "post_id" and "tag_id" is all we'll need. Here's what the SQL dumps will look like for our Posts HABTM Tags example: --- Table structure for table `posts` -CREATE TABLE `posts` ( `id` int(10) unsigned NOT NULL auto_increment, `user_id` int(10) default NULL, `title` varchar(50) default NULL, `body` text, `created` datetime default NULL, `modified` datetime default NULL, `status` tinyint(1) NOT NULL default '0', PRIMARY KEY (`id`) ) TYPE=MyISAM; -- ---------------------------------------------------------- Table structure for table `posts_tags` -CREATE TABLE `posts_tags` ( `post_id` int(10) unsigned NOT NULL default '0', `tag_id` int(10) unsigned NOT NULL default '0', PRIMARY KEY (`post_id`,`tag_id`) ) TYPE=MyISAM; -- ---------------------------------------------------------- Table structure for table `tags` 29
Models
-CREATE TABLE `tags` ( `id` int(10) unsigned NOT NULL auto_increment, `tag` varchar(100) default NULL, PRIMARY KEY (`id`) ) TYPE=MyISAM;
With our tables set up, let's define the association in the Post model:
The $hasAndBelongsToMany array is what Cake uses to build the association between the Post and Tag models. Each key in the array allows you to further configure the association: className (required): the classname of the model you'd like to associate For our example, we want to specify the 'User' model class name. joinTable: this is here for a database that doesn't adhere to Cake's naming conventions. If your table doesn't look like [plural model1]_[plural model2] in lexical order, you can specify the name of your table here. foreignKey: the name of the foreign key that points to the associated model. This is here in case you're working with a database that doesn't follow Cake's naming conventions. associationForeignKey: the name of the foreign key in the join table that points to the current model. conditions: SQL condition fragments that define the relationship We could use this to tell Cake to only associate a Comment that has been moderated. You would do this by setting the value of the key to be "Comment.moderated = 1", or something similar.
30
Models
order: the ordering of the associated models If you'd like your associated models in a specific order, set the value for this key using an SQL order predicate: "Comment.created DESC", for example.
limit: the maximum number of associated models you'd like Cake to fetch. For this example, we didn't want to fetch *all* of the user's comments, just five.
uniq: If set to true, duplicate associated objects will be ignored by accessors and query methods. Basically, if the associations are distinct, set this to true. That way the Tag "Awesomeness" can only be assigned to the Post "Cake Model Associations" once, and will only show up once in result arrays.
finderSql: Specify a complete SQL statement to fetch the association. This is a good way to go for complex associations that depends on multiple tables. If Cake's automatic assocations aren't working for you, here's where you customize it.
deleteQuery: A complete SQL statement to be used to remove assocations between HABTM models. If you don't like the way Cake is performing deletes, or your setup is customized in some way, you can change the way deletion works by supplying your own query here.
Now, when we execute find() or findAll() calls using the Post model, we should see our associated Tag models there as well: $post = $this->Post->findById('2'); print_r($post); //output: Array ( [Post] => Array ( [id] => 2 [user_id] => 25 [title] => Cake Model Associations [body] => Time saving, easy, and powerful. [created] => 2006-04-15 09:33:24 [modified] => 2006-04-15 09:33:24 [status] => 1 ) [Tag] => Array ( [0] => Array ( [id] => 247 [tag] => CakePHP ) [1] => Array ( [id] => 256 [tag] => Powerful Software ) ) )
31
Models
The form as it stands now will just create Post records. Let's add some code to allow us to bind a given Post to one or many Tags:
Models
</tr> <tr> <td colspan="2"> <?php echo $html->hidden('Post/user_id', array('value'=>$_SESSION['uid']))?> <?php echo $html->hidden('Post/status' , array('value'=>'0'))?> <?php echo $html->submit('Save Post')?> </td> </tr> <tr> <td>Related Tags:</td> <td> <?php echo $html->selectTag('Tag/Tag', $tags, null, array('multiple' => 'multiple')) ?> </td> </tr> </table>
In order for a call to $this->Post->save() in the controller to save the links between this new Post and its associated Tags, the name of the field must be in the form "Tag/Tag". The submitted data must be a single ID, or an array of IDs of linked records. Because we're using a multiple select here, the submitted data for Tag/Tag will be an array of IDs. The $tags variable here is just an array where the keys are the IDs of the possible Tags, and the values are the displayed names of the Tags in the multi-select element.
33
Chapter 7. Controllers
A controller is used to manage the logic for a certain section of your application. Most commonly, controllers are used to manage the logic for a single model. For example, if you were building a site that manages a video collection, you might have a VideoController and a RentalController managing your videos and rentals, respectively. In Cake, controller names are always plural. Your application's controllers are classes that extend the Cake AppController class, which in turn extends a core Controller class. Controllers can include any number of actions: functions used in your web application to display views. AppController class can be defined in /app/app_controller.php and it should contain methods that are shared between two or more controllers. It itself extends the Controller class which is a standard Cake library. An action is a single functionality of a controller. It is run automatically by the Dispatcher if an incoming page request specifies it in routes configuration. Returning to our video collection example, our VideoController might contain the view(), rent(), and search() actions. The controller would be found in /app/controllers/videos_controller.php and contain: class VideosController extends AppController { function view($id) { //action logic goes here.. } function rent($customer_id, $video_id) { //action logic goes here.. } function search($query) { //action logic goes here.. } }
You would be able to access these actions using the following example URLs: http://www.example.com/videos/view/253 http://www.example.com/videos/rent/5124/0-235253 http://www.example.com/videos/search/hudsucker+proxy
But how would these pages look? You would need to define a view for each of these actions - check it out in the next chapter, but stay with me: the following sections will show you how to harness the power of the Cake controller and use it to your advantage. Specifically, you'll learn how to have your controller hand data to the view, redirect the user, and much more.
Controllers
Controllers
afterFilter(); ; Called after every controller action. beforeRender(); ; Called after controller logic, and just before a view is rendered.
Imagine that we needed to create a simple table showing the users in the system. Instead of duplicating code in another controller, we can get the data from UsersController::getUserList() instead by using requestAction(). class ProductsController extends AppController { function showUserProducts() { $this->set('users', $this->requestAction('/users/getUserList')); // Now the $users variable in the view will have the data from // UsersController::getUserList(). } }
If you have an oft used element in your application that is not static, you might want to use requestAction() to inject it into your views. Let's say that rather than just passing the data 36
Controllers
from UsersController::getUserList, we actually wanted to render that action's view (which might consist of a table), inside another controller. This saves you from duplicating view code. class ProgramsController extends AppController { function viewAll() { $this->set('userTable', $this->requestAction('/users/getUserList', array('return'))); // Now, we can echo out $userTable in this action's view to // see the rendered view that is also available at /users/getUserList. } }
Please note that actions called using requestAction() are rendered using an empty layout this way you don't have to worry about layouts getting rendered inside of layouts. The requestAction() function is also useful in AJAX situations where a small element of a view needs to be populated before or during and AJAX update. log($message, $type = LOG_ERROR); string $message; int $type = LOG_ERROR; ou can use this function to log different events that happen within your web application. Logs can be found inside Cake's /tmp directory. If the $type is equal to the PHP constant LOG_DEBUG, the message is written to the log as a debug message. Any other type is written to the log as an error. // Inside a controller, you can use log() to write entries: $this->log('Mayday! Mayday!'); //Log entry: 06-03-28 08:06:22 Error: Mayday! Mayday! $this->log("Look like {$_SESSION['user']} just logged in.", LOG_DEBUG); //Log entry: 06-03-28 08:06:22 Debug: Looks like Bobby just logged in.
postConditions($data); array $data; A method to which you can pass $this->params['data'], and it will pass back an array formatted as a model conditions array. For example, if I have a person search form: // app/views/people/search.thtml: <?php echo $html->input('Person/last_name'); ?>
37
Controllers
Submitting the form with this element would result in the following $this->params['data'] array: Array ( [Person] => Array ( [last_name] => Anderson ) )
At this point, we can use postConditions() to format this data to use in model: // app/controllers/people_controller.php: $conditions = $this->postConditions($this->params['data']); // Yields an array looking like this: Array ( [Person.last_name] => Anderson ) // Which can be used in model find operations: $this->Person->findAll($conditions);
$helpers Use this variable to have your controller load helpers into its views. The HTML helper is automatically loaded, but you can use this variable to specify a few others: var $helpers = array('Html','Ajax','Javascript');
$layout Set this variable to the name of the layout you would like to use for this controller. 38
Controllers
$autoRender Setting this to false will stop your actions from automatically rendering. $beforeFilter If you'd like a bit of code run every time an action is called (and before any of that action code runs), use $beforeFilter. This functionality is really nice for access control - you can check to see a user's permissions before any action takes place. Just set this variable using an array containing the controller action(s) you'd like to run: class ProductsController extends AppController { var $beforeFilter = array('checkAccess'); function checkAccess() { //Logic to check user identity and access would go here.... } function index() { //When this action is called, checkAccess() is called first. } }
$components Just like $helpers and $uses, this variable is used to load up components you will need: var $components = array('acl');
Controllers
[username] => mrrogers [password] => myn3ighb0r [first_name] => Mister [last_name] => Rogers ) ) )
$this->params['form'] Any POST data from any form is stored here, including information also found in $_FILES. $this->params['bare'] Stores '1' if the current layout is bare, '0' if not. $this->params['ajax'] Stores '1' if the current layout is ajax, '0' if not. $this->params['controller'] Stores the name of the current controller handling the request. For example, if the URL / posts/view/1 was called, $this->params['controller'] would equal "posts". $this->params['action'] Stores the name of the current action handling the request. For example, if the URL / posts/view/1 was called, $this->params['action'] would equal "view". $this->params['pass'] Stores the GET query string passed with the current request. For example, if the URL / posts/view/?var1=3&var2=4 was called, $this->params['pass'] would equal "?var1=3&var2=4". $this->params['url'] Stores the current URL requested, along with key-value pairs of get variables. For example, if the URL /posts/view/?var1=3&var2=4 was called, $this->params['pass'] would look like this: [url] => Array ( [url] => posts/view [var1] => 3 [var2] => 4 )
40
Chapter 8. Views
8.1. Views
A view is a page template, usually named after an action. For example, the view for PostsController::add() would be found at /app/views/posts/add.thtml. Cake views are quite simply PHP files, so you can use any PHP code inside them. Although most of your view files will contain HTML, a view could be any perspective on a certain set of data, be it XML, and image, etc. In the view template file, you can use the data from the corresponding Model. This data is passed as an array called $data. Any data that you've handed to the view using set() in the controller is also now available in your view.
Note
The HTML helper is available in every view by default, and is by far the most commonly used helper in views. It is very helpful in creating forms, including scripts and media, linking and aiding in data validation. Please see section 1.1 in Chapter 9 for a discussion on the HTML helper. Most of the functions available in the views are provided by Helpers. Cake comes with a great set of helpers (discussed in Chapter 9), and you can also include your own. Because views shouldn't contain much logic, there aren't many well used public functions in the view class. One that is helpful is renderElement(), which will be discussed in section 1.2.
8.1.1. Layouts
A layout contains all the presentational code that wraps around a view. Anything you want to see in all of your views should be placed in your layout. Layout files are placed in /app/views/layouts. Cake's default layout can be overridden by placing a new default layout at /app/views/layouts/default.thtml. Once a new default layout has been created, controller view code is placed inside of the default layout when the page is rendered. When you create a layout, you need to tell Cake where to place your controller view code: to do so, make sure your layout includes a place for $content_for_layout (and optionally, $title_for_layout). Here's an example of what a default layout might look like: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title><?php echo $title_for_layout?></title> <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> <body> <!-- IF you'd like some sort of menu to show up on all of your views, include it here --> <div id="header"> <div id="menu">...</div> </div> <!-- Here's where I want my views to be displayed --> <?php echo $content_for_layout ?> <!-- Add a footer to each displayed page --> <div id="footer">...</div> </body> 41
Views
</html>
To set the title for the layout, its easiest to do so in the controller, using the $pageTitle controller variable. class UsersController extends AppController { function viewActive() { $this->pageTitle = 'View Active Users'; } }
You can create as many layouts as you wish for your Cake site, just place them in the app/ views/layouts directory, and switch between them inside of your controller actions using the controller's $layout variable, or setLayout() function. For example, if a section of my site included a smaller ad banner space, I might create a new layout with the smaller advertising space and specify it as the layout for all controller's actions using something like: var $layout = 'default_small_ad';
8.1.2. Elements
Many applications have small blocks of presentational code that needs to be repeated from page to page, sometimes in different places in the layout. Cake can help you repeat parts of your website that need to be reused. These reusable parts are called Elements. Ads, help boxes, navigational controls, extra menus, and callouts are often implemented in Cake as elements. An Element is basically a mini-view that can be included in other Views. Elements live in the /app/views/elements/ folder, and have the .thtml filename extension. The Element by default has no access to any data. To give it access to data, you send it in as a named parameter in an array.
42
Views
Inside the Element file, all the passed variables are available as the names of the keys of the passed array (much like how set() in the controller works with the views). In the above example, the /app/views/elements/helpbox.thtml file can use the $helptext variable. Of course, it would be more useful to pass an array to the Element. Elements can be used to make a View more readable, placing the rendering of repeating elements in its own file. They can also help you re-use content fragments in your website.
43
Chapter 9. Helpers
9.1. Helpers
Helpers are meant to provide functions that are commonly needed in views to format and present data in useful ways.
9.1.1. HTML
9.1.1.1. Introduction
The HTML helper is one of Cake's ways of making development less monotonous and more rapid. The HTML helper has two main goals: to aid in the insertion of oft-repeated sections of HTML code, and to aid in the quick-and-easy creation of web forms. The following sections will walk you through the highlight functions in the helper, but remember http://api.cakephp.org should always be used as a final reference. Many of the functions in the HTML helper make use of a HTML tag definition file called tags.ini.php. Cake's core configuration contains a tags.ini.php, but if you'd like to make some changes, create a copy of /cake/config/tags.ini.php and place it in your / app/config/ folder. The HTML helper uses the tag definitions in this file to generate tags you request. Using the HTML helper to create some of your view code can be helpful, as a change to the tags.ini.php file will result in a site-wide cascading change. Additionally, if AUTO_OUTPUT is set to true in your core config file for your application (/ app/config/core.php), the helper will automatically output the the tag, rather than returning the value. This has been done in an effort to assuage those who dislike short tags (<?= ?>) or lots of echo() calls in their view code. Functions that include the $return parameter allow you to force-override the settings in your core config. Set $return to true if you'd like the HTML helper to return the HTML code regardless of any AUTO_OUTPUT settings. HTML helper functions also include a $htmlAttributes parameter, that allow you to tack on any extra attributes on your tags. For example, if you had a tag you'd like to add a class attribute to, you'd pass this as the $htmlAttribute value: array('class'=>'someClass')
Helpers
boolean $return = false; Creates a link to a CSS stylesheet. The $rel parameter allows you to provide a rel= value for the tag. image($path, $htmlAttributes, $return = false); string $path; array $htmlAttributes; boolean $return = false; Renders an image tag. The code returned from this function can be used as an input for the link() function to automatically create linked images. link($title, $url, $htmlAttributes, $escapeTitle = true, $return = false); string $title; string $url; array $htmlAttributes; string $confirmMessage = false; boolean $escapeTitle = true; boolean $return = false; $confirmMessage = false,
Use this function to create links in your view. $confirmMessage is used when you need a JavaScript confirmation message to appear once the link is clicked. For example, a link that deletes an object should probably have a "Are you sure?" type message to confirm the action before the link is activated. Set $escapeTitle to true if you'd like to have the HTML helper escape the data you handed it in the $title variable. tableHeaders($names, $tr_options, $th_options); array $names; array $tr_options; array $th_options; Used to create a formatted table header. tableCells($data, $odd_tr_options, $even_tr_options); array $data; array $odd_tr_options; array $even_tr_options; Used to create a formatted set of table cells. guiListTree($data, $htmlAttributes, $bodyKey = 'body', $childrenKey = 'children', $return = false); array $data; array $htmlAttributes; string $bodyKey = 'body'; string $childrenKey = 'children'; boolean $return = false; Generates a nested unordered list tree from an array.
Helpers
Once we've got our controller set up, lets look at the view code (which would be found in app/views/notes/edit.thtml). Our Note model is pretty simple at this point it only contains an id, a submitter's id and a body. This view code is meant to display Note data and allow the user to enter new values and save that data to the model. The HTML helper is available in all views by default, and can be accessed using $html. Specifically, let's just look at the table where the guts of the form are found:
46
Helpers
<table cellpadding="10" cellspacing="0"> <tr> <td align="right">Body: </td> <td> <!-- Here's where we use the HTML helper to render the text area tag and its possible error message the $note variable was created by the controller, and contains the data for the note we're editing. --> <?php echo $html->textarea('Note/body', array('cols'=>'60', 'rows'=>'10')); ?> <?php echo $html->tagErrorMsg('Note/body', 'Please enter in a body for this note.') ?> </td> </tr> <tr> <td></td> <td> <!-- We can also use the HTML helper to include hidden tags inside our table --> <?php echo $html->hiddenTag('Note/id')?> <?php echo $html->hiddenTag('note/submitter_id', $this->controller->Session->read('User.id'))?> </td> </tr> </table> <!-- And finally, the submit button--> <?php echo $html->submit()?> </form>
Most of the form tag generating functions (along with tagErrorMsg) require you to supply a $fieldName. This $fieldName lets Cake know what data you are passing so that it can save and validate the data correclty. The string passed in the $fieldName parameter is in the form "modelname/fieldname." If you were going to add a new title field to our Note, you might add something to the view that looked like this: <?php echo $html->input('Note/title') ?> <?php echo $html->tagErrorMsg('Note/title', 'Please supply a title for this note.')?>
Error messages displayed by the tagErrorMsg() function are wrapped in <div class="error_message"></div> for easy CSS styling. Here are the form tags the HTML helper can generate (most of them are straightforward): submit($buttonCaption, $htmlAttributes, $return = false); string $buttonCaption; array $htmlAttributes; boolean $return = false; password($fieldName, $htmlAttributes, $return = false); string $fieldName; array $htmlAttributes; boolean $return = false;
47
Helpers
textarea($fieldName, $title, $htmlAttributes, $return = false); string $fieldName; string $title; array $htmlAttributes; boolean $return = false; checkbox($fieldName, $htmlAttributes, $return = false); string $fieldName; array $htmlAttributes; boolean $return = false; file($fieldName, $htmlAttributes, $return = false); string $fieldName; array $htmlAttributes; boolean $return = false; hidden($fieldName, $htmlAttributes, $return = false); string $fieldName; array $htmlAttributes; boolean $return = false; input($fieldName, $htmlAttributes, $return = false); string $fieldName; array $htmlAttributes; boolean $return = false; radio($fieldName, $options, $inbetween, $htmlAttributes, $return = false); string $fieldName; array $options; array $inbetween; array $htmlAttributes; boolean $return = false; tagErrorMsg($fieldName, $message); string $fieldName; string $message; The HTML helper also includes a set of functions that aid in creating date-related option tags. The $tagName parameter should be handled in the same way that the $fieldName parameter; Just provide the name of the field this date option tag is relevant to. Once the data is processed, you'll see it in your controller with the part of the date it handles concatenated to the end of the field name. For example, if my Note had a deadline field that was a date, and my dayOptionTag $tagName parameter was set to 'note/deadline', the day data would show up in the $params variable once the form has been submitted to a controller action: $this->data['Note']['deadline_day']
You can then use this information to concatenate the time data in a format that is friendly to your current database configuration. This code would be placed just before you attempt to save the data, and saved in the $data array used to save the information to the model.
Example 9.3. Concatenating time data before saving a model (excerpt from NotesController)
48
Helpers
function edit($id) { //First, let's check to see if any form data has been submitted to the action. if (!empty($this->data['Note'])) { //Concatenate time data for storage... $this->data['Note']['deadline'] = $this->data['Note']['deadline_year'] . "-" . $this->data['Note']['deadline_month'] . "-" . $this->data['Note']['deadline_day']; //Here's where we try to validate the form data (see Chap. 10) and save it if ($this->Note->save($this->data['Note'])) { ...
dayOptionTag ($tagName, $value=null, $selected=null, $optionAttr=null) yearOptionTag ($tagName, $selected=null, $optionAttr=null) $value=null, $minYear=null, $maxYear=null,
monthOptionTag ($tagName, $value=null, $selected=null, $optionAttr=null) hourOptionTag ($tagName, $optionAttr=null) $value=null, $format24Hours=false, $selected=null,
minuteOptionTag ($tagName, $value=null, $selected=null, $optionAttr=null) meridianOptionTag ($tagName, $value=null, $selected=null, $optionAttr=null) dateTimeOptionTag ($tagName, $selected=null, $optionAttr=null) $dateFormat= 'DMY', $timeFormat= '12',
9.1.2. AJAX
The Cake Ajax helper utilizes the ever-popular Prototype and script.aculo.us libraries for Ajax operations and client side effects. In order to use this helper, you must have a current version of the JavaScript libraries from http://script.aculo.us placed in / app/webroot/js/. In addition, any views that plan to use the Ajax Helper will need to include those libraries. Most of the functions in this helper expect a special $options array as a parameter. This array is used to specify different things about your Ajax operation. Here are the different values you can specify:
Helpers
// observeField() checks are made. $options['update'] update with $options['with'] to serialize $options['type'] 'synchronous'. types. /* Callbacks : JS code to be executed at certain times during the XMLHttpRequest process */ $options['loading'] document browser. $options['loaded'] has finished // JS code to be executed when the browser // loading the remote document. $options['interactive'] // JS code to be executed when the user can interact // with the remote document, even though it has not // finished loading. $options['complete'] XMLHttpRequest is $options['confirm'] dialog before $optoins['condition'] XMLHttpRequest $options['before'] initiated. $options['after'] request was // JS code to be called when the // complete. // Text to be displayed in a confirmation // a XMLHttpRequest action begins. // JS condition to be met before the // is initiated. // JS code to be called before request is // JS code to be called immediately after // initiated and before 'loading'. // JS code to be executed when the remote // is being loaded with data by the // The DOM ID of the element you wish you // the resuls of a Ajax operation. // The DOM ID of the form element you wish // and send with an Ajax form submission. // Either 'asynchronous' (default), or // Allows you to pick between operation
Here are the helper's functions for making Ajax in Cake quick and easy: link($title, $options, $confirm, $escapeTitle); string $title; array $options; boolean $confirm; boolean $escapeTitle; Displays linked text $title, which retrieves the remote document at $options['url'] and updates the DOM element $options['update']. Callbacks can be used with this function. remoteFunction($options); 50
Helpers
array $options; This function creates the JavaScript needed to make a remote call it is primarily used as a helper for linkToRemote. This isn't often used unless you need to generate some custom scripting. remoteTimer($options); array $options; Periodically calls the specified action at $options['url'], every $options['frequency'] seconds (default is 10). Usually used to update a specified div (specified by $options['update']) with the results of the remote call. Callbacks can be used with this function. form($action, $type, $options); string $action; string $type; array $options; Returns a form tag that will submit to the action at $action using XMLHttpRequest in the background instead of the regular reload-required POST submission. The form data from this form will act just as a normal form data would (i.e. it will be available in $this->params['form']). The DOM element specified by $options['update'] will be updated with the resulting remote document. Callbacks can be used with this function. observeField($field_id, $options); string $field_id; array $options; Observes the field with the DOM ID specified by $field_id (every $options['frequency'] seconds) and calls the action at $options['url'] when its contents have changed. You can update a DOM element with ID $options['update'] or specify a form element using $options['with'] as well. Callbacks can be used with this function. observeForm($form_id, $options); string $form_id; array $options; Works the same as observeField(), only this observes all the elements in a given form. autoComplete($field, $url, $options); string $field; string $url; array $options; Renders a text field with ID $field with autocomplete. The action at $url should be able to return the autocomplete terms: basically, your action needs to spit out a unordered list (<ul></ul>) with list items that are the auto complete terms. If you wanted an autocomplete field that retrieved the subjects of your blog posts, your controller action might look something like: function autocomplete () { $this->set('posts', $this->Post->findAll( "subject LIKE '{$this->data['Post']['subject']}'") ); $this->layout = "ajax"; }
51
Helpers
And your view for the autocomplete() action above would look something like: <ul> <?php foreach($posts as $post): ?> <li><?php echo $post['Post']['subject']; ?></li> <?php endforeach; ?> </ul>
The actual auto-complete field as it would look in a view would look like this: <form action="/users/index" method="POST"> <?php echo $ajax->autoComplete('Post/subject', '/posts/autoComplete')?> <?php echo $html->submit('View Post')?> </form>
The autoComplete() function will use this information to render a text field, and some divs that will be used to show the autocomplete terms supplied by your action. You might also want to style the view with something like the following: <style type="text/css"> div.auto_complete { position width background-color border margin padding } :absolute; :250px; :white; :1px solid #888; :0px; :0px;
drag($id, $options); string $id; array $options; Makes the DOM element with ID $id draggable. There are some additional things you can specify using $options: // (The version numbers refer to script.aculo.us versions) $options['handle'] only be value must be // an element reference or element id. $options['handle'] be a first // child/grandchild/etc. element found within the // element that has this CSS class value will be used // as the handle. // (V1.5) As above, except now the value may // string referencing a CSS class value. The // (v1.0) Sets whether the element should // draggable by an embedded handle. The
52
Helpers
// (V1.0) If set to true, the element // original position when the drags ends. // (V1.5) Revert can also be an arbitrary // reference, called when the drag ends.
$options['constraint'] // If set to horizontal or vertical, the drag will // be constrained to take place only horizontally or // vertically.
drop($id, $options); string $id; array $options; Makes the DOM element with ID $id drop-able. There are some additional things you can specify using $options: $options['accept'] array of Droppable will // only accept Draggables that have one or more of // these CSS classes. $options['containment'] // the // the given // single element // $options['overlap'] droppable if its //overlapping by more than 50% in the given direction. The droppable element will only accept draggable element if it is contained in elements (or element ids). Can be a or a JS array of elements. // Set accept to a string or a JavaScript // strings describing CSS classes. The
//If set to horizontal or vertical, the // will only react to a draggable element
dropRemote($id, $options, $ajaxOptions); string $id; array $options; array $ajaxOptions; Used to create a drop target that initiates a XMLHttpRequest when a draggable element is dropped on it. The $options are the same as in drop(), and the $ajaxOptions are the same as in link(). sortable($id, $options); string $id; array $options; Makes a list or group of floated objects (specified by DOM element ID $id) sortable. The $options array can configure your sorting as follows:
53
Helpers
// Sets the kind of tag (of the child // container) that will be made sortable. // OL containers, this is LI, you have to
provide // the tag kind for other sorts of child tags. // Defaults to 'li'. $options['only'] elements given CSS class // (or, if you provide an array of strings, on any of // the classes). $options['overlap'] For choose // horizontal. Vertical lists should use vertical. $options['constraint'] elements, // Restricts the movement of draggable // 'vertical' or 'horizontal'. $options['containment'] // Enables dragging and dropping between Sortables. // Takes an array of elements or element-ids (of the // containers). $options['handle'] handles, // Makes the created draggable elemetns use // see the handle option on drag(). // Either vertical(default) or horizontal. // floating sortables or horizontal lists, // Further restricts the selection of child // to only encompass elements with the
9.1.3. Javascript
The JavaScript helper is used to aid the developer in outputting well-formatted Javascript-related tags and data. codeBlock($string); string $string; Used to return $script placed within JavaScript <script> tags. link($url); string $url; Returns a JavaScript include tag pointing to the script referenced by $url. linkOut($url); string $url; Same as link(), only the include tag assumes that the script referenced by $url is not hosted on the same domain. escapeScript($script); string $script; 54
Helpers
Escapes carriage returns and single and double quotes for JavaScript code segments. event($object, $event, $observer, $useCapture); string $object; string $event; string $observer; boolean $useCapture; Attaches an event to an element. Used with the Prototype library. cacheEvents(); ; Caches JavaScript events created with event(). writeEvents(); ; Writes cached events cached with cacheEvents(). includeScript($script); string $script;
9.1.4. Number
The Number helper includes a few nice functions for formatting numerical data in your views. precision($number, $precision = 3); mixed $number; int $precision = 3; Returns $number formatted to the level of precision specified by $precision. toReadableSize($sizeInBytes); int $sizeInBytes; Returns a human readable size, given the $size supplied in bytes. Basically, you pass a number of bytes in, and this function returns the appropriate human-readable value in KB, MB, GB, or TB. toPercentage($number, $precision = 2); mixed $number; int $precision = 2; Returns the given number formatted as a percentage, limited to the precision specified in $precision.
9.1.5. Text
The Text Helper provides methods that a developer may need for outputting well formatted text to the browser. highlight($text, $highlighter = class="highlight">\1</span>'); string $text; string $highlighter = '<span class="highlight">\1</span>'; '<span
55
Helpers
Returns $text, with every occurance or $phrase wrapped with the tags specified in $highlighter. stripLinks($text); string $text; Returns $text, with all HTML links (<a href= ...) removed. autoLinkUrls($text, $htmlOptions); string $text; array $htmlOptions; Returns $text with URLs wrapped in corresponding <a> tags. autoLinkEmails($text, $htmlOptions); string $text; array $htmlOptions; Returns $text with email addresses wrapped in corresponding <a> tags. autoLink($text, $htmlOptions); string $text; array $htmlOptions; Returns $text with URLs and emails wrapped in corresponding <a> tags. truncate($text, $length, $ending = '...'); string $text; int $length; string $ending = '...'; Returns the first $length number of characters of $text followed by $ending ('...' by default). excerpt($text, $phrase, $radius = 100, $ending = '...'); string $text; string $phrase; int $radius = 100; string $ending = '...'; Extracts an excerpt from the $text, grabbing the $phrase with a number of characters on each side determined by $radius. autoLinkEmails($text, $allowHtml = false); string $text; boolean $allowHtml = false; Text-to-html parser, similar to Textile or RedCloth, only with a little different syntax.
9.1.6. Time
The Time Helper provides methods that a developer may need for outputting Unix timestamps and/or datetime strings into more understandable phrases to the browser. Dates can be provided to all functions as either valid PHP datetime strings or Unix timestamps. fromString($dateString); string $dateString;
56
Helpers
Returns a UNIX timestamp, given either a UNIX timestamp or a valid strtotime() date string. nice($dateString, $return = false); string $dateString; boolean $return = false; Returns a nicely formatted date string. Dates are formatted as "D, M jS Y, H:i", or 'Mon, Jan 1st 2005, 12:00'. niceShort($dateString, $return = false); string $dateString; boolean $return = false; Formats date strings as specified in nice(), but ouputs "Today, 12:00" if the date string is today, or "Yesterday, 12:00" if the date string was yesterday. isToday($dateString); string $dateString; Returns true if given datetime string is today. daysAsSql($begin, $end, $fieldName, $return = false); string $begin; string $end; string $fieldName; boolean $return = false; Returns a partial SQL string to search for all records between two dates. dayAsSql($dateString, $fieldName, $return = false); string $dateString; string $fieldName; boolean $return = false; Returns a partial SQL string to search for all records between two times occurring on the same day. isThisYear($dateString, $return = false); string $dateString; boolean $return = false; Returns true if given datetime string is within current year. wasYesterday($dateString, $return = false); string $dateString; boolean $return = false; Returns true if given datetime string was yesterday. isTomorrow($dateString, $return = false); string $dateString; boolean $return = false; Returns true if given datetime string is tomorrow. toUnix($dateString, $return = false); string $dateString; boolean $return = false; 57
Helpers
Returns a UNIX timestamp from a textual datetime description. Wrapper for PHP function strtotime(). toAtom($dateString, $return = false); string $dateString; boolean $return = false; Returns a date formatted for Atom RSS feeds. toRSS($dateString, $return = false); string $dateString; boolean $return = false; Formats date for RSS feeds timeAgoInWords($dateString, $return = false); string $dateString; boolean $return = false; Returns either a relative date or a formatted date depending on the difference between the current time and given datetime. $datetime should be in a strtotime-parsable format like MySQL datetime. relativeTime($dateString, $return = false); string $dateString; boolean $return = false; Works much like timeAgoInWords(), but includes the ability to create output for timestamps in the future as well (i.e. "Yesterday, 10:33", "Today, 9:42", and also "Tomorrow, 4:34"). relativeTime($timeInterval, $dateString, $return = false); string $timeInterval; string $dateString; boolean $return = false; Returns true if specified datetime was within the interval specified, else false. The time interval should be specifed with the number as well as the units: '6 hours', '2 days', etc.
9.1.7. Cache
Helpers
{ function makeEdit($title, $url) { // Logic to create specially formatted link goes here... } }
There are a few functions included in Cake's helper class you might want to take advantage of: output($string, $return = false); string $string; boolean $return = false; Decides whether to output or return a string based on AUTO_OUTPUT (see / app/config/core.php) and $return's value. You should use this function to hand any data back to your view. loadConfig(); ; Returns your application's current core configuration and tag definitions. Let's use output() to format our link title and URL and hand it back to the view.
Helpers
{ // Use the HTML helper to output // formatted data: $link = $this->Html->link($title, $url, array('class' => 'edit')); $this->output("<div class=\"editOuter\">$link</div>"); } }
Remember to include the HTML helper in the array if you plan to use it elsewhere. The naming conventions are similar to that of models. LinkHelper = class name link = key in helpers array link.php = name of php file in /app/views/helpers.
9.2.4. Contributing
Please consider giving your code back to Cake - contact one of the developers using our Trac system or the mailing list, or open a new CakeForge project to share your new helper with others.
60
vendor($lib1, $lib2...); string $lib1; string $lib2...; Used to load external libraries found in the /vendors directory. Supply the name of the lib filename without the '.php' extension. vendor('myWebService', 'nusoap');
debug($var, $showHtml = false); mixed $var; boolean $showHtml = false; If the application's DEBUG level is non-zero, the $var is printed out. If $showHTML is true, the data is rendered to be browser-friendly. a(); ; Returns an array of the parameters used to call the wrapping function. function someFunction() { echo print_r(a('foo', 'bar')); } 61
aa(); ; Used to create associative arrays formed from the parameters used to call the wrapping function. echo aa('a','b'); // output: array( 'a' => 'b' )
e($text); string $text; Convenience wrapper for echo(). low(); ; Convenience wrapper for strtolower(). up(); ; Convenience wrapper for strtoupper(). r($search, $replace, $subject); string $search; string $replace; string $subject; Convenience wrapper for str_replace(). pr($data); mixed $data; Convenience function equivalent to: echo "<pre>" . print_r($data) . "</pre>";
62
am($array1, $array2...); array $array1; array $array2...; Merges and returns the arrays supplied in the parameters. env($key); string $key; Gets an environment variable from available sources. Used as a backup if $_SERVER or $_ENV are disabled. This function also emulates PHP_SELF and DOCUMENT_ROOT on unsupporting servers. In fact, it's a good idea to always use env() instead of $_SERVER or getenv() (especially if you plan to distribute the code), since it's a full emulation wrapper. cache($path, $expires, $target = 'cache'); string $path; string $expires; string $target = 'cache'; Writes the data in $data to the path in /app/tmp specified by $path as a cache. The expiration time specified by $expires must be a valid strtotime() string. The $target of the cached data can either be 'cache' or 'public'. clearCache($search, $path = 'views', $ext); string $search; string $path = 'views'; string $ext; Used to delete files in the cache directories, or clear contents of cache directories. If $search is a string, matching cache directory or file names will be removed from the cache. The $search parameter can also be passed as an array of names of files/directories to be cleared. If empty, all files in /app/tmp/cache/views will be cleared. The $path parameter can be used to specify which directory inside of /tmp/cache is to be cleared. Defaults to 'views'. The $ext param is used to specify files with a certain file extention you wish to clear. stripslashes_deep($array); array $array; Recursively strips slashes from all values in an array. countdim($array); array $array; Returns the number of dimensions in the supplied array. fileExistsInPath($file); string $file; Searches the current include path for a given filename. Returns the path to that file if found, false if not found. convertSlash($string); string $string; 63
Converts forward slashes to underscores and removes first and last underscores in a string.
64
Validations are defined using Perl-compatibile regular expressions, some of which are predefined in /libs/validators.php. These are: VALID_NOT_EMPTY VALID_NUMBER VALID_EMAIL VALID_YEAR If there are any validations present in the model definition (i.e. in the $validate array), they will be parsed and checked during saves (i.e. in the Model::save() method). To validate the data directly use the Model::validates() (returns false if data is incorrect) and Model::invalidFields() (which returns an array of error messages). But usually the data is implicit in the controller code. The following example demonstrates how to create a form-handling action:
Action
in
Data Validation
function add () { if (empty($this->data)) { $this->render(); } else { if($this->Post->validates($this->data)) { //ok cool, the stuff is valid } else { $this->validateErrors($this->User);//set the values for the tagErrorMsg $this->render(); } } } } ?>
<h2>Add post to blog</h2> <form action="<?php echo $html->url('/blog/add')?>" method="post"> <div class="blog_add"> <p>Title: <?php echo $html->input('Post/title', array('size'=>'40'))?> <?php echo $html->tagErrorMsg('Post/title', 'Title is required.')?> </p> <p>Body <?php echo $html->textarea('Post/body') ?> <?php echo $html->tagErrorMsg('Post/body', 'Body is required.')?> </p> <p><?=$html->submit('Save')?></p> </div> </form>
66
Data Validation
The Controller::validates($model[, $model...]) is used to check any custom validation added in the model. The Controller::validationErrors() method returns any error messages thrown in the model so they can be displayed by tagErrorMsg() in the view. If you'd like to perform some custom validation apart from the regex based Cake validation, you can use the invalidate() function of your model to flag a field as erroneous. Imagine that you wanted to show an error on a form when a user tries to create a username that already exists in the system. Because you can't just ask Cake to find that out using regex, you'll need to do your own validation, and flag the field as invalid to invoke Cake's normal form invalidation process. The controller might look something like this: <?php class UsersController extends AppController { function create() { // Check to see if form data has been submitted if (!empty($this->data['User'])) { //See if a user with that username exists $user = $this->User->findByUsername($this->data['User']['username']); // Invalidate the field to trigger the HTML Helper's error messages if (!empty($user['User']['username'])) { $this->User->invalidate('username');//populates tagErrorMsg('User/username') } //Try to save as normal, shouldn't work if the field was invalidated. if($this->User->save($this->data)) { $this->redirect('/users/index/saved'); } else { $this->render(); } } } } ?>
67
These are the entities in the system that will be requesting things (the ACOs) from the system. It should be noted that ACL is *not* a system that is meant to authenticate users. You should already have a way to store user information and be able to verify that user's identity when they enter the system. Once you know who a user is, that's where ACL really shines. Okay - back to our adventure. The next thing Gandalf needs to do is make an initial list of things, or ACOs, the system will handle. His list might look something like: Weapons The One Ring Salted Pork Diplomacy Ale
Traditionally, systems were managed using a sort of matrix, that showed a basic set of users and permissions relating to objects. If this information were stored in a table, it might look like the following, with X's indicating denied access, and O's indicating allowed access. Weapons Ale Gandalf O X The One Ring X Salted Pork O Diplomacy O
68
O X X X O O X X
X X O X X X X X
O X X O O O X X
O X X X O X O X
At first glance, it seems that this sort of system could work rather well. Assignments can be made to protect security (only Frodo can access the ring) and protect against accidents (keeping the hobbits out of the salted pork). It seems fine grained enough, and easy enough to read, right? For a small system like this, maybe a matrix setup would work. But for a growing system, or a system with a large amount of resources (ACOs) and users (AROs), a table can become unwieldy rather quickly. Imagine trying to control access to the hundreds of war encampments and trying to manage them by unit, for example. Another drawback to matrices is that you can't really logically group sections of users, or make cascading permissions changes to groups of users based on those logical groupings. For example, it would sure be nice to automatically allow the hobbits access to the ale and pork once the battle is over: Doing it on an indivudual user basis would be tedious and error prone, while making a cascading permissions change to all 'hobbits' would be easy. ACL is most usually implemented in a tree structure. There is usually a tree of AROs and a tree of ACOs. By organizing your objects in trees, permissions can still be dealt out in a granular fashion, while still maintaining a good grip on the big picture. Being the wise leader he is, Gandalf elects to use ACL in his new system, and organizes his objects along the following lines: Fellowship of the Ring: Warriors Aragorn Legolas Gimli Wizards Gandalf Hobbits Frodo Bilbo Merry Pippin Vistors Gollum
By structuring our party this way, we can define access controls to the tree, and apply those permissions to any children. The default permission is to deny access to everything. As you work your way down the tree, you pick up permissions and apply them. The last permission applied (that applies to the ACO you're wondering about) is the one you keep. So, using our ARO tree, Gandalf can hang on a few permissions: Fellowship of the Ring: [Deny: ALL] Warriors [Allow: Weapons, Ale, Elven Rations, 69
Salted Pork] Aragorn Legolas Gimli Wizards Gandalf Hobbits Frodo Bilbo Merry Pippin Vistors Gollum
If we wanted to use ACL to see if the Pippin was allowed to access the ale, we'd first get his path in the tree, which is Fellowship->Hobbits->Pippin. Then we see the different permissions that reside at each of those points, and use the most specific permission relating to Pippin and the Ale. Fellowship = DENY Ale, so deny (because it is set to deny all ACOs) Hobbits = ALLOW Ale, so allow Pippin = ?; There really isn't any ale-specific information so we stick with ALLOW. Final Result: allow the ale.
The tree also allows us to make finer adjustments for more granular control - while still keeping the ability to make sweeping changes to groups of AROs: Fellowship of the Ring: [Deny: ALL] Warriors [Allow: Weapons, Ale, Elven Rations, Salted Pork] Aragorn [Allow: Diplomacy] Legolas Gimli Wizards [Allow: Salted Pork, Diplomacy, Ale] Gandalf Hobbits [Allow: Ale] Frodo [Allow: Ring] Bilbo Merry [Deny: Ale] Pippin [Allow: Diplomacy] Vistors [Allow: Salted Pork] Gollum
You can see this because the Aragorn ARO maintains is permissions just like others in the Warriors ARO group, but you can still make fine-tuned adjustments and special cases when you see fit. Again, permissions default to DENY, and only change as the traversal down the tree forces an ALLOW. To see if Merry can access the Ale, we'd find his path in the tree: Fellowship->Hobbits->Merry and work our way down, keeping track of ale-related permissions: Fellowship = DENY (because it is set to deny all), so deny the ale. Hobbits = ALLOW: ale, so allow the ale Merry = DENY ale, so deny the ale
70
Using the INI file, you can specify users (AROs), the group(s) they belong to, and their own personal permissions. You can also specify groups along with their permissions. To learn how to use Cake's ACL component to check permissions using this INI file, see section 11.4.
page, you should see the messages "Your database configuration file is present." and "Cake is able to connect to the database." if you've done it correctly. See section 4.1 for more information on database configuration. Next, use the the ACL command-line script to initialize your database to store ACL information. The script found at /cake/scripts/acl.php will help you accomplish this. Initialize the your database for ACL by executing the following command (from your /cake/scripts/ directory):
At this point, you should be able to check your project's database to see the new tables. If you're curious about how Cake stores tree information in these tables, read up on modified database tree traversal. Basically, it stores nodes, and their place in the tree. The acos and aros tables store the nodes for their respective trees, and the aros_acos table is used to link your AROs to the ACOs they can access. Now, you should be able to start creating your ARO and ACO trees.
12.3.2. Creating Access Request Objects (AROs) and Access Control Objects (ACOs)
There are two ways of referring to AROs/ACOs. One is by giving them an numeric id, which is usually just the primary key of the table they belong to. The other way is by giving them a string alias. The two are not mutually exclusive. The way to create a new ARO is by using the methods defined the the Aro Cake model. The create() method of the Aro class takes three parameters: $link_id, $parent_id, and $alias. This method creates a new ACL object under the parent specified by a parent_id or as a root object if the $parent_id passed is null. The $link_id allows you to link a current user object to Cake's ACL structures. The alias parameter allows you address your object using a non-integer ID. Before we can create our ACOs and AROs, we'll need to load up those classes. The easiest way to do this is to include Cake's ACL Component in your controller using the $components array: var $components = array('Acl');
Once we've got that done, let's see what some examples of creating these objects might look like. The following code could be placed in a controller action somewhere: $aro = new Aro(); // First, set up a few AROs. // These objects will have no parent initially.
72
1, 2, 3, 4,
// Now, we can make groups to organize these users: // Notice that the IDs for these objects are 0, because // they will never tie to users in our system $aro->create(0, null, 'Presidents'); $aro->create(0, null, 'Artists'); //Now, hook AROs to their respective groups: $aro->setParent('Presidents', 'George Washington'); $aro->setParent('Presidents', 'Abraham Lincoln'); $aro->setParent('Artists', 'Jimi Hendrix'); $aro->setParent('Artists', 'Bob Marley'); //In short, here is how to create an ARO: $aro = new Aro(); $aro->create($user_id, $parent_id, $alias);
You can also create AROs using the command line script using $acl.php create aro <link_id> <parent_id> <alias>. Creating an ACO is done in a similar manner: $aco = new Aco(); //Create some access control objects: $aco->create(1, null, 'Electric Guitar'); $aco->create(2, null, 'United States Army'); $aco->create(3, null, 'Fans'); // I suppose we could create groups for these // objects using setParent(), but we'll skip that // for this particular example //So, to create an ACO: $aco = new Aco(); $aco->create($id, $parent, $alias);
The corresponding command line script command would be: $acl.php create aco <link_id> <parent_id> <alias>.
// checks were made on anything, it would // be denied. Let's allow an ARO access to an ACO. function someAction() { //ALLOW // Here is how you grant an ARO full access to an ACO $this->Acl->allow('Jimi Hendrix', 'Electric Guitar'); $this->Acl->allow('Bob Marley', 'Electric Guitar'); // We can also assign permissions to groups, remember? $this->Acl->Allow('Presidents', 'United States Army'); // // // // The allow() method has a third parameter, $action. You can specify partial access using this parameter. $action can be set to create, read, update or delete. If no action is specified, full access is assumed.
// Look, don't touch, gentlemen: $this->Acl->allow('George Washington', 'Electric Guitar', 'read'); $this->Acl->allow('Abraham Lincoln', 'read'); //DENY //Denies work in the same manner: //When his term is up... $this->Acl->deny('Abraham Lincoln', 'United States Army'); } } 'Electric Guitar',
This particular controller isn't especially useful, but it is mostly meant to show you how the process works. Using the Acl component in connection with your user management controller would be the best usage. Once a user has been created on the system, her ARO could be created and placed at the right point of the tree, and permissions could be assigned to specific ACO or ACO groups based on her identity. Permissions can also be assigned using the command line script packaged with Cake. The syntax is similar to the model functions, and can be viewed by executing $php acl.php help.
// Check access using the component: $access = $this->Acl->check($this->Session->read('user_alias'), $aco, $action = "*"); //access denied if ($access === false) { echo "access denied"; exit; } //access allowed else { echo "access allowed"; exit; } } }
Basically, by making the Acl component available in the AppController, it will be visible for use in any controller in your application. // Here's the basic format: $this->Acl->Check($aro, $aco, $action = '*');
75
echo $mrClean->paranoid($badString); // output: scripthtml echo $mrClean->paranoid($badString, array(' ', '@')); // output: scripthtml @@
html($string, $remove = false); string $string; boolean $remove = false; This method helps you get user submitted data ready for display inside an existing HTML layout. This is especially useful if you don't want users to be able to break your layouts or insert images or scripts inside of blog comments, forum posts, and the like. If the $remove option is set to true, any HTML is removed rather than rendered as HTML entities. $badString = '<font size="99" color="#FF0000">HEY</font><script>...</script>'; echo $mrClean->html($badString); 76
Data Sanitization
// output: <font size="99" color="#FF0000">HEY</font><script>...</script> echo $mrClean->html($badString, true); // output: font size=99 color=#FF0000 HEY fontscript...script
sql($string); string $string; Used to escape SQL statements by adding slashes, depending on the system's current magic_quotes_gpc setting. cleanArray($dirtyArray); array @$dirtyArray; This function is an industrial strength, multi-purpose cleaner, meant to be used on entire arrays (like $this->params['form'], for example). The function takes an array and cleans it: nothing is returned because the array is passed by reference. The following cleaning operations are performed on each element in the array (recursively): Odd spaces (including 0xCA) are replaced with regular spaces. HTML is replaced by its corresponding HTML entities (including \n to <br>). Double-check special chars and remove carriage returns for increased SQL security. Add slashes for SQL (just calls the sql function outlined above). Swap user-inputted backslashes with trusted backslashes.
77
In order to use the database session storage, you'll need to create a table in your database. The schema for that table can be found in /app/config/sql/sessions.sql.
78
Deletes the session variable specified by $name. error(); ; Returns the last error created by the CakeSession component. Mostly used for debugging. flash($key = 'flash'); string $key = 'flash'; Returns the last message set in the session using setFlash(). If $key has been set, the message returned is the most recent stored under that key. read($name); string $name; Returns the session variable specified by $name. renew(); ; Renews the currently active session by creating a new session ID, deleting the old one, and passing the old session data to the new one. setFlash($flashMessage, $layout 'flash'); string $flashMessage; string $layout = 'default'; array $params; string $key = 'flash'; = 'default', $params, $key =
Writes the message specified by $flashMessage into the session (to be later retrieved by flash()). If $layout is set to 'default', the message is stored as '<div class="message">'.$flashMessage.'</div>'. If $default is set to '', the message is stored just as it has been passed. If any other value is passed, the message is stored inside the Cake view specified by $layout. Params has been placed in this function for future usage. Check back for more info. The $key variable allows you to store flash messages under keys. See flash() for retreiving a flash message based off of a key. valid(); ; Returns true if the session is valid. Best used before read() operations to make sure that the session data you are trying to access is in fact valid. write($name, $value); string $name; mixed $value; Writes the variable specified by $name and $value into the active session.
79
class PostsController extends AppController { var $components = array('RequestHandler'); function beforeFilter () { if ($this->RequestHandler->accepts('html')) { // Execute code only if client accepts an HTML (text/html) response } elseif ($this->RequestHandler->accepts('rss')) { // Execute RSS-only code } elseif ($this->RequestHandler->accepts('atom')) { // Execute Atom-only code } elseif ($this->RequestHandler->accepts('xml')) { // Execute XML-only code } if ($this->RequestHandler->accepts(array('xml', 'rss', 80
'atom'))) { // Executes if the client accepts any of the above: XML, RSS or Atom } } }
getAjaxVersion(); ; If you are using the Prototype JS libraries, you can fetch a special header it sets on AJAX requests. This function returns the Prototype version used. getClientIP(); ; Returns the remote client's IP address. getReferrer(); ; Returns the server name from which the request originated. isAjax(); ; Returns true if the current request was an XMLHttpRequest. isAtom(); ; Returns true if the client accepts Atom feed content (application/atom+xml). isDelete(); ; Returns true if the current request was via DELETE. isGet(); ; Returns true if the current request was via GET. isMobile(); ; Returns true if the user agent string matches a mobile web browser. isPost(); ; Returns true if the current request was via POST. isPut(); 81
; Returns true if the current request was via PUT. isRss(); ; Returns true if the clients accepts RSS feed content (application/rss+xml). isXml(); ; Returns true if the client accepts XML content (application/xml or text/xml). setContent($name, $type); string $name; string $type; Adds a content-type alias mapping, for use with accepts() and prefers(), where $name is the name of the mapping (string), and $type is either a string or an array of strings, each of which is a MIME type. The built-in type mappings are as follows:
// Name => 'js' => 'css' => 'html' => 'form' => 'file' => 'xhtml' => 'text/xhtml'), 'xml' => 'rss' => 'atom' =>
Type 'text/javascript', 'text/css', 'text/html', 'application/x-www-form-urlencoded', 'multipart/form-data', array('application/xhtml+xml', 'application/xhtml', array('application/xml', 'text/xml'), 'application/rss+xml', 'application/atom+xml'
82
stripTags($str, $tag1, $tag2...); string $str; string $tag1; string $tag2...; Removes the tags specified by $tag1, $tag2, etc. from $str. $someString = '<font color="#FF0000"><bold>Foo</bold></font> <em>Bar</em>'; echo $this->RequestHandler->stripTags($someString, 'font', 'bold'); // output: Foo <em>Bar</em>
When a normal browser request is made to /things/list, the unordered list is rendered inside of the default layout for the application. If the URL is requested as part of an AJAX operation, the list is automatically rendered in the bare AJAX layout.
83
class ThingsController extends AppController { var $components = array('Security'); function beforeFilter() { $this->Security->requirePost('delete'); } function delete($id) { // This will only happen if the action is called via an HTTP POST request $this->Thing->del($id); } } Here, we're telling the Security component that the 'delete' action requires a POST request. The beforeFilter() method is usually where you'll want to tell Security (and most other com84
ponents) what to do with themselves. It will then go do what it's told right after beforeFilter() is called, but right before the action itself is called. And that's about all there is to it. You can test this by typing the URL for the action into your browser and seeing what happens.
85
This line tells Cake that you want to enable View Caching. Next, you'll need to specify what you want to cache.
View Caching
//Cache an entire action. In this case the recalled product list, for one day: var $cacheAction = array('recalled/' => 86400); //If we wanted to, we could cache every action by setting it to a string: //that is strtotime() friendly to indicate the caching time. var $cacheAction = "1 hour"; //You can also define caching in the actions using $this->cacheAction = array()...
87
View Caching
//Remove all cached pages that have the controller_action_params name. //Note: you can have multiple params clearCache('controller/action/params'); //You can also use an array to clear muliple caches at once. clearCache(array('controller/action/params','controller2/action/params));
88
Regardless of how you downloaded it, place the code inside of your DocumentRoot. Once finished, your directory setup should look something like the following: /path_to_document_root /.htaccess /app /cake /index.php /vendors /VERSION.txt
Now might be a good time to learn a bit about how Cake's directory structure works: check out Chapter 2, Section 2: Overview of the Cake File Layout. 89
The choices on table and column names are not arbitrary. If you follow Cake's database naming conventions (outlined in Chapter 1, Section 4), and Cake's class naming conventions (outlined in Appendix B), you'll be able to take advantage of a lot of free functionality and avoid configuration. Cake is flexible enough to accomodate even the worst legacy database schema, but adhering to convention will save you time. Check out Chapter 1, Section 4 and Appendix B for more information, but suffice it to say that naming our table 'posts' automatically hooks it to our Post model, and having fields called 'modified' and 'created' will be automagically managed by Cake.
Once you've saved your new database.php file, you should be able to open your browser and see the Cake welcome page. It should also tell you that your database connection file was found, and that Cake can successfully connect to the database.
90
If you don't want or can't get mod_rewrite (or some other compatible module) up and running on your server, you'll need to use Cake's built in pretty URLs. In / app/config/core.php, uncomment the line that looks like: define ('BASE_URL', env('SCRIPT_NAME'));
look rather
like than
Because of the way the class and file are named, this tells Cake that you want a Post mod91
el available in your PostsController that is tied to a table in your default database called 'posts'. The $name variable is always a good idea to add, and is used to overcome some class name oddness in PHP4. For more on models, such as table prefixes, callbacks, and validation, check out Chapter 6.
Now, lets add an action to our controller. When users request www.example.com/posts, this is the same as requesting www.example.com/posts/index. Since we want our readers to view a list of posts when they access that URL, the index action would look something like this:
Example added)
<?php
A.3.
/app/controllers/posts_controller.php
(index
action
class PostsController extends AppController { var $name = 'Posts'; function index() { $this->set('posts', $this->Post->findAll()); } } ?>
Let me explain the action a bit. By defining function index() in our PostsController, users can now access the logic there by requesting www.example.com/posts/index. Similarly, if we were to define a function called foobar(), users would be able to access that at www.example.com/posts/foobar. The single instruction in the action uses set() to pass data to the view (which we'll create 92
next). The line sets the view variable called 'posts' equal to the return value of the findAll() method of the Post model. Our Post model is automatically available at $this->Post because we've followed Cake's naming conventions. To learn more about Cake's controllers, check out Chapter 7.
Cake's view files are stored in /app/views inside a folder named after the controller they correspond to (we'll have to create a folder named 'posts' in this case). To format this post data in a nice table, our view code might look something like this:
<h1>Blog posts</h1> <table> <tr> <th>Id</th> <th>Title</th> <th>Created</th> </tr> <!-- Here's where we loop through our $posts array, printing out post info --> <?php foreach ($posts as $post): ?> <tr> <td><?php echo $post['Post']['id']; ?></td> <td> <?php echo $html->link($post['Post']['title'], "/posts/view/".$post['Post']['id']); ?> </td> <td><?php echo $post['Post']['created']; ?></td> </tr> <?php endforeach; ?> </table>
Hopefully this should look somewhat simple. You might have noticed the use of an object called $html. This is an instance of the HtmlHelper class. Cake comes with a set of view 'helpers' that make things like linking, form output, JavaScript and Ajax a snap. You can learn more about how to use them in Chapter 9, but what's important to note here is that the link() method will generate an HTML link with the given title (the first parameter) and URL (the second parameter). When specifying URL's in Cake, you simply give a path relative to the base of the application, and Cake fills in the rest. As such, your URL's will typically take the form of / controller/action/id. Now you should be able to point your browser to http://www.example.com/posts/index. You should see your view, correctly formatted with the title and table listing of the posts. If you happened to have clicked on one of the links we created in this view (that link a post's title to a URL /posts/view/some_id), you were probably informed by Cake that the action hasn't yet been defined. If you were not so informed, either something has gone wrong, or you actually did define it already, in which case you are very sneaky. Otherwise, we'll create it now:
Example added)
<?php
A.5.
/app/controllers/posts_controller.php
(view
action
class PostsController extends AppController { var $name = 'Posts'; function index() { $this->set('posts', $this->Post->findAll()); } 94
The set() call should look familiar. Notice we're using read() rather than findAll() because we only really want a single post's information. Notice that our view action takes a parameter. This parameter is handed to the action by the URL called. If a user requests /posts/view/3, then the value '3' is passed as $id. Now let's create the view app/views/posts/view.thtml. for our new 'view' action and place it in /
Verify that this is working by trying the links at /posts/index or manually requesting a post by accessing /posts/view/1.
} function add() { if (!empty($this->data)) { if ($this->Post->save($this->data)) { $this->flash('Your post has been saved.','/posts'); } } } } ?>
Let me read the add() action for you in plain English: if the form data isn't empty, try to save the post model using that data. If for some reason it doesn't save, give me the data validation errors and render the view showing those errors. When a user uses a form to POST data to your application, that information is available in $this->params. You can pr() that out if you want to see what it looks like. $this->data is an alias for $this->params['data'] The $this->flash() function called is a controller function that flashes a message to the user for a second (using the flash layout) then forwards the user on to another URL (/posts, in this case). If DEBUG is set to 0 $this->flash() will redirect automatically, however, if DEBUG > 0 then you will be able to see the flash layout and click on the message to handle the redirect. Calling the save() method well check for validation errors and will not save if any occur. There are several methods available so you can check for validation errors, but we talk about the validateErrors() call in a bit, so keep that on the back burner for a moment while I show you what the view looks like when we move on to the section about data validation.
<p> Body: <?php echo $html->textarea('Post/body', array('rows'=>'10')) ?> <?php echo $html->tagErrorMsg('Post/body', 'Body is required.') ?> </p> <p> <?php echo $html->submit('Save') ?> </p> </form>
As with $html->link(), $html->url() will generate a proper URL from the controller and action we have given it. By default, it prints out a POST form tag, but this can be modified by the second parameter. The $html->input() and $html->textarea() functions spit out form elements of the same name. The first parameter tells Cake which model/field they correspond to, and the second param is for extra HTML attributes (like the size of the input field). Again, refer to Chapter 9 for more on helpers. The tagErrorMsg() function calls will output the error messages in case there is a validation problem. If you'd like, you can update your /app/views/posts/index.thtml view to include a new "Add Post" link that points to www.example.com/posts/add. That seems cool enough, but how do I tell Cake about my validation requirements? This is where we come back to the model.
The $validate array tells Cake how to validate your data when the save() method is called. The values for those keys are just constants set by Cake that translate to regex matches (see /cake/libs/validators.php). Right now Cake's validation is regex based, but you can also use Model::invalidate() to set your own validation dynamically. Now that you have your validation in place, use the app to try to add a post without a title or body to see how it works.
97
This logic deletes the post specified by $id, and uses flash() to show the user a confirmation message before redirecting them on to /posts. Because we're just executing some logic and redirecting, this action has no view. You might want to update your index view to allow users to delete posts, however.
98
This view code also uses the HtmlHelper to prompt the user with a JavaScript confirmation dialog before they attempt to delete a post.
This checks for submitted form data. If nothing was submitted, go find the Post and hand it to the view. If some data has been submitted, try to save the Post model (or kick back and show the user the validation errors). The edit view might look something like this:
This view ouputs the edit form (with the values populated), and the necessary error messages (if present). One thing to note here: Cake will assume that you are edititing a model if the 'id' field is present and exists in a currently stored model. If no 'id' is present (look back at our add view), Cake will assume that you are inserting a new model when save() is called. You can now update your index view with links to edit specific posts:
A.13. Routes
This part is optional, but helpful in understanding how URLs map to specific function calls in Cake. We're only going to make a quick change to routes in this tutorial. For more information, see Chapter 4, Section 3: Routes Configuration. Cake's default route will take a person visiting the root of your site (i.e. ht100
tp://www.example.com) to the PagesController, and render a view called home. Rather than do that, we'll want users of our blog application to go to our soon-to-be-created PostsController. Cake's routing is found in /app/config/routes.php. You'll want to comment out or remove the line that looks like this: $Route->connect ('/', array('controller'=>'pages', 'action'=>'display', 'home'));
This line connects the URL / with the default Cake home page. We want it to connect with our own controller, so add a line that looks like this: $Route->connect ('/', array('controller'=>'posts', 'action'=>'index'));
This should connect users requesting '/' to the index() action of our soon-to-be-created PostsController.
A.14. Conclusion
Creating applications this way will win you peace, honor, women, and money beyond even your wildest fantasies. Simple, isn't it? Keep in mind that this tutorial was very basic. Cake has many more features to offer, and is flexible in ways we didn't wish to cover here. Use the rest of this manual as a guide for building more feature-rich applications. Now that you've created a basic Cake application you're ready for the real thing. Start your own project, read the rest of the Manual and API. If you need help, come see us in #cakephp. Welcome to Cake!
101
In this example, we'll create a simple user authentication system for a client management system. This fictional application would probably be used by an office to track contact information and related notes about clients. All of the system functionality will be placed behind our user authentication system except for few bare-bones, public-safe views that shows only the names and titles of clients stored in the system. We'll start out by showing you how to verify users that try to access the system. Authenticated user info will be stored in a PHP session using Cake's Session Component. Once we've got user info in the session, we'll place checks in the application to make sure application users aren't entering places they shouldn't be. One thing to note - authentication is not the same as access control. All we're after in this example is how to see if people are who they say they are, and allow them basic access to parts of the application. If you want to fine tune this access, check out the chapter on Cake's Access Control Lists. We'll make notes as to where ACLs might fit in, but for now, let's focus on simple user authentication. I should also say that this isn't meant to serve as some sort of primer in application security. We just want to give you enough to work with so you can build bulletproof apps of your own.
Database
CREATE TABLE `users` ( `id` int(11) NOT NULL auto_increment, `username` varchar(255) NOT NULL, `password` varchar(32) NOT NULL, `first_name` varchar(255) NOT NULL, `last_name` varchar(255) NOT NULL, PRIMARY KEY (`id`) )
Pretty simple, right? The Cake Model for this table can be pretty bare: <?php class User extends AppModel { var $name = 'User'; } ?>
First thing we'll need is a login view and action. This will provide a way for application users to attempt logins and a way for the system to process that information to see if they should be allowed to access the system or not. The view is just a HTML form, created with the help of Cake's Html Helper:
This view presents a simple login form for users trying access the system. The action for the form is /users/login, which is in teh UsersController and looks like this:
<?php class UsersController extends AppController { function login() { // If a has submitted form data: if (!empty($this->data)) { // First, let's see if there are any users in the database // with the username supplied by the user using the form: $someone = $this->User->findByUsername($this->params['data']['User']['username']); // At this point, $someone is full of user data, or its empty. // Let's compare the form-submitted password with the one in // the database. if($someone['User']['password'] == $this->params['data']['User']['password']) { // Note: hopefully your password in the DB is hashed, // so your comparison might look more like: // md5($somone['User']['password']) == ... // This means they were the same. We can now build some basic // session information to remember this user as 'logged-in'. $this->Session->write('User', $someone['User']); // Now that we have them stored in a session, forward them on // to a landing page for the application. $this->redirect('/clients'); } // Else, they supplied incorrect data: else { // Remember the $error var in the view? Let's set that to true: $this->set('error', true); } } //Don't show the error message if no data has been submitted. $this->set('error', false); } function logout() { // Redirect users to this action if they click on a Logout button. // All we need to do here is trash the session information: $this->Session->delete('User); // And we should probably forward them somewhere, too... $this->redirect('/'); 104
} } ?>
Not too bad: the contents of the login() action could be less than 20 lines if you were concise. The result of this action is either 1: the user information is entered into the session and forwarded to the landing page of the app, or 2: kicked back to the login screen and presented the login form (with an additional error message).
Now you have a function you can use in any controller to make sure users aren't trying to access controller actions without logging in first. Once this is in place you can check access at any level - here are some examples:
105
Now that you have the basics down, you might want to venture out on your own and implement some advanced or customized features past what has been outlined here. Integration with Cake's ACL component might be a good first step.
106
Database tables related to models also use a lower-case underscored syntax - but they are plural. Examples: people, monkeys, glass_doors, line_items, really_nifty_things
Note
CakePHP naming conventions are meant to streamline code creation and make code more readable. If you find it getting in your way, you can override it. Model name: Set var $name in your model definition. Model-related database tables: Set var $useTable in your model definition.
C.2. Controllers
Controller class names are plural. Controller class names are Capitalized for single-word controllers, and UpperCamelCased for multi-word controllers. Controller class names also end with 'Controller'. Examples: PeopleController, MonkeysController, GlassDoorsController, LineItemsController, ReallyNiftyThingsController Controller file names use a lower-case underscored syntax. Controller file names also end with '_controller'. So if you have a controller class called PostsController, the controller file name should be posts_controller.php Examples: people_controller.php, monkeys_controller.php, glass_doors_controller.php, line_items_controller.php, really_nifty_things_controller.php
C.3. Views
107
Views are named after actions they display. Name the view file after action name, in lowercase. Examples: PeopleController::worldPeace() expects a view in / app/views/people/worldpeace.thtml; MonkeysController::banana() expects a view in /app/views/monkeys/banana.thtml.
Note
You can force an action to render a specific view by $this->render('name_of_view_file_without_dot_thtml'); at the end of your action. calling
108
Appendix D. Plugins
109
110