0% found this document useful (0 votes)
641 views750 pages

Cake PHP

Uploaded by

Ramesh
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
641 views750 pages

Cake PHP

Uploaded by

Ramesh
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 750

CakePHP Cookbook Documentation

Release 2.x

Cake Software Foundation

January 08, 2013

Contents

Getting Started Blog Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Blog Tutorial - Adding a layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Installation Requirements . . . . . . . . . . . . . . . . . . . . . . License . . . . . . . . . . . . . . . . . . . . . . . . . Downloading CakePHP . . . . . . . . . . . . . . . . . Permissions . . . . . . . . . . . . . . . . . . . . . . . Setup . . . . . . . . . . . . . . . . . . . . . . . . . . Development . . . . . . . . . . . . . . . . . . . . . . Production . . . . . . . . . . . . . . . . . . . . . . . . Advanced Installation and server specic conguration Fire It Up . . . . . . . . . . . . . . . . . . . . . . . .

1 1 5 25 25 25 26 26 26 26 27 28 33

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

CakePHP Overview 35 What is CakePHP? Why Use it? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Understanding Model-View-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Where to Get Help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Controllers The App Controller . . . . . Request parameters . . . . . Controller actions . . . . . . Request Life-cycle callbacks Controller Methods . . . . . Controller Attributes . . . . More on controllers . . . . . Views 41 41 42 42 43 44 51 52 73 i

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

View Templates . . . . . . . . Using view blocks . . . . . . . Layouts . . . . . . . . . . . . Elements . . . . . . . . . . . . Creating your own view classes View API . . . . . . . . . . . More about Views . . . . . . . 6

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

73 75 77 79 82 83 85

Models 95 Understanding Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 More on models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 Core Libraries General Purpose Behaviors . . . Components . . Helpers . . . . Utilities . . . . 195 195 233 259 308 396 505 505 505 506 507 507 508 509 509 510 511 511 511 513 513 515 516 518 518 519 520 527 530 543 543 555 572

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

Plugins Installing a Plugin . . . . . . . . . . Plugin conguration . . . . . . . . . Advanced bootstrapping . . . . . . Using a Plugin . . . . . . . . . . . . Creating Your Own Plugins . . . . . Plugin Controllers . . . . . . . . . . Plugin Models . . . . . . . . . . . . Plugin Views . . . . . . . . . . . . Plugin assets . . . . . . . . . . . . . Components, Helpers and Behaviors Expand Your Plugin . . . . . . . . . Plugin Tips . . . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

. . . . . . . . . . . .

Console and Shells The CakePHP console . . . . . . . . . . Creating a shell . . . . . . . . . . . . . Shell tasks . . . . . . . . . . . . . . . . Invoking other shells from your shell . . Console output levels . . . . . . . . . . Styling output . . . . . . . . . . . . . . Conguring options and generating help Shell API . . . . . . . . . . . . . . . . More topics . . . . . . . . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

. . . . . . . . .

10 Development Conguration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Routing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

ii

Exceptions . . . . Error Handling . Debugging . . . . Testing . . . . . . REST . . . . . . Dispatcher Filters Vendor packages

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

578 585 587 590 612 615 619 621 621 621 621 622 623 623 627 638 645 651 655 655 661 667 678 711 730 733 735

11 Deployment Check your security . . . . . . . . . . . . . . . . . Set document root . . . . . . . . . . . . . . . . . . Update core.php . . . . . . . . . . . . . . . . . . . Multiple CakePHP applications using the same core

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

12 Tutorials & Examples Blog Tutorial . . . . . . . . . . . . . . . . . . . . . Blog Tutorial - Adding a layer . . . . . . . . . . . . Simple Authentication and Authorization Application Simple Acl controlled Application . . . . . . . . . . Simple Acl controlled Application - part 2 . . . . . . 13 Appendices 2.3 Migration Guide . . . 2.2 Migration Guide . . . 2.1 Migration Guide . . . 2.0 Migration Guide . . . Migration from 1.2 to 1.3 General Information . . . 14 Indices and tables Index

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

. . . . . .

iii

iv

CHAPTER 1

Getting Started

The CakePHP framework provides a robust base for your application. It can handle every aspect, from the users initial request all the way to the nal rendering of a web page. And since the framework follows the principles of MVC, it allows you to easily customize and extend most aspects of your application. The framework also provides a basic organizational structure, from lenames to database table names, keeping your entire application consistent and logical. This concept is simple but powerful. Follow the conventions and youll always know exactly where things are and how theyre organized. The best way to experience and learn CakePHP is to sit down and build something. To start off well build a simple blog application.

Blog Tutorial
Welcome to CakePHP. Youre probably checking out this tutorial because you want to learn more about how CakePHP works. Its our aim to increase productivity and make coding more enjoyable: we hope youll see this as you dive into the code. This tutorial will walk you through the creation of a simple blog application. Well be getting and installing Cake, creating and conguring a database, and creating enough application logic to list, add, edit, and delete blog posts. Heres what youll need: 1. A running web server. Were going to assume youre using Apache, though the instructions for using other servers should be very similar. We might have to play a little with the server conguration, but most folks can get Cake up and running without any conguration at all. Make sure you have PHP 5.2.8 or greater. 2. A database server. Were going to be using MySQL server in this tutorial. Youll need to know enough about SQL in order to create a database: Cake will be taking the reins from there. Since were using MySQL, also make sure that you have pdo_mysql enabled in PHP. 3. Basic PHP knowledge. The more object-oriented programming youve done, the better: but fear not if youre a procedural fan. 1

CakePHP Cookbook Documentation, Release 2.x

4. Finally, youll need a basic knowledge of the MVC programming pattern. A quick overview can be found in Understanding Model-View-Controller. Dont worry, its only a half a page or so. Lets get started!

Getting Cake
First, lets get a copy of fresh Cake code. To get a fresh download, visit the CakePHP project http://github.com/cakephp/cakephp/downloads and download the latest release of 2.0 You can also clone the repository using git://github.com/cakephp/cakephp.git git (http://git-scm.com/). on GitHub:

git clone

Regardless of how you downloaded it, place the code inside of your DocumentRoot. Once nished, your directory setup should look something like the following:
/path_to_document_root /app /lib /plugins /vendors .htaccess index.php README

Now might be a good time to learn a bit about how Cakes directory structure works: check out CakePHP Folder Structure section.

Creating the Blog Database


Next, lets set up the underlying database for our blog. If you havent already done so, create an empty database for use in this tutorial, with a name of your choice. Right now, well just create a single table to store our posts. Well also throw in a few posts right now to use for testing purposes. Execute the following SQL statements into your database:
/* First, create our posts table: */ CREATE TABLE posts ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, title VARCHAR(50), body TEXT, created DATETIME DEFAULT NULL, modified DATETIME DEFAULT NULL ); /* Then insert some posts for testing: */ INSERT INTO posts (title,body,created) VALUES (The title, This is the post body., NOW()); INSERT INTO posts (title,body,created) VALUES (A title once again, And the post body follows., NOW());

Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

INSERT INTO posts (title,body,created) VALUES (Title strikes back, This is really exciting! Not., NOW());

The choices on table and column names are not arbitrary. If you follow Cakes database naming conventions, and Cakes class naming conventions (both outlined in CakePHP Conventions), youll be able to take advantage of a lot of free functionality and avoid conguration. Cake is exible enough to accommodate even the worst legacy database schema, but adhering to convention will save you time. Check out CakePHP Conventions for more information, but sufce it to say that naming our table posts automatically hooks it to our Post model, and having elds called modied and created will be automagically managed by Cake.

Cake Database Conguration


Onward and upward: lets tell Cake where our database is and how to connect to it. For many, this is the rst and last time you congure anything. A copy of CakePHPs database conguration le is found in /app/Config/database.php.default. Make a copy of this le in the same directory, but name it database.php. The cong le should be pretty straightforward: just replace the values in the $default array with those that apply to your setup. A sample completed conguration array might look something like the following:
public $default = array( datasource => Database/Mysql, persistent => false, host => localhost, port => , login => cakeBlog, password => c4k3-rUl3Z, database => cake_blog_tutorial, schema => , prefix => , encoding => );

Once youve saved your new database.php le, you should be able to open your browser and see the Cake welcome page. It should also tell you that your database connection le was found, and that Cake can successfully connect to the database. Note: Remember that youll need to have PDO, and pdo_mysql enabled in your php.ini.

Optional Conguration
There are three other items that can be congured. Most developers complete these laundry-list items, but theyre not required for this tutorial. One is dening a custom string (or salt) for use in security hashes. The second is dening a custom number (or seed) for use in encryption. The third item is allowing CakePHP write access to its tmp folder. Blog Tutorial 3

CakePHP Cookbook Documentation, Release 2.x

The security salt is used for generating hashes. Change the default salt value by editing /app/Config/core.php line 187. It doesnt much matter what the new value is, as long as its not easily guessed:
/** * A random string used in security hashing methods. */ Configure::write(Security.salt, pl345e-P45s_7h3*S@l7!);

The cipher seed is used for encrypt/decrypt strings. Change the default seed value by editing /app/Config/core.php line 192. It doesnt much matter what the new value is, as long as its not easily guessed:
/** * A random numeric string (digits only) used to encrypt/decrypt strings. */ Configure::write(Security.cipherSeed, 7485712659625147843639846751);

The nal task is to make the app/tmp directory web-writable. The best way to do this is to nd out what user your webserver runs as (<?php echo whoami; ?>) and change the ownership of the app/tmp directory to that user. The nal command you run (in *nix) might look something like this:
$ chown -R www-data app/tmp

If for some reason CakePHP cant write to that directory, youll be informed by a warning while not in production mode.

A Note on mod_rewrite
Occasionally a new user will run into mod_rewrite issues, so Ill mention them marginally here. If the CakePHP welcome page looks a little funny (no images or css styles), it probably means mod_rewrite isnt functioning on your system. Here are some tips to help get you up and running: 1. Make sure that an .htaccess override is allowed: in your httpd.conf, you should have a section that denes a section for each Directory on your server. Make sure the AllowOverride is set to All for the correct Directory. For security and performance reasons, do not set AllowOverride to All in <Directory />. Instead, look for the <Directory> block that refers to your actual website directory. 2. Make sure you are editing the correct httpd.conf rather than a user- or site-specic httpd.conf. 3. For some reason or another, you might have obtained a copy of CakePHP without the needed .htaccess les. This sometimes happens because some operating systems treat les that start with . as hidden, and dont copy them. Make sure your copy of CakePHP is from the downloads section of the site or our git repository. 4. Make sure Apache is loading up mod_rewrite correctly! You should see something like:
LoadModule rewrite_module libexec/httpd/mod_rewrite.so

or (for Apache 1.3):

Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

AddModule

mod_rewrite.c

in your httpd.conf. If you dont want or cant get mod_rewrite (or some other compatible module) up and running on your server, youll need to use Cakes built in pretty URLs. In /app/Config/core.php, uncomment the line that looks like:
Configure::write(App.baseUrl, env(SCRIPT_NAME));

Also remove these .htaccess les:


/.htaccess /app/.htaccess /app/webroot/.htaccess

This will make your URLs look like www.example.com/index.php/controllername/actionname/param rather than www.example.com/controllername/actionname/param. If you are installing CakePHP on a webserver besides Apache, you can nd instructions for getting URL rewriting working for other servers under the Advanced Installation section. Continue to Blog Tutorial - Adding a layer to start building your rst CakePHP application.

Blog Tutorial - Adding a layer


Create a Post Model
The Model class is the bread and butter of CakePHP applications. By creating a CakePHP model that will interact with our database, well have the foundation in place needed to do our view, add, edit, and delete operations later. CakePHPs model class les go in /app/Model, and the le well be creating will be saved to /app/Model/Post.php. The completed le should look like this:
class Post extends AppModel { }

Naming convention is very important in CakePHP. By naming our model Post, CakePHP can automatically infer that this model will be used in the PostsController, and will be tied to a database table called posts. Note: CakePHP will dynamically create a model object for you if it cannot nd a corresponding le in /app/Model. This also means that if you accidentally name your le wrong (i.e. post.php or posts.php), CakePHP will not recognize any of your settings and will use the defaults instead. For more on models, such as table prexes, callbacks, and validation, check out the Models chapter of the Manual.

Blog Tutorial - Adding a layer

CakePHP Cookbook Documentation, Release 2.x

Create a Posts Controller


Next, well create a controller for our posts. The controller is where all the business logic for post interaction will happen. In a nutshell, its the place where you play with the models and get post-related work done. Well place this new controller in a le called PostsController.php inside the /app/Controller directory. Heres what the basic controller should look like:
class PostsController extends AppController { public $helpers = array(Html, Form); }

Now, lets add an action to our controller. Actions often represent a single function or interface in an application. For example, when users request www.example.com/posts/index (which is also the same as www.example.com/posts/), they might expect to see a listing of posts. The code for that action would look something like this:
class PostsController extends AppController { public $helpers = array(Html, Form); public function index() { $this->set(posts, $this->Post->find(all)); } }

Let me explain the action a bit. By dening function index() in our PostsController, users can now access the logic there by requesting www.example.com/posts/index. Similarly, if we were to dene a function called foobar(), users would be able to access that at www.example.com/posts/foobar. Warning: You may be tempted to name your controllers and actions a certain way to obtain a certain URL. Resist that temptation. Follow CakePHP conventions (plural controller names, etc.) and create readable, understandable action names. You can map URLs to your code using routes covered later on. The single instruction in the action uses set() to pass data from the controller to the view (which well create next). The line sets the view variable called posts equal to the return value of the find(all) method of the Post model. Our Post model is automatically available at $this->Post because weve followed Cakes naming conventions. To learn more about Cakes controllers, check out the Controllers chapter.

Creating Post Views


Now that we have our data owing to our model, and our application logic and ow dened by our controller, lets create a view for the index action we created above. Cake views are just presentation-avored fragments that t inside an applications layout. For most applications theyre HTML mixed with PHP, but they may end up as XML, CSV, or even binary data. Layouts are presentation code that is wrapped around a view, and can be dened and switched between, but for now, lets just use the default.

Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

Remember in the last section how we assigned the posts variable to the view using the set() method? That would hand down data to the view that would look something like this:
// print_r($posts) output: Array ( [0] => Array ( [Post] => Array ( [id] => 1 [title] => The title [body] => This is the post body. [created] => 2008-02-13 18:34:55 [modified] => ) ) [1] => Array ( [Post] => Array ( [id] => 2 [title] => A title once again [body] => And the post body follows. [created] => 2008-02-13 18:34:56 [modified] => ) ) [2] => Array ( [Post] => Array ( [id] => 3 [title] => Title strikes back [body] => This is really exciting! Not. [created] => 2008-02-13 18:34:57 [modified] => ) ) )

Cakes view les are stored in /app/View inside a folder named after the controller they correspond to (well 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
<!-- File: /app/View/Posts/index.ctp --> <h1>Blog posts</h1> <table> <tr> <th>Id</th> <th>Title</th> <th>Created</th>

Blog Tutorial - Adding a layer

CakePHP Cookbook Documentation, Release 2.x

</tr> <!-- Here is 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 $this->Html->link($post[Post][title], array(controller => posts, action => view, $post[Post][id])); ?> </td> <td><?php echo $post[Post][created]; ?></td> </tr> <?php endforeach; ?> <?php unset($post); ?> </table>

Hopefully this should look somewhat simple. You might have noticed the use of an object called $this->Html. This is an instance of the CakePHP HtmlHelper class. CakePHP 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 Helpers, but whats important to note here is that the link() method will generate an HTML link with the given title (the rst parameter) and URL (the second parameter). When specifying URLs in Cake, it is recommended that you use the array format. This is explained in more detail in the section on Routes. Using the array format for URLs allows you to take advantage of CakePHPs reverse routing capabilities. You can also specify URLs relative to the base of the application in the form of /controller/action/param1/param2. At this point, 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 posts title to a URL /posts/view/some_id), you were probably informed by CakePHP that the action hasnt yet been dened. If you were not so informed, either something has gone wrong, or you actually did dene it already, in which case you are very sneaky. Otherwise, well create it in the PostsController now:
class PostsController extends AppController { public $helpers = array(Html, Form); public function index() { $this->set(posts, $this->Post->find(all)); } public function view($id = null) { if (!$id) { throw new NotFoundException(__(Invalid post)); } $post = $this->Post->findById($id); if (!$post) { throw new NotFoundException(__(Invalid post));

Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

} $this->set(post, $post); } }

The set() call should look familiar. Notice were using findById() rather than find(all) because we only really want a single posts information. Notice that our view action takes a parameter: the ID of the post wed like to see. This parameter is handed to the action through the requested URL. If a user requests /posts/view/3, then the value 3 is passed as $id. We also do a bit of error checking to ensure a user is actually accessing a record. If a user requests /posts/view, we will throw a NotFoundException and let the CakePHP ErrorHandler take over. We also perform a similar check to make sure the user has accessed a record that exists. Now lets create the view for our new view action and place it in /app/View/Posts/view.ctp
<!-- File: /app/View/Posts/view.ctp --> <h1><?php echo h($post[Post][title]); ?></h1> <p><small>Created: <?php echo $post[Post][created]; ?></small></p> <p><?php echo h($post[Post][body]); ?></p>

Verify that this is working by trying the links at /posts/index or manually requesting a post by accessing /posts/view/1.

Adding Posts
Reading from the database and showing us the posts is a great start, but lets allow for the adding of new posts. First, start by creating an add() action in the PostsController:
class PostsController extends AppController { public $helpers = array(Html, Form, Session); public $components = array(Session); public function index() { $this->set(posts, $this->Post->find(all)); } public function view($id) { if (!$id) { throw new NotFoundException(__(Invalid post)); } $post = $this->Post->findById($id); if (!$post) { throw new NotFoundException(__(Invalid post)); }

Blog Tutorial - Adding a layer

CakePHP Cookbook Documentation, Release 2.x

$this->set(post, $post); } public function add() { if ($this->request->is(post)) { $this->Post->create(); if ($this->Post->save($this->request->data)) { $this->Session->setFlash(Your post has been saved.); $this->redirect(array(action => index)); } else { $this->Session->setFlash(Unable to add your post.); } } } }

Note: You need to include the SessionComponent - and SessionHelper - in any controller where you will use it. If necessary, include it in your AppController. Heres what the add() action does: if the HTTP method of the request was POST, try to save the data using the Post model. If for some reason it doesnt save, just render the view. This gives us a chance to show the user validation errors or other warnings. Every CakePHP request includes a CakeRequest object which is accessible using $this->request. The request object contains useful information regarding the request that was just received, and can be used to control the ow of your application. In this case, we use the CakeRequest::is() method to check that the request is a HTTP POST request. When a user uses a form to POST data to your application, that information is available in $this->request->data. You can use the pr() or debug() functions to print it out if you want to see what it looks like. We use the SessionComponents SessionComponent::setFlash() method to set a message to a session variable to be displayed on the page after redirection. In the layout we have SessionHelper::flash which displays the message and clears the corresponding session variable. The controllers Controller::redirect function redirects to another URL. The param array(action => index) translates to URL /posts i.e the index action of posts controller. You can refer to Router::url() function on the API (http://api20.cakephp.org) to see the formats in which you can specify a URL for various Cake functions. Calling the save() method will check for validation errors and abort the save if any occur. Well discuss how those errors are handled in the following sections.

Data Validation
Cake goes a long way in taking the monotony out of form input validation. Everyone hates coding up endless forms and their validation routines. CakePHP makes it easier and faster. To take advantage of the validation features, youll need to use Cakes FormHelper in your views. The FormHelper is available by default to all views at $this->Form. 10 Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

Heres our add view:


<!-- File: /app/View/Posts/add.ctp --> <h1>Add Post</h1> <?php echo $this->Form->create(Post); echo $this->Form->input(title); echo $this->Form->input(body, array(rows => 3)); echo $this->Form->end(Save Post); ?>

Here, we use the FormHelper to generate the opening tag for an HTML form. Heres the HTML that $this->Form->create() generates:
<form id="PostAddForm" method="post" action="/posts/add">

If create() is called with no parameters supplied, it assumes you are building a form that submits to the current controllers add() action (or edit() action when id is included in the form data), via POST. The $this->Form->input() method is used to create form elements of the same name. The rst parameter tells CakePHP which eld they correspond to, and the second parameter allows you to specify a wide array of options - in this case, the number of rows for the textarea. Theres a bit of introspection and automagic here: input() will output different form elements based on the model eld specied. The $this->Form->end() call generates a submit button and ends the form. If a string is supplied as the rst parameter to end(), the FormHelper outputs a submit button named accordingly along with the closing form tag. Again, refer to Helpers for more on helpers. Now lets go back and update our /app/View/Posts/index.ctp view to include a new Add Post link. Before the <table>, add the following line:
<?php echo $this->Html->link( Add Post, array(controller => posts, action => add) ); ?>

You may be wondering: how do I tell CakePHP about my validation requirements? Validation rules are dened in the model. Lets look back at our Post model and make a few adjustments:
class Post extends AppModel { public $validate = array( title => array( rule => notEmpty ), body => array( rule => notEmpty ) ); }

The $validate array tells CakePHP how to validate your data when the save() method is called. Here, Ive specied that both the body and title elds must not be empty. CakePHPs validation engine is strong, with a number of pre-built rules (credit card numbers, email addresses, etc.) and exibility for adding your Blog Tutorial - Adding a layer 11

CakePHP Cookbook Documentation, Release 2.x

own validation rules. For more information on that setup, check the Data Validation. Now that you have your validation rules in place, use the app to try to add a post with an empty title or body to see how it works. Since weve used the FormHelper::input() method of the FormHelper to create our form elements, our validation error messages will be shown automatically.

Editing Posts
Post editing: here we go. Youre a CakePHP pro by now, so you should have picked up a pattern. Make the action, then the view. Heres what the edit() action of the PostsController would look like:
public function edit($id = null) { if (!$id) { throw new NotFoundException(__(Invalid post)); } $post = $this->Post->findById($id); if (!$post) { throw new NotFoundException(__(Invalid post)); } if ($this->request->is(post) || $this->request->is(put)) { $this->Post->id = $id; if ($this->Post->save($this->request->data)) { $this->Session->setFlash(Your post has been updated.); $this->redirect(array(action => index)); } else { $this->Session->setFlash(Unable to update your post.); } } if (!$this->request->data) { $this->request->data = $post; } }

This action rst ensures that the user has tried to access an existing record. If they havent passed in a passed in an $id parameter, or the post does not exist, we throw a NotFoundException for the CakePHP ErrorHandler to take care of. Next the action checks that the request is a POST request. If it is, then we use the POST data to update our Post record, or kick back and show the user validation errors. If there is no data set to $this->request->data, we simply set it to the previously retrieved post. The edit view might look something like this:
<!-- File: /app/View/Posts/edit.ctp --> <h1>Edit Post</h1> <?php echo $this->Form->create(Post); echo $this->Form->input(title);

12

Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

echo $this->Form->input(body, array(rows => 3)); echo $this->Form->input(id, array(type => hidden)); echo $this->Form->end(Save Post);

This view outputs the edit form (with the values populated), along with any necessary validation error messages. One thing to note here: CakePHP will assume that you are editing a model if the id eld is present in the data array. 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 specic posts:
<!-- File: /app/View/Posts/index.ctp (edit links added) -->

<h1>Blog posts</h1> <p><?php echo $this->Html->link("Add Post", array(action => add)); ?></p> <table> <tr> <th>Id</th> <th>Title</th> <th>Action</th> <th>Created</th> </tr> <!-- Heres 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 $this->Html->link($post[Post][title], array(action => view, </td> <td> <?php echo $this->Html->link(Edit, array(action => edit, $post[Post][i </td> <td> <?php echo $post[Post][created]; ?> </td> </tr> <?php endforeach; ?> </table>

Deleting Posts
Next, lets make a way for users to delete posts. Start with a delete() action in the PostsController:
public function delete($id) { if ($this->request->is(get)) { throw new MethodNotAllowedException(); }

Blog Tutorial - Adding a layer

13

CakePHP Cookbook Documentation, Release 2.x

if ($this->Post->delete($id)) { $this->Session->setFlash(The post with id: . $id . has been deleted.); $this->redirect(array(action => index)); } }

This logic deletes the post specied by $id, and uses $this->Session->setFlash() to show the user a conrmation message after redirecting them on to /posts. If the user attempts to do a delete using a GET request, we throw an Exception. Uncaught exceptions are captured by CakePHPs exception handler, and a nice error page is displayed. There are many built-in Exceptions that can be used to indicate the various HTTP errors your application might need to generate. Because were just executing some logic and redirecting, this action has no view. You might want to update your index view with links that allow users to delete posts, however:
<!-- File: /app/View/Posts/index.ctp --> <h1>Blog posts</h1> <p><?php echo $this->Html->link(Add Post, array(action => add)); ?></p> <table> <tr> <th>Id</th> <th>Title</th> <th>Actions</th> <th>Created</th> </tr> <!-- Heres 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 $this->Html->link($post[Post][title], array(action => view, </td> <td> <?php echo $this->Form->postLink( Delete, array(action => delete, $post[Post][id]), array(confirm => Are you sure?)); ?> <?php echo $this->Html->link(Edit, array(action => edit, $post[Post][i </td> <td> <?php echo $post[Post][created]; ?> </td> </tr> <?php endforeach; ?> </table>

Using postLink() will create a link that uses Javascript to do a POST request deleting our post. Allowing content to be deleted using GET requests is dangerous, as web crawlers could accidentally delete all your 14 Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

content. Note: This view code also uses the FormHelper to prompt the user with a JavaScript conrmation dialog before they attempt to delete a post.

Routes
For some, CakePHPs default routing works well enough. Developers who are sensitive to user-friendliness and general search engine compatibility will appreciate the way that CakePHPs URLs map to specic actions. So well just make a quick change to routes in this tutorial. For more information on advanced routing techniques, see Routes Conguration. By default, CakePHP responds to a request for the root of your site (i.e. http://www.example.com) using its PagesController, rendering a view called home. Instead, well replace this with our PostsController by creating a routing rule. Cakes routing is found in /app/Config/routes.php. Youll want to comment out or remove the line that denes the default root route. It looks like this:
Router::connect(/, array(controller => pages, action => display, home));

This line connects the URL / with the default CakePHP home page. We want it to connect with our own controller, so replace that line with this one:
Router::connect(/, array(controller => posts, action => index));

This should connect users requesting / to the index() action of our PostsController. Note: CakePHP also makes use of reverse routing - if with the above route dened you pass array(controller => posts, action => index) to a function expecting an array, the resultant URL used will be /. Its therefore a good idea to always use arrays for URLs as this means your routes dene where a URL goes, and also ensures that links point to the same place too.

Conclusion
Creating applications this way will win you peace, honor, love, and money beyond even your wildest fantasies. Simple, isnt it? Keep in mind that this tutorial was very basic. CakePHP has many more features to offer, and is exible in ways we didnt wish to cover here for simplicitys sake. Use the rest of this manual as a guide for building more feature-rich applications. Now that youve created a basic Cake application youre ready for the real thing. Start your own project, read the rest of the Cookbook and API (http://api20.cakephp.org). If you need help, come see us in #cakephp. Welcome to CakePHP!

Blog Tutorial - Adding a layer

15

CakePHP Cookbook Documentation, Release 2.x

Suggested Follow-up Reading These are common tasks people learning CakePHP usually want to study next: 1. Layouts: Customizing your website layout 2. Elements Including and reusing view snippets 3. Scaffolding: Prototyping before creating code 4. Code Generation with Bake Generating basic CRUD code 5. Simple Authentication and Authorization Application: User authentication and authorization tutorial

Additional Reading
A Typical CakePHP Request Weve covered the basic ingredients in CakePHP, so lets look at how objects work together to complete a basic request. Continuing with our original request example, lets imagine that our friend Ricardo just clicked on the Buy A Custom Cake Now! link on a CakePHP applications landing page.

Figure 1.1: Flow diagram showing a typical CakePHP request Figure: 2. Typical Cake Request.

16

Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

Black = required element, Gray = optional element, Blue = callback 1. Ricardo clicks the link pointing to http://www.example.com/cakes/buy, and his browser makes a request to your web server. 2. The Router parses the URL in order to extract the parameters for this request: the controller, action, and any other arguments that will affect the business logic during this request. 3. Using routes, a request URL is mapped to a controller action (a method in a specic controller class). In this case, its the buy() method of the CakesController. The controllers beforeFilter() callback is called before any controller action logic is executed. 4. The controller may use models to gain access to the applications data. In this example, the controller uses a model to fetch Ricardos last purchases from the database. Any applicable model callbacks, behaviors, and DataSources may apply during this operation. While model usage is not required, all CakePHP controllers initially require at least one model. 5. After the model has retrieved the data, it is returned to the controller. Model callbacks may apply. 6. The controller may use components to further rene the data or perform other operations (session manipulation, authentication, or sending emails, for example). 7. Once the controller has used models and components to prepare the data sufciently, that data is handed to the view using the controllers set() method. Controller callbacks may be applied before the data is sent. The view logic is performed, which may include the use of elements and/or helpers. By default, the view is rendered inside of a layout. 8. Additional controller callbacks (like afterFilter) may be applied. The complete, rendered view code is sent to Ricardos browser. CakePHP Conventions We are big fans of convention over conguration. While it takes a bit of time to learn CakePHPs conventions, you save time in the long run: by following convention, you get free functionality, and you free yourself from the maintenance nightmare of tracking cong les. Convention also makes for a very uniform system development, allowing other developers to jump in and help more easily. CakePHPs conventions have been distilled out of years of web development experience and best practices. While we suggest you use these conventions while developing with CakePHP, we should mention that many of these tenets are easily overridden something that is especially handy when working with legacy systems.
Controller Conventions

Controller classnames are plural, CamelCased, and end in Controller. PeopleController and LatestArticlesController are both examples of conventional controller names. The rst method you write for a controller might be the index() method. When a request species a controller but not an action, the default CakePHP behavior is to execute the index() method of that controller. For example, a request for http://www.example.com/apples/ maps to a call on the index() method of the ApplesController, whereas http://www.example.com/apples/view/ maps to a call on the view() method of the ApplesController. Blog Tutorial - Adding a layer 17

CakePHP Cookbook Documentation, Release 2.x

You can also change the visibility of controller methods in CakePHP by prexing controller method names with underscores. If a controller method has been prexed with an underscore, the method will not be accessible directly from the web but is available for internal use. For example:
class NewsController extends AppController { public function latest() { $this->_findNewArticles(); } protected function _findNewArticles() { // Logic to find latest news articles } }

While the page http://www.example.com/news/latest/ would be accessible to the user as usual, someone trying to get to the page http://www.example.com/news/_ndNewArticles/ would get an error, because the method is preceded with an underscore. You can also use PHPs visibility keywords to indicate whether or not a method can be accessed from a url. Non-public methods cannot be accessed. URL Considerations for Controller Names As youve just seen, single word controllers map easily to a simple lower case URL path. For example, ApplesController (which would be dened in the le name ApplesController.php) is accessed from http://example.com/apples. Multiple word controllers can be any inected form which equals the controller name so: /redApples /RedApples /Red_apples /red_apples will all resolve to the index of the RedApples controller. However, the convention is that your urls are lowercase and underscored, therefore /red_apples/go_pick is the correct form to access the RedApplesController::go_pick action. For more information on CakePHP URLs and parameter handling, see Routes Conguration.
File and Classname Conventions

In general, lenames match the classnames, which are CamelCased. So if you have a class MyNiftyClass, then in Cake, the le should be named MyNiftyClass.php. Below are examples of how to name the le for each of the different types of classes you would typically use in a CakePHP application: The Controller class KissesAndHugsController would be found in a le named KissesAndHugsController.php The Component class MyHandyComponent would be found in a le named MyHandyComponent.php The Model class OptionValue would be found in a le named OptionValue.php 18 Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

The Behavior class EspeciallyFunkableBehavior would be found in a le named EspeciallyFunkableBehavior.php The View class SuperSimpleView would be found in a le named SuperSimpleView.php The Helper class BestEverHelper would be found in a le named BestEverHelper.php Each le would be located in the appropriate folder in your app folder.
Model and Database Conventions

Model classnames are singular and CamelCased. Person, BigPerson, and ReallyBigPerson are all examples of conventional model names. Table names corresponding to CakePHP models are plural and underscored. The underlying tables for the above mentioned models would be people, big_people, and really_big_people, respectively. You can use the utility library Inflector to check the singular/plural of words. See the Inector for more information. Field names with two or more words are underscored like, rst_name. Foreign keys in hasMany, belongsTo or hasOne relationships are recognized by default as the (singular) name of the related table followed by _id. So if a Baker hasMany Cake, the cakes table will refer to the bakers table via a baker_id foreign key. For a multiple worded table like category_types, the foreign key would be category_type_id. Join tables, used in hasAndBelongsToMany (HABTM) relationships between models should be named after the model tables they will join in alphabetical order (apples_zebras rather than zebras_apples). All tables with which CakePHP models interact (with the exception of join tables), require a singular primary key to uniquely identify each row. If you wish to model a table which does not have a single-eld primary key, CakePHPs convention is that a single-eld primary key is added to the table. You have to add a single-eld primary key if you want to use that tables model. CakePHP does not support composite primary keys. If you want to directly manipulate your join table data, use direct query calls or add a primary key to act on it as a normal model. E.g.:
CREATE TABLE posts_tags ( id INT(10) NOT NULL AUTO_INCREMENT, post_id INT(10) NOT NULL, tag_id INT(10) NOT NULL, PRIMARY KEY(id));

Rather than using an auto-increment key as the primary key, you may also use char(36). Cake will then use a unique 36 character uuid (String::uuid) whenever you save a new record using the Model::save method.
View Conventions

View template les are named after the controller functions they display, in an underscored form. The getReady() function of the PeopleController class will look for a view template in /app/View/People/get_ready.ctp. Blog Tutorial - Adding a layer 19

CakePHP Cookbook Documentation, Release 2.x

The basic pattern is /app/View/Controller/underscored_function_name.ctp. By naming the pieces of your application using CakePHP conventions, you gain functionality without the hassle and maintenance tethers of conguration. Heres a nal example that ties the conventions Database table: people Model class: Person, found at /app/Model/Person.php Controller class: PeopleController, found at /app/Controller/PeopleController.php View template, found at /app/View/People/index.ctp Using these conventions, CakePHP knows that a request to http://example.com/people/ maps to a call on the index() function of the PeopleController, where the Person model is automatically available (and automatically tied to the people table in the database), and renders to a le. None of these relationships have been congured by any means other than by creating classes and les that youd need to create anyway. Now that youve been introduced to CakePHPs fundamentals, you might try a run through the Blog Tutorial to see how things t together. CakePHP Folder Structure After youve downloaded and extracted CakePHP, these are the les and folders you should see: app lib vendors plugins .htaccess index.php README Youll notice three main folders: The app folder will be where you work your magic: its where your applications les will be placed. The lib folder is where weve worked our magic. Make a personal commitment not to edit les in this folder. We cant help you if youve modied the core. Finally, the vendors folder is where youll place third-party PHP libraries you need to use with your CakePHP applications.
The App Folder

CakePHPs app folder is where you will do most of your application development. Lets look a little closer at the folders inside of app. Cong Holds the (few) conguration les CakePHP uses. Database connection details, bootstrapping, core conguration les and more should be stored here. 20 Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

Controller Contains your applications controllers and their components. Lib Contains 1st party libraries that do not come from 3rd parties or external vendors. This allows you to separate your organizations internal libraries from vendor libraries. Locale Stores string les for internationalization. Model Contains your applications models, behaviors, and datasources. Plugin Contains plugin packages. tmp This is where CakePHP stores temporary data. The actual data it stores depends on how you have CakePHP congured, but this folder is usually used to store model descriptions, logs, and sometimes session information. Make sure that this folder exists and that it is writable, otherwise the performance of your application will be severely impacted. In debug mode, CakePHP will warn you if it is not the case. Vendor Any third-party classes or libraries should be placed here. Doing so makes them easy to access using the App::import(vendor, name) function. Keen observers will note that this seems redundant, as there is also a vendors folder at the top level of our directory structure. Well get into the differences between the two when we discuss managing multiple applications and more complex system setups. View Presentational les are placed here: elements, error pages, helpers, layouts, and view les. webroot In a production setup, this folder should serve as the document root for your application. Folders here also serve as holding places for CSS stylesheets, images, and JavaScript les. CakePHP Structure CakePHP features Controller, Model, and View classes, but it also features some additional classes and objects that make development in MVC a little quicker and more enjoyable. Components, Behaviors, and Helpers are classes that provide extensibility and reusability to quickly add functionality to the base MVC classes in your applications. Right now well stay at a higher level, so look for the details on how to use these tools later on.
Application Extensions

Controllers, helpers and models each have a parent class you can use to dene applicationwide changes. AppController (located at /app/Controller/AppController.php), AppHelper (located at /app/View/Helper/AppHelper.php) and AppModel (located at /app/Model/AppModel.php) are great places to put methods you want to share between all controllers, helpers or models. Although they arent classes or les, routes play a role in requests made to CakePHP. Route denitions tell CakePHP how to map URLs to controller actions. The default behavior assumes that the URL /controller/action/var1/var2 maps to Controller::action($var1, $var2), but you can use routes to customize URLs and how they are interpreted by your application. Some features in an application merit packaging as a whole. A plugin is a package of models, controllers and views that accomplishes a specic purpose that can span multiple applications. A user management system or a simplied blog might be a good t for CakePHP plugins. Blog Tutorial - Adding a layer 21

CakePHP Cookbook Documentation, Release 2.x

Controller Extensions (Components)

A Component is a class that aids in controller logic. If you have some logic you want to share between controllers (or applications), a component is usually a good t. As an example, the core EmailComponent class makes creating and sending emails a snap. Rather than writing a controller method in a single controller that performs this logic, you can package the logic so it can be shared. Controllers are also tted with callbacks. These callbacks are available for your use, just in case you need to insert some logic between CakePHPs core operations. Callbacks available include: beforeFilter(), executed before any controller action logic beforeRender(), executed after controller logic, but before the view is rendered afterFilter(), executed after all controller logic, including the view render. There may be no difference between afterRender() and afterFilter() unless youve manually made a call to render() in your controller action and have included some logic after that call.
Model Extensions (Behaviors)

Similarly, Behaviors work as ways to add common functionality between models. For example, if you store user data in a tree structure, you can specify your User model as behaving like a tree, and gain free functionality for removing, adding, and shifting nodes in your underlying tree structure. Models also are supported by another class called a DataSource. DataSources are an abstraction that enable models to manipulate different types of data consistently. While the main source of data in a CakePHP application is often a database, you might write additional DataSources that allow your models to represent RSS feeds, CSV les, LDAP entries, or iCal events. DataSources allow you to associate records from different sources: rather than being limited to SQL joins, DataSources allow you to tell your LDAP model that it is associated to many iCal events. Just like controllers, models are featured with callbacks as well: beforeFind() afterFind() beforeValidate() beforeSave() afterSave() beforeDelete() afterDelete() The names of these methods should be descriptive enough to let you know what they do. You can nd the details in the models chapter.

22

Chapter 1. Getting Started

CakePHP Cookbook Documentation, Release 2.x

View Extensions (Helpers)

A Helper is a class that aids in view logic. Much like a component used among controllers, helpers allow presentational logic to be accessed and shared between views. One of the core helpers, JsHelper, makes Ajax requests within views much easier and comes with support for jQuery (default), Prototype and Mootools. Most applications have pieces of view code that are used repeatedly. CakePHP facilitates view code reuse with layouts and elements. By default, every view rendered by a controller is placed inside a layout. Elements are used when small snippets of content need to be reused in multiple views.

Blog Tutorial - Adding a layer

23

CakePHP Cookbook Documentation, Release 2.x

24

Chapter 1. Getting Started

CHAPTER 2

Installation

CakePHP is fast and easy to install. The minimum requirements are a webserver and a copy of Cake, thats it! While this manual focuses primarily on setting up with Apache (because its the most common), you can congure Cake to run on a variety of web servers such as LightHTTPD or Microsoft IIS.

Requirements
HTTP Server. For example: Apache. mod_rewrite is preferred, but by no means required. PHP 5.2.8 or greater. Technically a database engine isnt required, but we imagine that most applications will utilize one. CakePHP supports a variety of database storage engines: MySQL (4 or greater) PostgreSQL Microsoft SQL Server SQLite Note: The built-in drivers all require PDO. You should make sure you have the correct PDO extensions installed.

License
CakePHP is licensed under the MIT license. This means that you are free to modify, distribute and republish the source code on the condition that the copyright notices are left intact. You are also free to incorporate CakePHP into any Commercial or closed source application.

25

CakePHP Cookbook Documentation, Release 2.x

Downloading CakePHP
There are two main ways to get a fresh copy of CakePHP. You can either download an archive copy (zip/tar.gz/tar.bz2) from the main website, or check out the code from the git repository. To download the latest major release of CakePHP. Visit the main website http://www.cakephp.org and follow the Download Now link. All current releases of CakePHP are hosted on Github (http://github.com/cakephp/cakephp). Github houses both CakePHP itself as well as many other plugins for CakePHP. The CakePHP releases are available at Github downloads (http://github.com/cakephp/cakephp/downloads). Alternatively you can get fresh off the press code, with all the bug-xes and up to the minute enhancements. These can be accessed from github by cloning the Github (http://github.com/cakephp/cakephp) repository:
git clone git://github.com/cakephp/cakephp.git

Permissions
CakePHP uses the app/tmp directory for a number of different operations. Model descriptions, cached views, and session information are just a few examples. As such, make sure the directory app/tmp and all its subdirectories in your cake installation are writable by the web server user.

Setup
Setting up CakePHP can be as simple as slapping it in your web servers document root, or as complex and exible as you wish. This section will cover the three main installation types for CakePHP: development, production, and advanced. Development: easy to get going, URLs for the application include the CakePHP installation directory name, and less secure. Production: Requires the ability to congure the web servers document root, clean URLs, very secure. Advanced: With some conguration, allows you to place key CakePHP directories in different parts of the lesystem, possibly sharing a single CakePHP core library folder amongst many CakePHP applications.

Development
A development installation is the fastest method to setup Cake. This example will help you install a CakePHP application and make it available at http://www.example.com/cake_2_0/. We assume for the purposes of this example that your document root is set to /var/www/html.

26

Chapter 2. Installation

CakePHP Cookbook Documentation, Release 2.x

Unpack the contents of the Cake archive into /var/www/html. You now have a folder in your document root named after the release youve downloaded (e.g. cake_2.0.0). Rename this folder to cake_2_0. Your development setup will look like this on the le system:
/var/www/html/ cake_2_0/ app/ lib/ plugins/ vendors/ .htaccess index.php README

If your web server is congured correctly, you should now nd your Cake application accessible at http://www.example.com/cake_2_0/.

Using one CakePHP checkout for multiple applications


If you are developing a number of applications, it often makes sense to have them share the same CakePHP core checkout. There are a few ways in which you can accomplish this. Often the easiest is to use PHPs include_path. To start off, clone CakePHP into a directory. For this example, well use ~/projects:
git clone git://github.com/cakephp/cakephp.git ~/projects/cakephp

This will clone CakePHP into your ~/projects directory. If you dont want to use git, you can download a zipball and the remaining steps will be the same. Next youll have to locate and modify your php.ini. On *nix systems this is often in /etc/php.ini, but using php -i and looking for Loaded Conguration File. Once youve found the correct ini le, modify the include_path conguration to include ~/projects/cakephp/lib. An example would look like:
include_path = .:/home/mark/projects/cakephp/lib:/usr/local/php/lib/php

After restarting your webserver, you should see the changes reected in phpinfo(). Note: If you are on windows, separate include paths with ; instead of : Having nished setting up your include_path your applications should be able to nd CakePHP automatically.

Production
A production installation is a more exible way to setup Cake. Using this method allows an entire domain to act as a single CakePHP application. This example will help you install Cake anywhere on your lesystem and make it available at http://www.example.com. Note that this installation may require the rights to change the DocumentRoot on Apache webservers.

Production

27

CakePHP Cookbook Documentation, Release 2.x

Unpack the contents of the Cake archive into a directory of your choosing. For the purposes of this example, we assume you choose to install Cake into /cake_install. Your production setup will look like this on the lesystem:
/cake_install/ app/ webroot/ (this directory is set as the DocumentRoot directive) lib/ plugins/ vendors/ .htaccess index.php README

Developers using Apache should set the DocumentRoot directive for the domain to:
DocumentRoot /cake_install/app/webroot

If your web server is congured correctly, you should now nd your Cake application accessible at http://www.example.com.

Advanced Installation and server specic conguration


Advanced Installation
There may be some situations where you wish to place CakePHPs directories on different places on the lesystem. This may be due to a shared host restriction, or maybe you just want a few of your apps to share the same Cake libraries. This section describes how to spread your CakePHP directories across a lesystem. First, realize that there are three main parts to a Cake application: 1. The core CakePHP libraries, in /lib/Cake. 2. Your application code, in /app. 3. The applications webroot, usually in /app/webroot. Each of these directories can be located anywhere on your le 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 youve put it. To congure your Cake installation, youll need to make some changes to the following les. /app/webroot/index.php /app/webroot/test.php (if you use the Testing feature.) There are three constants that youll 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 (base)name of your app folder. CAKE_CORE_INCLUDE_PATH should be set to the path of your CakePHP libraries folder. 28 Chapter 2. Installation

CakePHP Cookbook Documentation, Release 2.x

Lets run through an example so you can see what an advanced installation might look like in practice. Imagine that I wanted to set up CakePHP to work as follows: The CakePHP core libraries will be placed in /usr/lib/cake. My applications webroot directory will be /var/www/mysite/. My applications app directory will be /home/me/myapp. Given this type of setup, I would need to edit my webroot/index.php le (which will end up at /var/www/mysite/index.php, in this example) to look like the following:
// /app/webroot/index.php (partial, comments removed) if (!defined(ROOT)) { define(ROOT, DS . home . DS . me); } if (!defined(APP_DIR)) { define (APP_DIR, myapp); } if (!defined(CAKE_CORE_INCLUDE_PATH)) { define(CAKE_CORE_INCLUDE_PATH, DS . usr . DS . lib); }

It is recommended to use the DS constant rather than slashes to delimit le paths. This prevents any missing le errors you might get as a result of using the wrong delimiter, and it makes your code more portable. Apache and mod_rewrite (and .htaccess) While CakePHP is built to work with mod_rewrite out of the boxand usually doesweve noticed that a few users struggle with getting everything to play nicely on their systems. Here are a few things you might try to get it running correctly. First look at your httpd.conf (Make sure you are editing the system httpd.conf rather than a user- or site-specic httpd.conf). 1. Make sure that an .htaccess override is allowed and that AllowOverride is set to All for the correct DocumentRoot. You should see something similar to:
# Each directory to which Apache has access can be configured with respect # to which services and features are allowed and/or disabled in that # directory (and its subdirectories). # # First, we configure the "default" to be a very restrictive set of # features. # <Directory /> Options FollowSymLinks AllowOverride All # Order deny,allow # Deny from all </Directory>

Advanced Installation and server specic conguration

29

CakePHP Cookbook Documentation, Release 2.x

2. Make sure you are loading up mod_rewrite correctly. You should see something like:
LoadModule rewrite_module libexec/apache2/mod_rewrite.so

In many systems these will be commented out (by being prepended with a #) by default, so you may just need to remove those leading # symbols. After you make changes, restart Apache to make sure the settings are active. Verify that you your .htaccess les are actually in the right directories. This can happen during copying because some operating systems treat les that start with . as hidden and therefore wont see them to copy. 3. Make sure your copy of CakePHP is from the downloads section of the site or our GIT repository, and has been unpacked correctly by checking for .htaccess les. Cake root directory (needs to be copied to your document, this redirects everything to your Cake app):
<IfModule mod_rewrite.c> RewriteEngine on RewriteRule ^ $ app/webroot/ RewriteRule </IfModule>

[L]

(.*) app/webroot/ $ 1 [L]

Cake app directory (will be copied to the top directory of your application by bake):
<IfModule mod_rewrite.c> RewriteEngine on RewriteRule ^$ webroot/ RewriteRule </IfModule> (.*) webroot/ $ 1

[L] [L]

Cake webroot directory (will be copied to your applications web root by bake):
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*) $ index.php [QSA,L] </IfModule>

If your CakePHP site still has problems with mod_rewrite you might want to try and modify settings for virtualhosts. If on ubuntu, edit the le /etc/apache2/sites-available/default (location is distribution dependent). In this le, ensure that AllowOverride None is changed to AllowOverride All, so you have:
<Directory /> Options FollowSymLinks AllowOverride All </Directory> <Directory /var/www> Options Indexes FollowSymLinks MultiViews AllowOverride All Order Allow,Deny

30

Chapter 2. Installation

CakePHP Cookbook Documentation, Release 2.x

Allow from all </Directory>

If on Mac OSX, another solution is to use the tool virtualhostx to make a virtual host to point to your folder. For many hosting services (GoDaddy, 1and1), your web server is actually being served from a user directory that already uses mod_rewrite. If you are installing CakePHP into a user directory (http://example.com/~username/cakephp/), or any other URL structure that already utilizes mod_rewrite, youll need to add RewriteBase statements to the .htaccess les CakePHP uses (/.htaccess, /app/.htaccess, /app/webroot/.htaccess). This can be added to the same section with the RewriteEngine directive, so for example your webroot .htaccess le would look like:
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase /path/to/cake/app RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*) $ index.php [QSA,L] </IfModule>

The details of those changes will depend on your setup, and can include additional things that are not Cake related. Please refer to Apaches online documentation for more information. Pretty URLs on nginx nginx is a popular server that uses less system resources than Apache. Its drawback is that it does not make use of .htaccess les like Apache, so it is necessary to create those rewritten URLs in the site-available conguration. Depending upon your setup, you will have to modify this, but at the very least, you will need PHP running as a FastCGI instance.
server { listen 80; server_name www.example.com; rewrite ^(.*) http://example.com $ 1 permanent; } server { listen 80; server_name example.com; # root directive should be global root /var/www/example.com/public/app/webroot/; index index.php; access_log /var/www/example.com/log/access.log; error_log /var/www/example.com/log/error.log; location / { try_files $uri $uri/ /index.php?$uri&$args;

Advanced Installation and server specic conguration

31

CakePHP Cookbook Documentation, Release 2.x

} location ~ \.php $ { try_files $uri =404; include /etc/nginx/fastcgi_params; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }

URL Rewrites on IIS7 (Windows hosts) IIS7 does not natively support .htaccess les. While there are add-ons that can add this support, you can also import htaccess rules into IIS to use CakePHPs native rewrites. To do this, follow these steps: 1. Use Microsofts Web Platform Installer to install the URL Rewrite Module 2.0. 2. Create a new le in your CakePHP folder, called web.cong. 3. Using Notepad or another XML-safe editor, copy the following code into your new web.cong le...

<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="Imported Rule 1" stopProcessing="true"> <match url="^(.*)$" ignoreCase="false" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="tru <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> </conditions> <action type="Rewrite" url="index.php?url={R:1}" appendQueryString="true" /> </rule>

<rule name="Imported Rule 2" stopProcessing="true"> <match url="^$" ignoreCase="false" /> <action type="Rewrite" url="/" /> </rule> <rule name="Imported Rule 3" stopProcessing="true"> <match url="(.*)" ignoreCase="false" /> <action type="Rewrite" url="/{R:1}" /> </rule> <rule name="Imported Rule 4" stopProcessing="true"> <match url="^(.*)$" ignoreCase="false" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="tru <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> </conditions> <action type="Rewrite" url="index.php/{R:1}" appendQueryString="true" />

32

Chapter 2. Installation

CakePHP Cookbook Documentation, Release 2.x

</rule> </rules> </rewrite> </system.webServer> </configuration>

It is also possible to use the Import functionality in IISs URL Rewrite module to import rules directly from CakePHPs .htaccess les in root, /app/, and /app/webroot/ - although some editing within IIS may be necessary to get these to work. When Importing the rules this way, IIS will automatically create your web.cong le for you. Once the web.cong le is created with the correct IIS-friendly rewrite rules, CakePHPs links, css, js, and rerouting should work correctly.

Fire It Up
Alright, lets see CakePHP in action. Depending on which setup you used, you should point your browser to http://example.com/ or http://example.com/cake_install/. At this point, youll be presented with CakePHPs default home, and a message that tells you the status of your current database connection. Congratulations! You are ready to create your rst CakePHP application. Not working? If youre getting timezone related error from PHP uncomment one line in app/Config/core.php:
/** * Uncomment this line and correct your server timezone to fix * any date & time related errors. */ date_default_timezone_set(UTC);

Fire It Up

33

CakePHP Cookbook Documentation, Release 2.x

34

Chapter 2. Installation

CHAPTER 3

CakePHP Overview

Welcome to the Cookbook, the manual for the CakePHP web application framework that makes developing a piece of cake! This manual assumes that you have a general understanding of PHP and a basic understanding of objectoriented programming (OOP). Different functionality within the framework makes use of different technologies such as SQL, JavaScript, and XML and this manual does not attempt to explain those technologies, only how they are used in context.

What is CakePHP? Why Use it?


CakePHP (http://www.cakephp.org/) is a free (http://en.wikipedia.org/wiki/MIT_License), open-source (http://en.wikipedia.org/wiki/Open_source), rapid development (http://en.wikipedia.org/wiki/Rapid_application_development) framework (http://en.wikipedia.org/wiki/Application_framework) for PHP (http://www.php.net/). Its a foundational structure for programmers to create web applications. Our primary goal is to enable you to work in a structured and rapid mannerwithout loss of exibility. CakePHP takes the monotony out of web development. We provide you with all the tools you need to get started coding what you really need to get done: the logic specic to your application. Instead of reinventing the wheel every time you sit down to a new project, check out a copy of CakePHP and get started with the real guts of your application. CakePHP has an active developer team (http://cakephp.lighthouseapp.com/contributors) and community, bringing great value to the project. In addition to keeping you from wheel-reinventing, using CakePHP means your applications core is well tested and is being constantly improved. Heres a quick list of features youll enjoy when using CakePHP: Active, friendly community (http://cakephp.org/feeds) Flexible licensing (http://en.wikipedia.org/wiki/MIT_License) Compatible with versions PHP 5.2.8 and greater.

35

CakePHP Cookbook Documentation, Release 2.x

Integrated CRUD (http://en.wikipedia.org/wiki/Create,_read,_update_and_delete) for database interaction. Application scaffolding (http://en.wikipedia.org/wiki/Scaffold_(programming)). Code generation. MVC (http://en.wikipedia.org/wiki/Model-view-controller) architecture. Request dispatcher with clean, custom URLs and routes. Built-in validation (http://en.wikipedia.org/wiki/Data_validation). Fast and exible templating (http://en.wikipedia.org/wiki/Web_template_system) (PHP syntax, with helpers). View Helpers for AJAX, JavaScript, HTML Forms and more. Email, Cookie, Security, Session, and Request Handling Components. Flexible ACL (http://en.wikipedia.org/wiki/Access_control_list). Data Sanitization. Flexible Caching (http://en.wikipedia.org/wiki/Web_cache). Localization. Works from any web site directory, with little to no Apache (http://httpd.apache.org/) conguration involved.

Understanding Model-View-Controller
CakePHP follows the MVC (http://en.wikipedia.org/wiki/Model-view-controller) software design pattern. Programming using MVC separates your application into three main parts:

The Model layer


The Model layer represents the part of your application that implements the business logic. It is responsible for retrieving data and converting it into meaningful concepts for your application. This includes processing, validating, associating or other tasks related to handling data. At a rst glance, Model objects can be looked at as the rst layer of interaction with any database you might be using for your application. But in general they stand for the major concepts around which you implement your application. In the case of a social network, the Model layer would take care of tasks such as Saving the user data, saving friends associations, storing and retrieving user photos, nding new friends for suggestions, etc. While the model objects can be thought as Friend, User, Comment, or Photo.

36

Chapter 3. CakePHP Overview

CakePHP Cookbook Documentation, Release 2.x

The View layer


The View renders a presentation of modeled data. Being separated from the Model objects, it is responsible for using the information it has available to produce any presentational interface your application might need. For example, as the Model layer returns a set of data, the view would use it to render a HTML page containing it. Or a XML formatted result for others to consume. The View layer is not only limited to HTML or text representation of the data, it can be used to deliver a wide variety of formats depending on your needs, such as videos, music, documents and any other format you can think of.

The Controller layer


The Controller layer handles requests from users. Its responsible for rendering back a response with the aid of both the Model and the View Layer. Controllers can be seen as managers taking care that all needed resources for completing a task are delegated to the correct workers. It waits for petitions from clients, checks their validity according to authentication or authorization rules, delegates data fetching or processing to the model, and selects the correct type of presentational data that the client is accepting, to nally delegate this rendering process to the View layer.

CakePHP request cycle

Figure: 1: A typical MVC Request in CakePHP The typical CakePHP request cycle starts with a user requesting a page or resource in your application. This request is rst processed by a dispatcher which will select the correct controller object to handle it. Once the request arrives at the controller, it will communicate with the Model layer to process any data fetching or saving operation that might be needed. After this communication is over, the controller will

Understanding Model-View-Controller

37

CakePHP Cookbook Documentation, Release 2.x

proceed at delegating to the correct view object the task of generating an output resulting from the data provided by the model. Finally, when this output is generated, it is immediately rendered to the user Almost every request to your application will follow this basic pattern. Well add some details later on which are specic to CakePHP, so keep this in mind as we proceed.

Benets
Why use MVC? Because it is a tried and true software design pattern that turns an application into a maintainable, modular, rapidly developed package. Crafting application tasks into separate models, views, and controllers makes your application very light on its feet. New features are easily added, and new faces on old features are a snap. The modular and separate design also allows developers and designers to work simultaneously, including the ability to rapidly prototype (http://en.wikipedia.org/wiki/Software_prototyping). Separation also allows developers to make changes in one part of the application without affecting the others. If youve never built an application this way, it takes some time getting used to, but were condent that once youve built your rst application using CakePHP, you wont want to do it any other way. To get started on your rst CakePHP application, try the blog tutorial now

Where to Get Help


The Ofcial CakePHP website
http://www.cakephp.org The Ofcial CakePHP website is always a great place to visit. It features links to oft-used developer tools, screencasts, donation opportunities, and downloads.

The Cookbook
http://book.cakephp.org This manual should probably be the rst place you go to get answers. As with many other open source projects, we get new folks regularly. Try your best to answer your questions on your own rst. Answers may come slower, but will remain longer and youll also be lightening our support load. Both the manual and the API have an online component.

The Bakery
http://bakery.cakephp.org The CakePHP Bakery is a clearing house for all things CakePHP. Check it out for tutorials, case studies, and code examples. Once youre acquainted with CakePHP, log on and share your knowledge with the community and gain instant fame and fortune. 38 Chapter 3. CakePHP Overview

CakePHP Cookbook Documentation, Release 2.x

The API
http://api20.cakephp.org/ Straight to the point and straight from the core developers, the CakePHP API (Application Programming Interface) is the most comprehensive documentation around for all the nitty gritty details of the internal workings of the framework. Its a straight forward code reference, so bring your propeller hat.

The Test Cases


If you ever feel the information provided in the API is not sufcient, check out the code of the test cases provided with CakePHP. They can serve as practical examples for function and data member usage for a class.:
lib/Cake/Test/Case

The IRC channel


IRC Channels on irc.freenode.net: #cakephp General Discussion #cakephp-docs Documentation #cakephp-bakery Bakery If youre stumped, give us a holler in the CakePHP IRC channel. Someone from the development team is usually there, especially during the daylight hours for North and South America users. Wed love to hear from you, whether you need some help, want to nd users in your area, or would like to donate your brand new sports car.

The Google Group


http://groups.google.com/group/cake-php CakePHP also has a very active Google Group. It can be a great resource for nding archived answers, frequently asked questions, and getting answers to immediate problems.

CakePHP Questions
http://ask.cakephp.org/ Simply register/login and ask a question. Wait until youve got some answers and pick the correct answer. You can view, comment and vote on previously asked and solved questions as well.

Where to Get Help

39

CakePHP Cookbook Documentation, Release 2.x

40

Chapter 3. CakePHP Overview

CHAPTER 4

Controllers

Controllers are the C in MVC. After routing has been applied and the correct controller has been found, your controllers action is called. Your controller should handle interpreting the request data, making sure the correct models are called, and the right response or view is rendered. Controllers can be thought of as middle man between the Model and View. You want to keep your controllers thin, and your models fat. This will help you more easily reuse your code and makes your code easier to test. Commonly, controllers are used to manage the logic around a single model. For example, if you were building a site for an on-line bakery, you might have a RecipesController and an IngredientsController managing your recipes and their ingredients. In CakePHP, controllers are named after the primary model they handle. Its totally possible to have controllers work with more than one model as well. Your applications controllers extend AppController class, which in turn extends the core Controller class. The AppController class can be dened in /app/Controller/AppController.php and it should contain methods that are shared between all of your applications controllers. Controllers provide a number of methods which are called actions. Actions are methods on a controller that handle requests. By default all public methods on a controller are an action, and accessible from a url. Actions are responsible for interpreting the request and creating the response. Usually responses are in the form of a rendered view, but there are other ways to create responses as well.

The App Controller


As stated in the introduction, the AppController class is the parent class to all of your applications controllers. AppController itself extends the Controller class included in the CakePHP core library. As such, AppController is dened in /app/Controller/AppController.php like so:
class AppController extends Controller { }

Controller attributes and methods created in your AppController will be available to all of your applications controllers. It is the ideal place to create code that is common to all of your controllers. Components (which youll learn about later) are best used for code that is used in many (but not necessarily all) controllers. 41

CakePHP Cookbook Documentation, Release 2.x

While normal object-oriented inheritance rules apply, CakePHP does a bit of extra work when it comes to special controller attributes. The list of components and helpers used by a controller are treated specially. In these cases, AppController value arrays are merged with child controller class arrays. The values in the child class will always override those in AppController. Note: CakePHP merges the following variables from the AppController to your applications controllers: $components $helpers $uses Remember to add the default Html and Form helpers, if you dene var $helpers in your AppController Please also remember to call AppControllers callbacks within child controller callbacks for best results:
public function beforeFilter() { parent::beforeFilter(); }

Request parameters
When a request is made to a CakePHP application, CakePHPs Router and Dispatcher classes use Routes Conguration to nd and create the correct controller. The request data is encapsulated into a request object. CakePHP puts all of the important request information into the $this->request property. See the section on CakeRequest for more information on the CakePHP request object.

Controller actions
Controller actions are responsible for converting the request parameters into a response for the browser/user making the request. CakePHP uses conventions to automate this process and remove some boiler-plate code you would otherwise need to write. By convention CakePHP renders a view with an inected version of the action name. Returning to our online bakery example, our RecipesController might contain the view(), share(), and search() actions. The controller would be found in /app/Controller/RecipesController.php and contain:
# /app/Controller/RecipesController.php class RecipesController extends AppController { public function view($id) { //action logic goes here.. } public function share($customerId, $recipeId) { //action logic goes here.. }

42

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

public function search($query) { //action logic goes here.. } }

The view les for these actions would be app/View/Recipes/view.ctp, app/View/Recipes/share.ctp, and app/View/Recipes/search.ctp. The conventional view le name is the lower cased and underscored version of the action name. Controller actions generally use set() to create a context that View uses to render the view. Because of the conventions that CakePHP uses, you dont need to create and render the view manually. Instead once a controller action has completed, CakePHP will handle rendering and delivering the View. If for some reason youd like to skip the default behavior. Both of the following techniques will by-pass the default view rendering behavior. If you return a string, or an object that can be converted to a string from your controller action, it will be used as the response body. You can return a CakeResponse object with the completely created response. When controller methods are used with requestAction() you will often want to return data that isnt a string. If you have controller methods that are used for normal web requests + requestAction you should check the request type before returning:
class RecipesController extends AppController { public function popular() { $popular = $this->Recipe->popular(); if (!empty($this->request->params[requested])) { return $popular; } $this->set(popular, $popular); } }

The above controller action is an example of how a method can be used with requestAction() and normal requests. Returning an array data to a non-requestAction request will cause errors and should be avoided. See the section on Controller::requestAction() for more tips on using requestAction() In order for you to use a controller effectively in your own application, well cover some of the core attributes and methods provided by CakePHPs controllers.

Request Life-cycle callbacks


class Controller CakePHP controllers come tted with callbacks you can use to insert logic around the request life-cycle: Controller::beforeFilter() This function is executed before every action in the controller. Its a handy place to check for an active session or inspect user permissions. Request Life-cycle callbacks 43

CakePHP Cookbook Documentation, Release 2.x

Note: The beforeFilter() method will be called for missing actions, and scaffolded actions. Controller::beforeRender() Called after controller action logic, but before the view is rendered. This callback is not used often, but may be needed if you are calling render() manually before the end of a given action. Controller::afterFilter() Called after every controller action, and after rendering is complete. This is the last controller method to run. In addition to controller life-cycle callbacks, Components also provide a similar set of callbacks.

Controller Methods
For a complete list of controller methods and their descriptions visit the CakePHP API. Check out http://api20.cakephp.org/class/controller.

Interacting with Views


Controllers interact with the view in a number of ways. First they are able to pass data to the views, using set(). You can also decide which view class to use, and which view le should be rendered from the controller. Controller::set(string $var, mixed $value) The set() method is the main way to send data from your controller to your view. Once youve used set(), the variable can be accessed in your view:
// First you pass data from the controller: $this->set(color, pink); // Then, in the view, you can utilize the data: ?> You have selected <?php echo $color; ?> icing for the cake.

The set() method also takes an associative array as its rst parameter. This can often be a quick way to assign a set of information to the view. Changed in version 1.3: Array keys will no longer be inected before they are assigned to the view (underscored_key does not become underscoredKey anymore, etc.):
$data = array( color => pink, type => sugar, base_price => 23.95 ); // make $color, $type, and $base_price

44

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

// available to the view: $this->set($data);

The attribute $pageTitle no longer exists, use set() to set the title:
$this->set(title_for_layout, This is the page title);

Controller::render(string $view, string $layout) The render() method is automatically called at the end of each requested controller action. This method performs all the view logic (using the data youve given in using the set() method), places the view inside its layout and serves it back to the end user. The default view le used by render is determined by convention. If the search() action of the RecipesController is requested, the view le in /app/View/Recipes/search.ctp will be rendered:
class RecipesController extends AppController { // ... public function search() { // Render the view in /View/Recipes/search.ctp $this->render(); } // ... }

Although CakePHP will automatically call it (unless youve set $this->autoRender to false) after every actions logic, you can use it to specify an alternate view le by specifying an action name in the controller using $action. If $view starts with / it is assumed to be a view or element le relative to the /app/View folder. This allows direct rendering of elements, very useful in ajax calls.
// Render the element in /View/Elements/ajaxreturn.ctp $this->render(/Elements/ajaxreturn);

The $layout parameter allows you to specify the layout the view is rendered in. Rendering a specic view In your controller you may want to render a different view than what would conventionally be done. You can do this by calling render() directly. Once you have called render() CakePHP will not try to re-render the view:
class PostsController extends AppController { public function my_action() { $this->render(custom_file); } }

This would render app/View/Posts/custom_file.ctp app/View/Posts/my_action.ctp

instead

of

Controller Methods

45

CakePHP Cookbook Documentation, Release 2.x

Flow Control
Controller::redirect(mixed $url, integer $status, boolean $exit) The ow control method youll use most often is redirect(). This method takes its rst parameter in the form of a CakePHP-relative URL. When a user has successfully placed an order, you might wish to redirect them to a receipt screen.:
public function place_order() { // Logic for finalizing order goes here if ($success) { $this->redirect(array(controller => orders, action => thanks)); } else { $this->redirect(array(controller => orders, action => confirm)); } }

You can also use a relative or absolute URL as the $url argument:
$this->redirect(/orders/thanks)); $this->redirect(http://www.example.com);

You can also pass data to the action:


$this->redirect(array(action => edit, $id));

The second parameter of redirect() allows you to dene an HTTP status code to accompany the redirect. You may want to use 301 (moved permanently) or 303 (see other), depending on the nature of the redirect. The method will issue an exit() after the redirect unless you set the third parameter to false. If you need to redirect to the referer page you can use:
$this->redirect($this->referer());

The method also supports name based parameters. If you want to redirect to a URL like: http://www.example.com/orders/confirm/product:pizza/quantity:5 you can use:

$this->redirect(array(controller => orders, action => confirm, product => p

Controller::flash(string $message, string $url, integer $pause, string $layout) Like redirect(), the flash() method is used to direct a user to a new page after an operation. The flash() method is different in that it shows a message before passing the user on to another URL. The rst parameter should hold the message to be displayed, and the second parameter is a CakePHPrelative URL. CakePHP will display the $message for $pause seconds before forwarding the user on. If theres a particular template youd like your ashed message to use, you may specify the name of that layout in the $layout parameter. For in-page ash messages, be sure to check out SessionComponents setFlash() method.

46

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Callbacks
In addition to the Request Life-cycle callbacks. CakePHP also supports callbacks related to scaffolding. Controller::beforeScaffold($method) $method name of method called example index, edit, etc. Controller::afterScaffoldSave($method) $method name of method called either edit or update. Controller::afterScaffoldSaveError($method) $method name of method called either edit or update. Controller::scaffoldError($method) $method name of method called example index, edit, etc.

Other Useful Methods


Controller::constructClasses() This method loads the models required by the controller. This loading process is done by CakePHP normally, but this method is handy to have when accessing controllers from a different perspective. If you need CakePHP in a command-line script or some other outside use, constructClasses() may come in handy. Controller::referer(mixed $default = null, boolean $local = false) Returns the referring URL for the current request. Parameter $default can be used to supply a default URL to use if HTTP_REFERER cannot be read from headers. So, instead of doing this:
class UserController extends AppController { public function delete($id) { // delete code goes here, and then... if ($this->referer() != /) { $this->redirect($this->referer()); } else { $this->redirect(array(action => index)); } } }

you can do this:


class UserController extends AppController { public function delete($id) { // delete code goes here, and then... $this->redirect($this->referer(array(action => index))); } }

If $default is not set, the function defaults to the root of your domain - /. Parameter $local if set to true, restricts referring URLs to local server.

Controller Methods

47

CakePHP Cookbook Documentation, Release 2.x

Controller::disableCache() Used to tell the users browser not to cache the results of the current request. This is different than view caching, covered in a later chapter. The headers sent to this effect are:
Expires: Mon, 26 Jul 1997 05:00:00 GMT Last-Modified: [current datetime] GMT Cache-Control: no-store, no-cache, must-revalidate Cache-Control: post-check=0, pre-check=0 Pragma: no-cache

Controller::postConditions(array $data, mixed $op, string $bool, boolean $exclusive) Use this method to turn a set of POSTed model data (from HtmlHelper-compatible inputs) into a set of nd conditions for a model. This function offers a quick shortcut on building search logic. For example, an administrative user may want to be able to search orders in order to know which items need to be shipped. You can use CakePHPs FormHelper and HtmlHelper to create a quick form based on the Order model. Then a controller action can use the data posted from that form to craft nd conditions:
public function index() { $conditions = $this->postConditions($this->request->data); $orders = $this->Order->find(all, compact(conditions)); $this->set(orders, $orders); }

If $this->request->data[Order][destination] equals Old Towne Bakery, postConditions converts that condition to an array compatible for use in a Model->nd() method. In this case, array(Order.destination => Old Towne Bakery). If you want to use a different SQL operator between terms, supply them using the second parameter:
/* Contents of $this->request->data array( Order => array( num_items => 4, referrer => Ye Olde ) ) */ // Lets get orders that have at least 4 items and contain Ye Olde $conditions = $this->postConditions( $this->request->data, array( num_items => >=, referrer => LIKE ) ); $orders = $this->Order->find(all, compact(conditions));

The third parameter allows you to tell CakePHP what SQL boolean operator to use between the nd conditions. Strings like AND, OR and XOR are all valid values. 48 Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Finally, if the last parameter is set to true, and the $op parameter is an array, elds not included in $op will not be included in the returned conditions. Controller::paginate() This method is used for paginating results fetched by your models. You can specify page sizes, model nd conditions and more. See the pagination section for more details on how to use paginate. Controller::requestAction(string $url, array $options) This function calls a controllers action from any location and returns data from the action. The $url passed is a CakePHP-relative URL (/controllername/actionname/params). To pass extra data to the receiving controller action add to the $options array. Note: You can use requestAction() to retrieve a fully rendered view by passing return in the options: requestAction($url, array(return));. It is important to note that making a requestAction using return from a controller method can cause script and css tags to not work correctly. Warning: If used without caching requestAction can lead to poor performance. It is rarely appropriate to use in a controller or model. requestAction is best used in conjunction with (cached) elements as a way to fetch data for an element before rendering. Lets use the example of putting a latest comments element in the layout. First we need to create a controller function that will return the data:

// Controller/CommentsController.php class CommentsController extends AppController { public function latest() { if (empty($this->request->params[requested])) { throw new ForbiddenException(); } return $this->Comment->find(all, array(order => Comment.created DESC, l } }

You should always include checks to make sure your requestAction methods are actually originating from requestAction. Failing to do so will allow requestAction methods to be directly accessible from a URL, which is generally undesirable. If we now create a simple element to call that function:
// View/Elements/latest_comments.ctp $comments = $this->requestAction(/comments/latest); foreach ($comments as $comment) { echo $comment[Comment][title]; }

We can then place that element anywhere to get the output using:
echo $this->element(latest_comments);

Controller Methods

49

CakePHP Cookbook Documentation, Release 2.x

Written in this way, whenever the element is rendered, a request will be made to the controller to get the data, the data will be processed, and returned. However in accordance with the warning above its best to make use of element caching to prevent needless processing. By modifying the call to element to look like this:
echo $this->element(latest_comments, array(cache => +1 hour));

The requestAction call will not be made while the cached element view le exists and is valid. In addition, requestAction now takes array based cake style urls:
echo $this->requestAction( array(controller => articles, action => featured), array(return) );

This allows the requestAction call to bypass the usage of Router::url which can increase performance. The url based arrays are the same as the ones that HtmlHelper::link() uses with one difference - if you are using named or passed parameters, you must put them in a second array and wrap them with the correct key. This is because requestAction merges the named args array (requestActions 2nd parameter) with the Controller::params member array and does not explicitly place the named args array into the key named; Additional members in the $option array will also be made available in the requested actions Controller::params array:
echo $this->requestAction(/articles/featured/limit:3); echo $this->requestAction(/articles/view/5);

As an array in the requestAction would then be:


echo $this->requestAction( array(controller => articles, action => featured), array(named => array(limit => 3)) ); echo $this->requestAction( array(controller => articles, action => view), array(pass => array(5)) );

Note: Unlike other places where array urls are analogous to string urls, requestAction treats them differently. When using an array url in conjunction with requestAction() you must specify all parameters that you will need in the requested action. This includes parameters like $this->request->data. In addition to passing all required parameters, named and pass parameters must be done in the second array as seen above. Controller::loadModel(string $modelClass, mixed $id) The loadModel function comes handy when you need to use a model which is not the controllers default model or its associated model:

50

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

$this->loadModel(Article); $recentArticles = $this->Article->find(all, array(limit => 5, order => Article. $this->loadModel(User, 2); $user = $this->User->read();

Controller Attributes
For a complete list of controller attributes and their descriptions visit the CakePHP API. Check out http://api20.cakephp.org/class/controller. property Controller::$name The $name attribute should be set to the name of the controller. Usually this is just the plural form of the primary model the controller uses. This property is not required, but saves CakePHP from inecting it:
// $name controller attribute usage example class RecipesController extends AppController { public $name = Recipes; }

$components, $helpers and $uses


The next most often used controller attributes tell CakePHP what helpers, components, and models youll be using in conjunction with the current controller. Using these attributes make MVC classes given by $components and $uses available to the controller as class variables ($this->ModelName, for example) and those given by $helpers to the view as an object reference variable ($this->{$helpername}). Note: Each controller has some of these classes available by default, so you may not need to congure your controller at all. property Controller::$uses Controllers have access to their primary model available by default. Our RecipesController will have the Recipe model class available at $this->Recipe, and our ProductsController also features the Product model at $this->Product. However, when allowing a controller to access additional models through the $uses variable, the name of the current controllers model must also be included. This is illustrated in the example below. If you do not wish to use a Model in your controller, set public $uses = array(). This will allow you to use a controller without a need for a corresponding Model le. However, the models dened in the AppController will still be loaded. You can also use false to not load any models at all. Even those dened in the AppController Changed in version 2.1: Uses now has a new default value, it also handles false differently. property Controller::$helpers The Html, Form, and Session Helpers are available by default, as is the SessionComponent. But if you Controller Attributes 51

CakePHP Cookbook Documentation, Release 2.x

choose to dene your own $helpers array in AppController, make sure to include Html and Form if you want them still available by default in your Controllers. To learn more about these classes, be sure to check out their respective sections later in this manual. Lets look at how to tell a CakePHP controller that you plan to use additional MVC classes:
class RecipesController extends AppController { public $uses = array(Recipe, User); public $helpers = array(Js); public $components = array(RequestHandler); }

Each of these variables are merged with their inherited values, therefore it is not necessary (for example) to redeclare the Form helper, or anything that is declared in your App controller. property Controller::$components The components array allows you to set which Components a controller will use. Like $helpers and $uses components in your controllers are merged with those in AppController. As with $helpers you can pass settings into components. See Conguring Components for more information.

Other Attributes
While you can check out the details for all controller attributes in the API, there are other controller attributes that merit their own sections in the manual.

More on controllers
Request and Response objects
New in CakePHP 2.0 are request and response objects. In previous versions these objects were represented through arrays, and the related methods were spread across RequestHandlerComponent, Router, Dispatcher and Controller. There was no authoritative object on what information the request contained. For 2.0, CakeRequest and CakeResponse are used for this purpose.

CakeRequest
CakeRequest is the default request object used in CakePHP. It centralizes a number of features for interrogating and interacting with request data. On each request one CakeRequest is created and then passed by reference to the various layers of an application that use request data. By default CakeRequest is assigned to $this->request, and is available in Controller, Views and Helpers. You can also access it in Components by using the controller reference. Some of the duties CakeRequest performs include: Process the GET, POST, and FILES arrays into the data structures you are familiar with. Provide environment introspection pertaining to the request. Things like the headers sent, the clients IP address, and the subdomain/domain information about the application the server is running on.

52

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Provide access to request parameters both as array indices and object properties. Accessing request parameters CakeRequest exposes several interfaces for accessing request parameters. The rst is as object properties, the second is array indexes, and the third is through $this->request->params:
$this->request->controller; $this->request[controller]; $this->request->params[controller];

All of the above will both access the same value. Multiple ways of accessing the parameters was done to ease migration for existing applications. All Route elements are accessed through this interface. In addition to Route elements you also often need access to Passed arguments and Named parameters. These are both available on the request object as well:
// Passed arguments $this->request->pass; $this->request[pass]; $this->request->params[pass]; // named parameters $this->request->named; $this->request[named]; $this->request->params[named];

Will all provide you access to the passed arguments and named parameters. There are several important/useful parameters that CakePHP uses internally, these are also all found in the request parameters: plugin The plugin handling the request, will be null for no plugin. controller The controller handling the current request. action The action handling the current request. prefix The prex for the current action. See Prex Routing for more information. bare Present when the request came from requestAction() and included the bare option. Bare requests do not have layouts rendered. requested Present and set to true when the action came from requestAction. Accessing Querystring parameters Querystring parameters can be read from using CakeRequest::$query:
// url is /posts/index?page=1&sort=title $this->request->query[page]; // You can also access it via array access $this->request[url][page]; // BC accessor, will be deprecated in future versions

More on controllers

53

CakePHP Cookbook Documentation, Release 2.x

You can either directly access the query property, or you can use CakeRequest::query() to read the url query array in an error free manner. Any keys that do not exist will return null:
$foo = $this->request->query(value_that_does_not_exist); // $foo === null

Accessing POST data All POST data can be accessed using CakeRequest::$data. Any form data that contains a data prex, will have that data prex removed. For example:
// An input with a name attribute equal to data[MyModel][title] is accessible at $this->request->data[MyModel][title];

You can either directly access the data property, or you can use CakeRequest::data() to read the data array in an error free manner. Any keys that do not exist will return null:
$foo = $this->request->data(Value.that.does.not.exist); // $foo == null

Accessing PUT or POST data New in version 2.2. When building REST services you often accept request data on PUT and DELETE requests. As of 2.2 any application/x-www-form-urlencoded request body data will automatically be parsed and set to $this->data for PUT and DELETE requests. If you are accepting JSON or XML data, see below for how you can access those request bodies. Accessing XML or JSON data Applications employing REST often exchange data in non URL encoded post bodies. You can read input data in any format using CakeRequest::input(). By providing a decoding function you can receive the content in a deserialized format:
// Get JSON encoded data submitted to a PUT/POST action $data = $this->request->input(json_decode);

Since some deserializing methods require additional parameters when being called, such as the as array parameter on json_decode or if you want XML converted into a DOMDocument object, CakeRequest::input() supports passing in additional parameters as well:
// Get Xml encoded data submitted to a PUT/POST action $data = $this->request->input(Xml::build, array(return => domdocument));

Accessing path information CakeRequest also provides useful information about the paths in your application. CakeRequest::$base and CakeRequest::$webroot are useful for generating urls, and determining whether or not your application is in a subdirectory. 54 Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Inspecting the request Detecting various request conditions used to require using RequestHandlerComponent. These methods have been moved to CakeRequest, and offer a new interface alongside a more backwards compatible usage:
$this->request->is(post); $this->request->isPost();

Both method calls will return the same value. For the time being the methods are still available on RequestHandler, but are deprecated and still might be removed before the nal release. You can also easily extend the request detectors that are available, by using CakeRequest::addDetector() to create new kinds of detectors. There are four different types of detectors that you can create: Environment value comparison - An environment value comparison, compares a value fetched from env() to a known value the environment value is equality checked against the provided value. Pattern value comparison - Pattern value comparison allows you to compare a value fetched from env() to a regular expression. Option based comparison - Option based comparisons use a list of options to create a regular expression. Subsequent calls to add an already dened options detector will merge the options. Callback detectors - Callback detectors allow you to provide a callback type to handle the check. The callback will receive the request object as its only parameter. Some examples would be:
// Add an environment detector. $this->request->addDetector(post, array(env => REQUEST_METHOD, value => POST));

// Add a pattern value detector. $this->request->addDetector(iphone, array(env => HTTP_USER_AGENT, pattern => /iPho // Add an option detector $this->request->addDetector(internalIp, array( env => CLIENT_IP, options => array(192.168.0.101, 192.168.0.100) )); // Add a callback detector. Can either be an anonymous function or a regular callable. $this->request->addDetector(awesome, array(callback => function ($request) { return isset($request->awesome); }));

CakeRequest also includes methods like CakeRequest::domain(), CakeRequest::subdomains() and CakeRequest::host() to help applications with subdomains, have a slightly easier life. There are several built-in detectors that you can use: is(get) Check to see if the current request is a GET. is(put) Check to see if the current request is a PUT.

More on controllers

55

CakePHP Cookbook Documentation, Release 2.x

is(post) Check to see if the current request is a POST. is(delete) Check to see if the current request is a DELETE. is(head) Check to see if the current request is HEAD. is(options) Check to see if the current request is OPTIONS. is(ajax) Check to see of the current request came with X-Requested-with = XmlHttpRequest. is(ssl) Check to see if the request is via SSL is(flash) Check to see if the request has a User-Agent of Flash is(mobile) Check to see if the request came from a common list of mobile agents. CakeRequest and RequestHandlerComponent Since many of the features CakeRequest offers used to be the realm of RequestHandlerComponent some rethinking was required to gure out how it still ts into the picture. For 2.0, RequestHandlerComponent acts as a sugar daddy. Providing a layer of sugar on top of the utility CakeRequest affords. Sugar like switching layout and views based on content types or ajax is the domain of RequestHandlerComponent. This separation of utility and sugar between the two classes lets you more easily pick and choose what you want and what you need. Interacting with other aspects of the request You can use CakeRequest to introspect a variety of things about the request. Beyond the detectors, you can also nd out other information from various properties and methods. $this->request->webroot contains the webroot directory. $this->request->base contains the base path. $this->request->here contains the full address to the current request $this->request->query contains the query string parameters. CakeRequest API class CakeRequest CakeRequest encapsulates request parameter handling, and introspection. CakeRequest::domain($tldLength = 1) Returns the domain name your application is running on. CakeRequest::subdomains($tldLength = 1) Returns the subdomains your application is running on as an array. CakeRequest::host() Returns the host your application is on.

56

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

CakeRequest::method() Returns the HTTP method the request was made with. CakeRequest::onlyAllow($methods) Set allowed HTTP methods, if not matched will throw MethodNotAllowexException The 405 response will include the required Allow header with the passed methods New in version 2.3. CakeRequest::referer($local = false) Returns the referring address for the request. CakeRequest::clientIp($safe = true) Returns the current visitors IP address. CakeRequest::header($name) Allows you to access any of the HTTP_* headers that were used for the request:
$this->request->header(User-Agent);

Would return the user agent used for the request. CakeRequest::input($callback[, $options ]) Retrieve the input data for a request, and optionally pass it through a decoding function. Additional parameters for the decoding function can be passed as arguments to input(). CakeRequest::data($name) Provides dot notation access to request data. Allows for reading and modication of request data, calls can be chained together as well:
// Modify some request data, so you can prepopulate some form fields. $this->request->data(Post.title, New post) ->data(Comment.1.author, Mark); // You can also read out data. $value = $this->request->data(Post.title);

CakeRequest::query($name) Provides dot notation access to url query data:


// url is /posts/index?page=1&sort=title $value = $this->request->query(page);

New in version 2.3. CakeRequest::is($type) Check whether or not a Request matches a certain criteria. Uses the built-in detection rules as well as any additional rules dened with CakeRequest::addDetector(). CakeRequest::addDetector($name, $options) Add a detector to be used with is(). See Inspecting the request for more information. CakeRequest::accepts($type = null) Find out which content types the client accepts or check if they accept a particular type of content. Get all types:

More on controllers

57

CakePHP Cookbook Documentation, Release 2.x

$this->request->accepts();

Check for a single type:


$this->request->accepts(application/json);

static CakeRequest::acceptLanguage($language = null) Get either all the languages accepted by the client, or check if a specic language is accepted. Get the list of accepted languages:
CakeRequest::acceptLanguage();

Check if a specic language is accepted:


CakeRequest::acceptLanguage(es-es);

property CakeRequest::$data An array of POST data. You can use CakeRequest::data() to read this property in a way that suppresses notice errors. property CakeRequest::$query An array of query string parameters. property CakeRequest::$params An array of route elements and request parameters. property CakeRequest::$here Returns the current request uri. property CakeRequest::$base The base path to the application, usually / unless your application is in a subdirectory. property CakeRequest::$webroot The current webroot.

CakeResponse
CakeResponse is the default response class in CakePHP. It encapsulates a number of features and functionality for generating HTTP responses in your application. It also assists in testing, as it can be mocked/stubbed allowing you to inspect headers that will be sent. Like CakeRequest, CakeResponse consolidates a number of methods previously found on Controller, RequestHandlerComponent and Dispatcher. The old methods are deprecated in favour of using CakeResponse. CakeResponse provides an interface to wrap the common response related tasks such as: Sending headers for redirects. Sending content type headers. Sending any header. Sending the response body.

58

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Changing the response class CakePHP uses CakeResponse by default. CakeResponse is a exible and transparent to use class. But if you need to replace it with an application specic class, you can override and replace CakeResponse with your own class. By replacing the CakeResponse used in index.php. This will make all the controllers in your application use CustomResponse instead of CakeResponse. You can also replace the response instance used by setting $this->response in your controllers. Overriding the response object is handy during testing, as it allows you to stub out the methods that interact with header(). See the section on CakeResponse and testing for more information. Dealing with content types You can control the Content-Type of your applications responses with using CakeResponse::type(). If your application needs to deal with content types that are not built into CakeResponse, you can map those types with type() as well:
// Add a vCard type $this->response->type(array(vcf => text/v-card)); // Set the response Content-Type to vcard. $this->response->type(vcf);

Usually youll want to map additional content types in your controllers beforeFilter callback, so you can leverage the automatic view switching features of RequestHandlerComponent if you are using it. Sending les There are times when you want to send les as responses for your requests. Prior to version 2.3 you could use Media Views to accomplish that. As of 2.3 MediaView is deprecated and you can use CakeResponse::file() to send a le as response:
public function sendFile($id) { $file = $this->Attachment->getFile($id); $this->response->file($file[path]); }

As shown in above example as expected you have to pass the le path to the method. Cake will send proper content type header if its a known le type listed in CakeReponse::$_mimeTypes. You can add new types prior to calling CakeResponse::file() by using the CakeResponse::type() method. If you want you can also force a le to be downloaded instead of being displayed in the browser by specifying the options:
$this->response->file($file[path], array(download => true, name => foo));

More on controllers

59

CakePHP Cookbook Documentation, Release 2.x

Setting headers Setting headers is done with the CakeResponse::header() method. It can be called with a few different parameter congurations:
// Set a single header $this->response->header(Location, http://example.com);

// Set multiple headers $this->response->header(array(Location => http://example.com, X-Extra => My header) $this->response->header(array(WWW-Authenticate: Negotiate, Content-type: application/pdf

Setting the same header multiple times will result in overwriting the previous values, just like regular header calls. Headers are not sent when CakeResponse::header() is called either. They are just buffered until the response is actually sent. Interacting with browser caching You sometimes need to force browsers to not cache the results of a controller action. CakeResponse::disableCache() is intended for just that:
public function index() { // do something. $this->response->disableCache(); }

Warning: Using disableCache() with downloads from SSL domains while trying to send les to Internet Explorer can result in errors. You can also tell clients that you want them to cache responses. By using CakeResponse::cache():
public function index() { //do something $this->response->cache(-1 minute, +5 days); }

The above would tell clients to cache the resulting response for 5 days, hopefully speeding up your visitors experience. cache() sets the Last-Modied value to the rst argument. Expires, and Max-age are set based on the second parameter. Cache-Control is set to public as well. Fine tuning HTTP cache One of the best and easiest ways of speeding up your application is using HTTP cache. Under this caching model you are only required to help clients decide if they should use a cached copy of the response by setting a few headers such as modied time, response entity tag and others. Opposed to having to code the logic for caching and for invalidating (refreshing) it once the data has changed, HTTP uses two models, expiration and validation which usually are a lot simpler than having to manage the cache yourself.

60

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Apart from using CakeResponse::cache() you can also use many other methods to ne tune HTTP cache headers to take advantage of browser or reverse proxy caching.
The Cache Control header

New in version 2.1. Used under the expiration model, this header contains multiple indicators which can change the way browsers or proxies use the cached content. A Cache-Control header can look like this:
Cache-Control: private, max-age=3600, must-revalidate

CakeResponse class helps you set this header with some utility methods that will produce a nal valid Cache-Control header. First of them is CakeResponse::sharable() method, which indicates whether a response in to be considered sharable across different users or clients or users. This method actually controls the public or private part of this header. Setting a response as private indicates that all or part of it is intended for a single user. To take advantage of shared caches it is needed to set the control directive as public Second parameter of this method is used to specify a max-age for the cache, which is the number of seconds after which the response is no longer considered fresh.:
public function view() { ... // set the Cache-Control as public for 3600 seconds $this->response->sharable(true, 3600); } public function my_data() { ... // set the Cache-Control as private for 3600 seconds $this->response->sharable(false, 3600); }

CakeResponse exposes separate methods for setting each of the components in the Cache-Control header.
The Expiration header

New in version 2.1. Also under the cache expiration model, you can set the Expires header, which according to the HTTP specication is the date/time after which the response is no longer considered fresh. This header can be set using the CakeResponse::expires() method:
public function view() { $this->response->expires(+5 days); }

This method also accepts a DateTime or any string that can be parsed by the DateTime class.

More on controllers

61

CakePHP Cookbook Documentation, Release 2.x

The Etag header

New in version 2.1. Cache validation in HTTP is often used when content is constantly changing, and asks the application to only generate the response contents if the cache is no longer fresh. Under this model, the client continues to store pages in the cache, but instead of using it directly, it asks the application every time whether the resources changed or not. This is commonly used with static resources such as images and other assets. The Etag header (called entity tag) is string that uniquely identies the requested resource. It is very much like the checksum of a le, caching will compare checksums to tell whether they match or not. To actually get advantage of using this header you have to either call manually CakeResponse::checkNotModified() method or have the RequestHandlerComponent included in your controller:
public function index() { $articles = $this->Article->find(all); $this->response->etag($this->Article->generateHash($articles)); if ($this->response->checkNotModified($this->request)) { return $this->response; } ... }

The Last Modied header

New in version 2.1. Also under the HTTP cache validation model, you can set the Last-Modied header to indicate the date and time at which the resource was modied for the last time. Setting this header helps CakePHP respond to caching clients whether the response was modied or not based on the client cache. To actually get advantage of using this header you have to either call manually CakeResponse::checkNotModified() method or have the RequestHandlerComponent included in your controller:
public function view() { $article = $this->Article->find(first); $this->response->modified($article[Article][modified]); if ($this->response->checkNotModified($this->request)) { return $this->response; } ... }

The Vary header

In some cases you might want to serve different contents using the same url. This is often the case when you have a multilingual page or respond with different HTML according to the browser that is requesting the resource. For such circumstances, you use the Vary header:

62

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

$this->response->vary(User-Agent); $this->response->vary(Accept-Encoding, User-Agent); $this->response->vary(Accept-Language);

CakeResponse and testing Probably one of the biggest wins from CakeResponse comes from how it makes testing controllers and components easier. Instead of methods spread across several objects, you only have a single object to mock as controllers and components delegate to CakeResponse. This helps you get closer to a unit test and makes testing controllers easier:
public function testSomething() { $this->controller->response = $this->getMock(CakeResponse); $this->controller->response->expects($this->once())->method(header); // ... }

Additionally you can more easily run tests from the command line, as you can use mocks to avoid the headers sent errors that can come up from trying to set headers in CLI. CakeResponse API class CakeResponse CakeResponse provides a number of useful methods for interacting with the response you are sending to a client. CakeResponse::header($header = null, $value = null) Allows you to directly set one or many headers to be sent with the response. CakeResponse::charset($charset = null) Sets the charset that will be used in the response. CakeResponse::type($contentType = null) Sets the content type for the response. You can either use a known content type alias or the full content type name. CakeResponse::cache($since, $time = +1 day) Allows you to set caching headers in the response. CakeResponse::disableCache() Sets the headers to disable client caching for the response. CakeResponse::sharable($public = null, $time = null) Sets the Cache-Control header to be either public or private and optionally sets a max-age directive of the resource New in version 2.1. CakeResponse::expires($time = null) Allows to set the Expires header to a specic date. New in version 2.1. CakeResponse::etag($tag = null, $weak = false) Sets the Etag header to uniquely identify a response resource. New in version 2.1. More on controllers 63

CakePHP Cookbook Documentation, Release 2.x

CakeResponse::modified($time = null) Sets the Last-Modied header to a specic date and time in the correct format. New in version 2.1. CakeResponse::checkNotModified(CakeRequest $request) Compares the cache headers for the request object with the cache header from the response and determines if it can still be considered fresh. In that case deletes any response contents and sends the 304 Not Modied header. New in version 2.1. CakeResponse::compress() Turns on gzip compression for the request. CakeResponse::download($lename) Allows you to send the response as an attachment and set the lename. CakeResponse::statusCode($code = null) Allows you to set the status code for the response. CakeResponse::body($content = null) Set the content body for the response. CakeResponse::send() Once you are done creating a response, calling send() will send all the set headers as well as the body. This is done automatically at the end of each request by Dispatcher CakeResponse::file($path, $options = array()) Allows you to set a le for display or download New in version 2.3.

Scaffolding
Application scaffolding is a technique that allows a developer to dene and create a basic application that can create, retrieve, update and delete objects. Scaffolding in CakePHP also allows developers to dene how objects are related to each other, and to create and break those links. All thats needed to create a scaffold is a model and its controller. Once you set the $scaffold variable in the controller, youre up and running. CakePHPs scaffolding is pretty cool. It allows you to get a basic CRUD application up and going in minutes. Its so cool that youll want to use it in production apps. Now, we think its cool too, but please realize that scaffolding is... well... just scaffolding. Its a loose structure you throw up real quick during the beginning of a project in order to get started. It isnt meant to be completely exible, its meant as a temporary way to get up and going. If you nd yourself really wanting to customize your logic and your views, its time to pull your scaffolding down in order to write some code. CakePHPs Bake console, covered in the next section, is a great next step: it generates all the code that would produce the same result as the most current scaffold. Scaffolding is a great way of getting the early parts of developing a web application started. Early database schemas are 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 CakePHP. 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.

64

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

To add scaffolding to your application, in the controller, add the $scaffold variable:
class CategoriesController extends AppController { public $scaffold; }

Assuming youve created even the most basic Category model class le (in /app/Model/Category.php), youre ready to go. Visit http://example.com/categories to see your new scaffold. Note: Creating methods in controllers that are scaffolded can cause unwanted results. For example, if you create an index() method in a scaffolded controller, your index method will be rendered rather than the scaffolding functionality. Scaffolding is knowledgeable about model associations, so if your Category model belongsTo a User, youll see related User IDs in the Category listings. While scaffolding knows about model associations, you will not see any related records in the scaffold views until you manually add the association code to the model. For example, if Group hasMany User and User belongsTo Group, you have to manually add the following code in your User and Group models. Before you add the following code, the view displays an empty select input for Group in the New User form. After you add the following code, the view displays a select input populated with IDs or names from the Group table in the New User form:
// In Group.php public $hasMany = User; // In User.php public $belongsTo = Group;

If youd rather see something besides an ID (like the users rst name), you can set the $displayField variable in the model. Lets set the $displayField variable in our User class so that users related to categories will be shown by rst name rather than just an ID in scaffolding. This feature makes scaffolding more readable in many instances:
class User extends AppModel { public $displayField = first_name; }

Creating a simple admin interface with scaffolding If you have enabled admin routing in your app/Cong/core.php, with Configure::write(Routing.prefixes, array(admin)); you can use scaffolding to generate an admin interface. Once you have enabled admin routing assign your admin prex to the scaffolding variable:
public $scaffold = admin;

You will now be able to access admin scaffolded actions:


http://example.com/admin/controller/index http://example.com/admin/controller/view http://example.com/admin/controller/edit

More on controllers

65

CakePHP Cookbook Documentation, Release 2.x

http://example.com/admin/controller/add http://example.com/admin/controller/delete

This is an easy way to create a simple backend interface quickly. Keep in mind that you cannot have both admin and non-admin methods scaffolded at the same time. As with normal scaffolding you can override individual methods and replace them with your own:
public function admin_view($id = null) { // custom code here }

Once you have replaced a scaffolded action you will need to create a view le for the action as well. Customizing Scaffold Views If youre looking for something a little different in your scaffolded views, you can create templates. We still dont recommend using this technique for production applications, but such a customization may be useful during prototyping iterations. Custom scaffolding views for a specic controller (PostsController in this example) should be placed like so:
/app/View/Posts/scaffold.index.ctp /app/View/Posts/scaffold.form.ctp /app/View/Posts/scaffold.view.ctp

Custom scaffolding views for all controllers should be placed like so:
/app/View/Scaffolds/index.ctp /app/View/Scaffolds/form.ctp /app/View/Scaffolds/view.ctp

The Pages Controller


CakePHP ships with a default controller PagesController.php. This is a simple and optional controller for serving up static content. The home page you see after installation is generated using this controller. If you make the view le app/View/Pages/about_us.ctp you can access it using the url http://example.com/pages/about_us. You are free to modify the Pages Controller to meet your needs. When you bake an app using CakePHPs console utility the Pages Controller is created in your app/Controller/ folder. You can also copy the le from lib/Cake/Console/Templates/skel/Controller/PagesController.php. Changed in version 2.1: With CakePHP 2.0 the Pages Controller was part of lib/Cake. Since 2.1 the Pages Controller is no longer part of the core but ships in the app folder. Warning: Do not directly modify ANY le under the lib/Cake folder to avoid issues when updating the core in future.

66

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Components
Components are packages of logic that are shared between controllers. If you nd yourself wanting to copy and paste things between controllers, you might consider wrapping some functionality in a component. CakePHP also comes with a fantastic set of core components you can use to aid in: Security Sessions Access control lists Emails Cookies Authentication Request handling Pagination Each of these core components are detailed in their own chapters. For now, well show you how to create your own components. Creating components keeps controller code clean and allows you to reuse code between projects. Conguring Components Many of the core components require conguration. Some examples of components requiring conguration are Authentication, Cookie and EmailComponent. Conguration for these components, and for components in general, is usually done in the $components array or your controllers beforeFilter() method:
class PostsController extends AppController { public $components = array( Auth => array( authorize => array(controller), loginAction => array(controller => users, action => login) ), Cookie => array(name => CookieMonster) );

Would be an example of conguring a component with the $components array. All core components allow their conguration settings to be set in this way. In addition you can congure components in your controllers beforeFilter() method. This is useful when you need to assign the results of a function to a component property. The above could also be expressed as:
public function beforeFilter() { $this->Auth->authorize = array(controller); $this->Auth->loginAction = array(controller => users, action => login); $this->Cookie->name = CookieMonster; }

More on controllers

67

CakePHP Cookbook Documentation, Release 2.x

Its possible, however, that a component requires certain conguration options to be set before the controllers beforeFilter() is run. To this end, some components allow conguration options be set in the $components array:
public $components = array( DebugKit.Toolbar => array(panels => array(history, session)) );

Consult the relevant documentation to determine what conguration options each component provides. One common setting to use is the className option, which allows you to alias components. This feature is useful when you want to replace $this->Auth or another common Component reference with a custom implementation:
// app/Controller/PostsController.php class PostsController extends AppController { public $components = array( Auth => array( className => MyAuth ) ); } // app/Controller/Component/MyAuthComponent.php App::uses(AuthComponent, Controller/Component); class MyAuthComponent extends AuthComponent { // Add your code to override the core AuthComponent }

The above would alias MyAuthComponent to $this->Auth in your controllers. Note: Aliasing a component replaces that instance anywhere that component is used, including inside other Components.

Using Components Once youve included some components in your controller, using them is pretty simple. Each component you use is exposed as a property on your controller. If you had loaded up the SessionComponent and the CookieComponent in your controller, you could access them like so:
class PostsController extends AppController { public $components = array(Session, Cookie); public function delete() { if ($this->Post->delete($this->request->data(Post.id)) { $this->Session->setFlash(Post deleted.); $this->redirect(array(action => index)); } }

68

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Note: Since both Models and Components are added to Controllers as properties they share the same namespace. Be sure to not give a component and a model the same name.

Loading components on the y

You might not need all of your components available on every controller action. In situations like this you can load a component at runtime using the Component Collection. From inside a controller you can do the following:
$this->OneTimer = $this->Components->load(OneTimer); $this->OneTimer->getTime();

Component Callbacks Components also offer a few request life-cycle callbacks that allow them to augment the request cycle. See the base Component API for more information on the callbacks components offer. Creating a Component Suppose our online application needs to perform a complex mathematical operation in many different parts of the application. We could create a component to house this shared logic for use in many different controllers. The rst step is to create a new component le and class. Create the le in /app/Controller/Component/MathComponent.php. The basic structure for the component would look something like this:
App::uses(Component, Controller); class MathComponent extends Component { public function doComplexOperation($amount1, $amount2) { return $amount1 + $amount2; } }

Note: All components must extend Component. Failing to do this will trigger an exception.

Including your component in your controllers

Once our component is nished, we can use it in the applications controllers by placing the components name (minus the Component part) in the controllers $components array. The controller will automatically be given a new attribute named after the component, through which we can access an instance of it:

More on controllers

69

CakePHP Cookbook Documentation, Release 2.x

/* Make the new component available at $this->Math, as well as the standard $this->Session */ public $components = array(Math, Session);

Components declared in AppController will be merged with those in your other controllers. So there is no need to re-declare the same component twice. When including Components in a Controller you can also declare a set of parameters that will be passed on to the Components constructor. These parameters can then be handled by the Component:
public $components = array( Math => array( precision => 2, randomGenerator => srand ), Session, Auth );

The above would pass the array containing precision and randomGenerator to MathComponent::__construct() as the second parameter. By convention, any settings that have been passed that are also public properties on your component will have the values set based on the settings.
Using other Components in your Component

Sometimes one of your components may need to use another component. In this case you can include other components in your component the exact same way you include them in controllers - using the $components var:
// app/Controller/Component/CustomComponent.php App::uses(Component, Controller); class CustomComponent extends Component { // the other component your component uses public $components = array(Existing); public function initialize(Controller $controller) { $this->Existing->foo(); } public function bar() { // ... } } // app/Controller/Component/ExistingComponent.php App::uses(Component, Controller); class ExistingComponent extends Component { public function foo() { // ... } }

70

Chapter 4. Controllers

CakePHP Cookbook Documentation, Release 2.x

Component API class Component The base Component class offers a few methods for lazily loading other Components through ComponentCollection as well as dealing with common handling of settings. It also provides prototypes for all the component callbacks. Component::__construct(ComponentCollection $collection, $settings = array()) Constructor for the base component class. All $settings that are also public properties will have their values changed to the matching value in $settings.
Callbacks

Component::initialize(Controller $controller) The initialize method is called before the controllers beforeFilter method. Component::startup(Controller $controller) The startup method is called after the controllers beforeFilter method but before the controller executes the current action handler. Component::beforeRender(Controller $controller) The beforeRender method is called after the controller executes the requested actions logic but before the controllers renders views and layout. Component::shutdown(Controller $controller) The shutdown method is called before output is sent to browser. Component::beforeRedirect(Controller $controller, $url, $status=null, $exit=true) The beforeRedirect method is invoked when the controllers redirect method is called but before any further action. If this method returns false the controller will not continue on to redirect the request. The $url, $status and $exit variables have same meaning as for the controllers method. You can also return a string which will be interpreted as the url to redirect to or return associative array with key url and optionally status and exit.

More on controllers

71

CakePHP Cookbook Documentation, Release 2.x

72

Chapter 4. Controllers

CHAPTER 5

Views

Views are the V in MVC. Views are responsible for generating the specic output required for the request. Often this is in the form of HTML, XML, or JSON, but streaming les and creating PDFs that users can download are also responsibilities of the View Layer. CakePHP comes with a few built-in View classes for handling the most common rendering scenarios: To create XML or JSON webservices you can use the JSON and XML views. To serve protected les, or dynamically generated les, you can use Media Views To create multiple themed views, you can use Themes

View Templates
The view layer of CakePHP is how you speak to your users. Most of the time your views will be showing (X)HTML documents to browsers, but you might also need to serve AMF data to a Flash object, reply to a remote application via SOAP, or output a CSV le for a user. By default CakePHP view les are written in plain PHP and have a default extension of .ctp (CakePHP Template). These les contain all the presentational logic needed to get the data it received from the controller in a format that is ready for the audience youre serving to. If youd prefer using a templating language like Twig, or Smarty, a subclass of View will bridge your templating language and CakePHP View les are stored in /app/View/, in a folder named after the controller that uses the les, and named after the action it corresponds to. For example, the view le for the Products controllers view() action, would normally be found in /app/View/Products/view.ctp. The view layer in CakePHP can be made up of a number of different parts. Each part has different uses, and will be covered in this chapter: views: Views are the part of the page that is unique to the action being run. They form the meat of your applications response. elements: smaller, reusable bits of view code. Elements are usually rendered inside of views.

73

CakePHP Cookbook Documentation, Release 2.x

layouts: view les that contain presentational code that is found wrapping many interfaces in your application. Most views are rendered inside of a layout. helpers: these classes encapsulate view logic that is needed in many places in the view layer. Among other things, helpers in CakePHP can help you build forms, build AJAX functionality, paginate model data, or serve RSS feeds.

Extending Views
New in version 2.1. View extending allows you to wrap one view in another. Combining this with view blocks gives you a powerful way to keep your views DRY. For example, your application has a sidebar that needs to change depending on the specic view being rendered. By extending a common view le you can avoid repeating the common markup for your sidebar, and only dene the parts that change:
// app/View/Common/view.ctp <h1><?php echo $this->fetch(title); ?></h1> <?php echo $this->fetch(content); ?> <div class="actions"> <h3>Related actions</h3> <ul> <?php echo $this->fetch(sidebar); ?> </ul> </div>

The above view le could be used as a parent view. It expects that the view extending it will dene the sidebar and title blocks. The content block is a special block that CakePHP creates. It will contain all the un-captured content from the extending view. Assuming our view le has a $posts variable with the data about our post. Our view could look like:
<?php // app/View/Posts/view.ctp $this->extend(/Common/view); $this->assign(title, $post); $this->start(sidebar); ?> <li> <?php echo $this->Html->link(edit, array( action => edit, $post[Post][id] )); ?> </li> <?php $this->end(); ?> // The remaining content will be available as the content block // in the parent view. <?php echo h($post[Post][body]);

The post view above shows how you can extend a view, and populate a set of blocks. Any content not already 74 Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

in a dened block will captured and put into a special block named content. When a view contains a call to extend() execution continues to the bottom of the current view le. Once its complete, the extended view will be rendered. Calling extend() more than once in a view le will override the parent view that will be processed next:
$this->extend(/Common/view); $this->extend(/Common/index);

The above will result in /Common/index.ctp being rendered as the parent view to the current view. You can nest extended views as many times as necessary. Each view can extend another view if desired. Each parent view will get the previous views content as the content block. Note: You should avoid using content as a block name in your application. CakePHP uses this for un-captured content in extended views.

Using view blocks


New in version 2.1. View blocks replace $scripts_for_layout and provide a exible API that allows you to dene slots or blocks in your views/layouts that will be dened elsewhere. For example blocks are ideal for implementing things such as sidebars, or regions to load assets at the bottom/top of the layout. Blocks can be dened in two ways. Either as a capturing block, or by direct assignment. The start(), append() and end() methods allow to work with capturing blocks:
// create the sidebar block. $this->start(sidebar); echo $this->element(sidebar/recent_topics); echo $this->element(sidebar/recent_comments); $this->end();

// Append into the sidebar later on. $this->append(sidebar); echo $this->element(sidebar/popular_topics); $this->end();

You can also append into a block using start() multiple times. assign() can be used to clear or overwrite a block at any time:
// Clear the previous content from the sidebar block. $this->assign(sidebar, );

In 2.3, a few new methods were added for working with blocks. The prepend() to prepend content to an existing block:
// Prepend to sidebar $this->prepend(sidebar, this content goes on top of sidebar);

The method startIfEmpty() can be used to start a block only if its empty or undened. If the block already exists the captured content will be discarded. This is useful when you want to conditionally dene Using view blocks 75

CakePHP Cookbook Documentation, Release 2.x

default content for a block should it not already exist:


// In a view file. // Create a navbar block $this->startIfEmpty(navbar); echo $this->element(navbar); echo $this->element(notifications); $this->end(); // In a parent view/layout $this->startIfEmpty(navbar); Default content $this->end(); echo $this->fetch(navbar);

In the above example, the navbar block will only contain the content added in the rst section. Since the block was dened in the child view, the default content will be discarded. Note: You should avoid using content as a block name. This is used by CakePHP internally for extended views, and view content in the layout.

Displaying blocks
New in version 2.1. You can display blocks using the fetch() method. fetch() will safely output a block, returning if a block does not exist:
echo $this->fetch(sidebar);

You can also use fetch to conditionally show content that should surround a block should it exist. This is helpful in layouts, or extended views where you want to conditionally show headings or other markup:
// in app/View/Layouts/default.ctp <?php if ($this->fetch(menu)): ?> <div class="menu"> <h3>Menu options</h3> <?php echo $this->fetch(menu); ?> </div> <?php endif; ?>

As of 2.3.0 you can also provide a default value for a block should it not have any content. This allows you to easily add placeholder content, for empty states. You can provide a default value using the 2nd argument:
<div class="shopping-cart"> <h3>Your Cart</h3> <?php echo $this->fetch(cart, Your cart is empty); </div>

Changed in version 2.3: The $default argument was added in 2.3.

76

Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

Using blocks for script and CSS les


New in version 2.1. Blocks replace the deprecated $scripts_for_layout layout variable. Instead you should use blocks. The HtmlHelper ties into view blocks, and its script(), css(), and meta() methods each update a block with the same name when used with the inline = false option:
<?php // in your view file $this->Html->script(carousel, array(inline => false)); $this->Html->css(carousel, null, array(inline => false)); ?> // In your layout file. <!DOCTYPE html> <html lang="en"> <head> <title><?php echo $this->fetch(title); ?></title> <?php echo $this->fetch(script); ?> <?php echo $this->fetch(css); ?> </head> // rest of the layout follows

The HtmlHelper also allows you to control which block the scripts and CSS go to:
// in your view $this->Html->script(carousel, array(block => scriptBottom)); // in your layout echo $this->fetch(scriptBottom);

Layouts
A layout contains presentation code that wraps around a view. Anything you want to see in all of your views should be placed in a layout. Layout les should be placed in /app/View/Layouts. CakePHPs default layout can be overridden by creating a new default layout at /app/View/Layouts/default.ctp. Once a new default layout has been created, controller-rendered view code is placed inside of the default layout when the page is rendered. When you create a layout, you need to tell CakePHP where to place the code for your views. To do so, make sure your layout includes a place for $this->fetch(content) Heres an example of what a default layout might look like:
<!DOCTYPE html> <html lang="en"> <head> <title><?php echo $title_for_layout?></title> <link rel="shortcut icon" href="favicon.ico" type="image/x-icon"> <!-- Include external files and scripts here (See HTML helper for more info.) --> <?php echo $this->fetch(meta);

Layouts

77

CakePHP Cookbook Documentation, Release 2.x

echo $this->fetch(css); echo $this->fetch(script); ?> </head> <body> <!-- If youd like some sort of menu to show up on all of your views, include it here --> <div id="header"> <div id="menu">...</div> </div> <!-- Heres where I want my views to be displayed --> <?php echo $this->fetch(content); ?> <!-- Add a footer to each displayed page --> <div id="footer">...</div> </body> </html>

Note: Prior to version 2.1, method fetch() was not available, fetch(content) is a replacement for $content_for_layout and lines fetch(meta), fetch(css) and fetch(script) are contained in the $scripts_for_layout variable in version 2.0 The script, css and meta blocks contain any content dened in the views using the built-in HTML helper. Useful for including javascript and CSS les from views. Note: When using HtmlHelper::css() or HtmlHelper::script() in view les, specify false for the inline option to place the html source in a block with the same name. (See API for more details on usage). The content block contains the contents of the rendered view. $title_for_layout contains the page title. This variable is generated automatically, but you can override it by setting it in your controller/view. To set the title for the layout, its easiest to do so in the controller, setting the $title_for_layout variable:
class UsersController extends AppController { public function view_active() { $this->set(title_for_layout, View Active Users); } }

You can also set the title_for_layout variable from inside the view le:
$this->set(title_for_layout, $titleContent);

78

Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

You can create as many layouts as you wish: just place them in the app/View/Layouts directory, and switch between them inside of your controller actions using the controller or views $layout property:
// from a controller public function admin_view() { // stuff $this->layout = admin; } // from a view file $this->layout = loggedin;

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 controllers actions using something like:
class UsersController extends AppController { public function view_active() { $this->set(title_for_layout, View Active Users); $this->layout = default_small_ad; } public function view_image() { $this->layout = image; //output user image } }

CakePHP features two core layouts (besides CakePHPs default layout) you can use in your own application: ajax and ash. The Ajax layout is handy for crafting Ajax responses - its an empty layout (most ajax calls only require a bit of markup in return, rather than a fully-rendered interface). The ash layout is used for messages shown by Controller::flash() method. Three other layouts, xml, js, and rss, exist in the core for a quick and easy way to serve up content that isnt text/html.

Using layouts from plugins


New in version 2.1. If you want to use a layout that exists in a plugin, you can use plugin syntax. For example to use the contact layout from the Contacts plugin:
class UsersController extends AppController { public function view_active() { $this->layout = Contacts.contact; } }

Elements
Many applications have small blocks of presentation code that need to be repeated from page to page, sometimes in different places in the layout. CakePHP can help you repeat parts of your website that need to Elements 79

CakePHP Cookbook Documentation, Release 2.x

be reused. These reusable parts are called Elements. Ads, help boxes, navigational controls, extra menus, login forms, and callouts are often implemented in CakePHP as elements. An element is basically a miniview that can be included in other views, in layouts, and even within other elements. Elements can be used to make a view more readable, placing the rendering of repeating elements in its own le. They can also help you re-use content fragments in your application. Elements live in the /app/View/Elements/ folder, and have the .ctp lename extension. They are output using the element method of the view:
echo $this->element(helpbox);

Passing Variables into an Element


You can pass data to an element through the elements second argument:
echo $this->element(helpbox, array( "helptext" => "Oh, this text is very helpful." ));

Inside the element le, all the passed variables are available as members of the parameter array (in the same way that Controller::set() in the controller works with view les). In the above example, the /app/View/Elements/helpbox.ctp le can use the $helptext variable:
// inside app/View/Elements/helpbox.ctp echo $helptext; //outputs "Oh, this text is very helpful."

The View::element() method also supports options for the element. The options supported are cache and callbacks. An example:

echo $this->element(helpbox, array( "helptext" => "This is passed to the element as $helptext", "foobar" => "This is passed to the element as $foobar", ), array( "cache" => "long_view", // uses the "long_view" cache configuration "callbacks" => true // set to true to have before/afterRender called for the elemen ) );

Element caching is facilitated through the Cache class. You can congure elements to be stored in any Cache conguration youve setup. This gives you a great amount of exibility to decide where and for how long elements are stored. To cache different versions of the same element in an application, provide a unique cache key value using the following format:
$this->element(helpbox, array(), array( "cache" => array(config => short, key => unique value) ) );

You can take full advantage of elements by using requestAction(). The requestAction() function fetches view variables from a controller action and returns them as an array. This enables your elements to perform in true MVC style. Create a controller action that prepares the view variables for your elements, 80 Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

then call requestAction() inside the second parameter of element() to feed the element the view variables from your controller. To do this, in your controller add something like the following for the Post example:
class PostsController extends AppController { // ... public function index() { $posts = $this->paginate(); if ($this->request->is(requested)) { return $posts; } else { $this->set(posts, $posts); } } }

And then in the element we can access the paginated posts model. To get the latest ve posts in an ordered list we would do something like the following:
<h2>Latest Posts</h2> <?php $posts = $this->requestAction(posts/index/sort:created/direction:asc/limit:5); ?> <ol> <?php foreach ($posts as $post): ?> <li><?php echo $post[Post][title]; ?></li> <?php endforeach; ?> </ol>

Caching Elements
You can take advantage of CakePHP view caching if you supply a cache parameter. If set to true, it will cache the element in the default Cache conguration. Otherwise, you can set which cache conguration should be used. See Caching for more information on conguring Cache. A simple example of caching an element would be:
echo $this->element(helpbox, array(), array(cache => true));

If you render the same element more than once in a view and have caching enabled be sure to set the key parameter to a different name each time. This will prevent each successive call from overwriting the previous element() calls cached result. E.g.:
echo $this->element( helpbox, array(var => $var), array(cache => array(key => first_use, config => view_long) ); echo $this->element( helpbox, array(var => $differenVar), array(cache => array(key => second_use, config => view_long) );

Elements

81

CakePHP Cookbook Documentation, Release 2.x

The above will ensure that both element results are cached separately. If you want all element caching to use the same cache conguration, you can save some repetition, by setting View::$elementCache to the cache conguration you want to use. CakePHP will use this conguration, when none is given.

Requesting Elements from a Plugin 2.0


To load an element from a plugin, use the plugin option (moved out of the data option in 1.x):
echo $this->element(helpbox, array(), array(plugin => Contacts));

2.1
If you are using a plugin and wish to use elements from within the plugin, just use the familiar plugin syntax. If the view is being rendered for a plugin controller/action, the plugin name will automatically be prexed onto all elements used, unless another plugin name is present. If the element doesnt exist in the plugin, it will look in the main APP folder.:
echo $this->element(Contacts.helpbox);

If your view is a part of a plugin you can omit the plugin name. ContactsController of the Contacts plugin:
echo $this->element(helpbox); // and echo $this->element(Contacts.helpbox);

For example, if you are in the

Are equivalent and will result in the same element being rendered. Changed in version 2.1: The $options[plugin] option was deprecated and support for Plugin.element was added.

Creating your own view classes


You may need to create custom view classes to enable new types of data views, or add additional custom view rendering logic to your application. Like most components of CakePHP view classes have a few conventions: View class les should be put in App/View. For example App/View/PdfView.php View classes should be sufxed with View. For example PdfView. When referencing view class names you should omit the View sufx. $this->viewClass = Pdf;. Youll also want to extend View to ensure things work correctly:
// in App/View/PdfView.php App::uses(View, View);

For example

82

Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

class PdfView extends View { public function render($view = null, $layout = null) { // custom logic here. } }

Replacing the render method lets you take full control over how your content is rendered.

View API
class View View methods are accessible in all view, element and layout les. $this->method() To call any view method use

View::set(string $var, mixed $value) Views have a set() method that is analogous to the set() found in Controller objects. Using set() from your view le will add the variables to the layout and elements that will be rendered later. See Controller Methods for more information on using set(). In your view le you can do:
$this->set(activeMenuButton, posts);

Then in your layout the $activeMenuButton variable will be available and contain the value posts. View::getVar(string $var) Gets the value of the viewVar with the name $var View::getVars() Gets a list of all the available view variables in the current rendering scope. Returns an array of variable names. View::element(string $elementPath, array $data, array $options = array()) Renders an element or view partial. See the section on Elements for more information and examples. View::uuid(string $object, mixed $url) Generates a unique non-random DOM ID for an object, based on the object type and url. This method is often used by helpers that need to generate unique DOM IDs for elements such as the JsHelper:
$uuid = $this->uuid(form, array(controller => posts, action => index)); //$uuid contains form0425fe3bad

View::addScript(string $name, string $content) Adds content to the internal scripts buffer. This buffer is made available in the layout as $scripts_for_layout. This method is helpful when creating helpers that need to add javascript or css directly to the layout. Keep in mind that scripts added from the layout, or elements in the layout will not be added to $scripts_for_layout. This method is most often used from inside helpers, like the JsHelper and HtmlHelper Helpers. Deprecated since version 2.1: Use the Using view blocks features instead.

View API

83

CakePHP Cookbook Documentation, Release 2.x

View::blocks() Get the names of all dened blocks as an array. View::start($name) Start a capturing block for a view block. See the section on Using view blocks for examples. New in version 2.1. View::end() End the top most open capturing block. See the section on Using view blocks for examples. New in version 2.1. View::append($name, $content) Append into the block with $name. See the section on Using view blocks for examples. New in version 2.1. View::prepend($name, $content) Prepend into the block with $name. See the section on Using view blocks for examples. New in version 2.3. View::startIfEmpty($name) Conditionally start a block, only if its empty. All content in the block will be captured and discarded if the block is already dened. New in version 2.3. View::assign($name, $content) Assign the value of a block. This will overwrite any existing content. See the section on Using view blocks for examples. New in version 2.1. View::fetch($name) Fetch the value of a block. Will be returned for blocks that are not dened. See the section on Using view blocks for examples. New in version 2.1. View::extend($name) Extend the current view/element/layout with the named one. See the section on Extending Views for examples. New in version 2.1. property View::$layout Set the layout the current view will be wrapped in. property View::$elementCache The cache conguration used to cache elements. Setting this property will change the default conguration used to cache elements. This default can be overridden using the cache option in the element method. property View::$request An instance of CakeRequest. Use this instance to access information about the current request. property View::$output Contains the last rendered content from a view, either the view le, or the layout content. Deprecated since version 2.1: Use $view->Blocks->get(content); instead. property View::$Blocks An instance of ViewBlock. Used to provide view block functionality in view rendering. New in version 2.1.

84

Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

More about Views


Themes
You can take advantage of themes, making it easy to switch the look and feel of your page quickly and easily. To use themes, specify the theme name in your controller:
class ExampleController extends AppController { public $theme = Example; }

Changed in version 2.1: Versions previous to 2.1 required setting the $this->viewClass = Theme. 2.1 removes this requirement as the normal View class supports themes You can also set or change the theme name within an action or within the beforeFilter or beforeRender callback functions:
$this->theme = AnotherExample;

Theme view les need to be within the /app/View/Themed/ folder. Within the themed folder, create a folder using the same name as your theme name. For example, the above theme would be found in /app/View/Themed/AnotherExample. Its important to remember that CakePHP expects CamelCase theme names. Beyond that, the folder structure within the /app/View/Themed/Example/ folder is exactly the same as /app/View/. For example, the view le for an edit action of a Posts controller would reside at /app/View/Themed/Example/Posts/edit.ctp. Layout les would reside in /app/View/Themed/Example/Layouts/. If a view le cant be found in the theme, CakePHP will try to locate the view le in the /app/View/ folder. This way, you can create master view les and simply override them on a case-by-case basis within your theme folder. Theme assets Themes can contain static assets as well as view les. A theme can include any necessary assets in its webroot directory. This allows for easy packaging and distribution of themes. While in development, requests for theme assets will be handled by Dispatcher. To improve performance for production environments, its recommended that you either symlink or copy theme assets into the applications webroot. See below for more information.

To use the new theme webroot create directories like app/View/Themed/<themeName>/webroot<path_to_file in your theme. The Dispatcher will handle nding the correct theme assets in your view paths. All of CakePHPs built-in helpers are aware of themes and will create the correct paths automatically. Like view les, if a le isnt in the theme folder, it will default to the main webroot folder:
//When in a theme with the name of purple_cupcake $this->Html->css(main.css);

More about Views

85

CakePHP Cookbook Documentation, Release 2.x

//creates a path like /theme/purple_cupcake/css/main.css //and links to app/View/Themed/PurpleCupcake/webroot/css/main.css

Increasing performance of plugin and theme assets Its a well known fact that serving assets through PHP is guaranteed to be slower than serving those assets without invoking PHP. And while the core team has taken steps to make plugin and theme asset serving as fast as possible, there may be situations where more performance is required. In these situations its recommended that you either symlink or copy out plugin/theme assets to directories in app/webroot with paths matching those used by CakePHP. app/Plugin/DebugKit/webroot/js/my_file.js app/webroot/debug_kit/js/my_file.js app/View/Themed/Navy/webroot/css/navy.css app/webroot/theme/Navy/css/navy.css becomes becomes

Media Views
class MediaView Deprecated since version 2.3: Use Sending les instead. Media views allow you to send binary les to the user. For example, you may wish to have a directory of les outside of the webroot to prevent users from direct linking them. You can use the Media view to pull the le from a special folder within /app/, allowing you to perform authentication before delivering the le to the user. To use the Media view, you need to tell your controller to use the MediaView class instead of the default View class. After that, just pass in additional parameters to specify where your le is located:
class ExampleController extends AppController { public function download() { $this->viewClass = Media; // Download app/outside_webroot_dir/example.zip $params = array( id => example.zip, name => example, download => true, extension => zip, path => APP . outside_webroot_dir . DS ); $this->set($params); } }

Heres an example of rendering a le whose mime type is not included in the MediaViews $mimeType array. We are also using a relative path which will default to your app/webroot folder:

86

Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

public function download() { $this->viewClass = Media; // Render app/webroot/files/example.docx $params = array( id => example.docx, name => example, extension => docx, mimeType => array( docx => application/vnd.openxmlformats-officedocument.wordprocessingml.docum ), path => files . DS ); $this->set($params); }

Settable Parameters id The ID is the le name as it resides on the le server including the le extension. name The name allows you to specify an alternate le name to be sent to the user. Specify the name without the le extension. download A boolean value indicating whether headers should be set to force download. extension The le extension. This is matched against an internal list of acceptable mime types. If the mime type specied is not in the list (or sent in the mimeType parameter array), the le will not be downloaded. path The folder name, including the nal directory separator. The path should be absolute but can be relative to the app/webroot folder. mimeType An array with additional mime types to be merged with MediaView internal list of acceptable mime types. cache A boolean or integer value - If set to true it will allow browsers to cache the le (defaults to false if not set); otherwise set it to the number of seconds in the future for when the cache should expire.

JSON and XML views


New in CakePHP 2.1 are two new view classes. The XmlView and JsonView let you easily create XML and JSON responses, and integrate with the RequestHandlerComponent. By enabling RequestHandlerComponent in your application, and enabling support for the xml and or json extensions, you can automatically leverage the new view classes. XmlView and JsonView will be referred to as data views for the rest of this page. There are two ways you can generate data views. The rst is by using the _serialize key, and the second is by creating normal view les.

More about Views

87

CakePHP Cookbook Documentation, Release 2.x

Enabling data views in your application Before you can use the data view classes, youll need to do a bit of setup: 1. Enable the json and or xml extensions with Router::parseExtensions(). This will enable Router to handle multiple extensions. 2. Add the RequestHandlerComponent to your controllers list of components. This will enable automatic view class switching on content types. You can also set the component up with the viewClassMap setting, to map types to your custom classes and/or map other data types. New in version 2.3: RequestHandlerComponent::viewClassMap() method has been added to map types to viewClasses. The viewClassMap setting will not work on earlier versions. After adding Router::parseExtensions(json); to your routes le, CakePHP will automatically switch view classes when a request is done with the .json extension, or the Accept header is application/json. Using data views with the serialize key The _serialize key is a special view variable that indicates which other view variable(s) should be serialized when using a data view. This lets you skip dening view les for your controller actions if you dont need to do any custom formatting before your data is converted into json/xml. If you need to do any formatting or manipulation of your view variables before generating the response, you should use view les. The value of _serialize can be either a string or an array of view variables to serialize:
class PostsController extends AppController { public function index() { $this->set(posts, $this->paginate()); $this->set(_serialize, array(posts)); } }

You can also dene _serialize as an array of view variables to combine:


class PostsController extends AppController { public function index() { // some code that created $posts and $comments $this->set(compact(posts, comments)); $this->set(_serialize, array(posts, comments)); } }

Dening _serialize as an array has the added benet of automatically appending a top-level <response> element when using XmlView. If you use a string value for _serialize and XmlView, make sure that your view variable has a single top-level element. Without a single top-level element the Xml will fail to generate.

88

Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

Using a data view with view les You should use view les if you need to do some manipulation of your view content before creating the nal output. For example if we had posts, that had a eld containing generated HTML, we would probably want to omit that from a JSON response. This is a situation where a view le would be useful:
// Controller code class PostsController extends AppController { public function index() { $this->set(compact(posts, comments)); } } // View code - app/View/Posts/json/index.ctp foreach ($posts as &$post) { unset($post[Post][generated_html]); } echo json_encode(compact(posts, comments));

You can do more more complex manipulations, or use helpers to do formatting as well. Note: The data view classes dont support layouts. They assume that the view le will output the serialized content. class XmlView A view class for generating Xml view data. See above for how you can use XmlView in your application. By default when using _serialize the XmlView will wrap your serialized view variables with a <response> node. You can set a custom name for this node using the _rootNode view variable. New in version 2.3: The _rootNode feature was added. class JsonView A view class for generating Json view data. See above for how you can use JsonView in your application.

Helpers
Helpers are the component-like classes for the presentation layer of your application. They contain presentational logic that is shared between many views, elements, or layouts. This chapter will show you how to create your own helpers, and outline the basic tasks CakePHPs core helpers can help you accomplish. CakePHP features a number of helpers that aid in view creation. They assist in creating well-formed markup (including forms), aid in formatting text, times and numbers, and can even speed up Ajax functionality. For more information on the helpers included in CakePHP, check out Helpers.

More about Views

89

CakePHP Cookbook Documentation, Release 2.x

Using and Conguring Helpers You enable helpers in CakePHP by making a controller aware of them. Each controller has a $helpers property that lists the helpers to be made available in the view. To enable a helper in your view, add the name of the helper to the controllers $helpers array:
class BakeriesController extends AppController { public $helpers = array(Form, Html, Js, Time); }

Adding helpers from plugins uses the plugin syntax used elsewhere in CakePHP:
class BakeriesController extends AppController { public $helpers = array(Blog.Comment); }

You can also add helpers from within an action, so they will only be available to that action and not the other actions in the controller. This saves processing power for the other actions that do not use the helper as well as help keep the controller better organized:
class BakeriesController extends AppController { public function bake { $this->helpers[] = Time; } public function mix { // The Time helper is not loaded here and thus not available } }

If you need to enable a helper for all controllers add the name of the helper to the $helpers array in /app/Controller/AppController.php (or create if not present). Remember to include the default Html and Form helpers:
class AppController extends Controller { public $helpers = array(Form, Html, Js, Time); }

You can pass options to helpers. These options can be used to set attribute values or modify behavior of a helper:
class AwesomeHelper extends AppHelper { public function __construct(View $view, $settings = array()) { parent::__construct($view, $settings); debug($settings); } } class AwesomeController extends AppController { public $helpers = array(Awesome => array(option1 => value1)); }

As of 2.3 the options are merged with the Helper::$settings property of the helper. One common setting to use is the className option, which allows you to create aliased helpers in your 90 Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

views. This feature is useful when you want to replace $this->Html or another common Helper reference with a custom implementation:
// app/Controller/PostsController.php class PostsController extends AppController { public $helpers = array( Html => array( className => MyHtml ) ); } // app/View/Helper/MyHtmlHelper.php App::uses(HtmlHelper, View/Helper); class MyHtmlHelper extends HtmlHelper { // Add your code to override the core HtmlHelper }

The above would alias MyHtmlHelper to $this->Html in your views. Note: Aliasing a helper replaces that instance anywhere that helper is used, including inside other Helpers.

Tip: Aliasing the Html or Session Helper while using the core PagesController will not work. It is better to copy lib/Cake/Controller/PagesController.php into your app/Controller/ folder. Using helper settings allows you to declaratively congure your helpers and keep conguration logic out of your controller actions. If you have conguration options that cannot be included as part of a class declaration, you can set those in your controllers beforeRender callback:
class PostsController extends AppController { public function beforeRender() { parent::beforeRender(); $this->helpers[CustomStuff] = $this->_getCustomStuffSettings(); } }

Using Helpers Once youve congured which helpers you want to use in your controller, each helper is exposed as a public property in the view. For example, if you were using the HtmlHelper you would be able to access it by doing the following:
echo $this->Html->css(styles);

The above would call the css method on the HtmlHelper. You can access any loaded helper using $this->{$helperName}. There may come a time where you need to dynamically load a helper from inside a view. You can use the views HelperCollection to do this:

More about Views

91

CakePHP Cookbook Documentation, Release 2.x

$mediaHelper = $this->Helpers->load(Media, $mediaSettings);

The HelperCollection is a collection and supports the collection API used elsewhere in CakePHP. Callback methods Helpers feature several callbacks that allow you to augment the view rendering process. See the Helper API and the Collections documentation for more information. Creating Helpers If a core helper (or one showcased on github or the Bakery) doesnt t your needs, helpers are easy to create. Lets say we wanted to create a helper that could be used to output a specically crafted CSS-styled link you needed many different places in your application. In order to t your logic in to CakePHPs existing helper structure, youll need to create a new class in /app/View/Helper. Lets call our helper LinkHelper. The actual PHP class le would look something like this:
/* /app/View/Helper/LinkHelper.php */ App::uses(AppHelper, View/Helper); class LinkHelper extends AppHelper { public function makeEdit($title, $url) { // Logic to create specially formatted link goes here... } }

Note: Helpers must extend either AppHelper or Helper or implement all the callbacks in the Helper API.

Including other Helpers

You may wish to use some functionality already existing in another helper. To do so, you can specify helpers you wish to use with a $helpers array, formatted just as you would in a controller:
/* /app/View/Helper/LinkHelper.php (using other helpers) */ App::uses(AppHelper, View/Helper); class LinkHelper extends AppHelper { public $helpers = array(Html); public function makeEdit($title, $url) { // Use the HTML helper to output // formatted data: $link = $this->Html->link($title, $url, array(class => edit));

92

Chapter 5. Views

CakePHP Cookbook Documentation, Release 2.x

return <div class="editOuter"> . $link . </div>; } }

Using your Helper

Once youve created your helper and placed it in /app/View/Helper/, youll be able to include it in your controllers using the special variable $helpers:
class PostsController extends AppController { public $helpers = array(Link); }

Once your controller has been made aware of this new class, you can use it in your views by accessing an object named after the helper:
<!-- make a link using the new helper --> <?php echo $this->Link->makeEdit(Change this Recipe, /recipes/edit/5); ?>

Creating Functionality for All Helpers All helpers extend a special class, AppHelper (just like models extend AppModel and controllers extend AppController). To create functionality that would be available to all helpers, create /app/View/Helper/AppHelper.php:
App::uses(Helper, View); class AppHelper extends Helper { public function customMethod() { } }

Helper API class Helper The base class for Helpers. It provides a number of utility methods and features for loading other helpers. Helper::webroot($le) Resolve a le name to the webroot of the application. If a theme is active and the le exists in the current themes webroot, the path to the themed le will be returned. Helper::url($url, $full = false) Generates an HTML escaped URL, delegates to Router::url(). Helper::value($options = array(), $eld = null, $key = value) Get the value for a given input name.

More about Views

93

CakePHP Cookbook Documentation, Release 2.x

Helper::domId($options = null, $id = id) Generate a CamelCased id value for the currently selected eld. Overriding this method in your AppHelper will allow you to change how CakePHP generates ID attributes.
Callbacks

Helper::beforeRenderFile($viewFile) Is called before all view les are rendered. This includes elements, views, parent views, and layouts. Helper::afterRenderFile($viewFile, $content) Is called after all view les are rendered. This includes elements, views, parent views, and layouts. A callback can modify and return $content to change how the rendered content will be displayed in the browser. Helper::beforeRender($viewFile) The beforeRender method is called after the controllers beforeRender method but before the controller renders view and layout. Receives the le being rendered as an argument. Helper::afterRender($viewFile) Is called after the view has been rendered but before layout rendering has started. Helper::beforeLayout($layoutFile) Is called before layout rendering starts. Receives the layout lename as an argument. Helper::afterLayout($layoutFile) Is called after layout rendering is complete. Receives the layout lename as an argument. Core Helpers CacheHelper Used by the core to cache view content. FormHelper Creates HTML forms and form elements that self populate and handle validation problems. HtmlHelper Convenience methods for crafting well-formed markup. Images, links, tables, header tags and more. JsHelper Used to create Javascript compatible with various Javascript libraries. NumberHelper Number and currency formatting. Paginator Model data pagination and sorting. RSS Convenience methods for outputting RSS feed XML data. SessionHelper Access for reading session values in views. TextHelper Smart linking, highlighting, word smart truncation. TimeHelper Proximity detection (is this next year?), nice string formatting(Today, 10:30 am) and time zone conversion.

94

Chapter 5. Views

CHAPTER 6

Models

Models are the classes that sit as the business layer in your application. This means that they should be responsible for managing almost everything that happens regarding your data, its validity, interactions and evolution of the information workow in your domain of work. Usually model classes represent data and are used in CakePHP applications for data access, more specically they represent a database table but they are not limited to this, but can be used to access anything that manipulates data such as les, external web services, iCal events, or rows in a CSV le. A model can be associated with other models. For example, a Recipe may be associated with the Author of the recipe as well as the Ingredient in the recipe. This section will explain what features of the model can be automated, how to override those features, and what methods and properties a model can have. Itll explain the different ways to associate your data. Itll describe how to nd, save, and delete data. Finally, itll look at Datasources.

Understanding Models
A Model represents your data model. In object-oriented programming a data model is an object that represents a thing, like a car, a person, or a house. A blog, for example, may have many blog posts and each blog post may have many comments. The Blog, Post, and Comment are all examples of models, each associated with another. Here is a simple example of a model denition in CakePHP:
class Ingredient extends AppModel { public $name = Ingredient; }

With just this simple declaration, the Ingredient model is bestowed with all the functionality you need to create queries along with saving and deleting data. These magic methods come from CakePHPs Model class by the magic of inheritance. The Ingredient model extends the application model, AppModel, which extends CakePHPs internal Model class. It is this core Model class that bestows the functionality onto your Ingredient model.

95

CakePHP Cookbook Documentation, Release 2.x

This intermediate class, AppModel, is empty and if you havent created your own, is taken from within the CakePHP core folder. Overriding the AppModel allows you to dene functionality that should be made available to all models within your application. To do so, you need to create your own AppModel.php le that resides in the Model folder, as all other models in your application. Creating a project using Bake will automatically generate this le for you. See also Behaviors for more information on how to apply similar logic to multiple models. Back to our Ingredient model, in order to work on it, create the PHP le in the /app/Model/ directory. By convention it should have the same name as the class; for this example Ingredient.php. Note: CakePHP will dynamically create a model object for you if it cannot nd a corresponding le in /app/Model. This also means that if your model le isnt named correctly (i.e. ingredient.php or Ingredients.php) CakePHP will use an instance of AppModel rather than your missing (from CakePHPs perspective) model le. If youre trying to use a method youve dened in your model, or a behavior attached to your model and youre getting SQL errors that are the name of the method youre calling - its a sure sign CakePHP cant nd your model and you either need to check the le names, your application cache, or both.

Note: Some class names are not usable for model names. For instance File cannot be used as File is a class already existing in the CakePHP core. With your model dened, it can be accessed from within your Controller. CakePHP will automatically make the model available for access when its name matches that of the controller. For example, a controller named IngredientsController will automatically initialize the Ingredient model and attach it to the controller at $this->Ingredient:
class IngredientsController extends AppController { public function index() { //grab all ingredients and pass it to the view: $ingredients = $this->Ingredient->find(all); $this->set(ingredients, $ingredients); } }

Associated models are available through the main model. In the following example, Recipe has an association with the Ingredient model:
class Recipe extends AppModel { public function steakRecipes() { $ingredient = $this->Ingredient->findByName(Steak); return $this->findAllByMainIngredient($ingredient[Ingredient][id]); } }

This shows how to use models that are already linked. To understand how associations are dened take a look at the Associations section

96

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

More on models
Associations: Linking Models Together
One of the most powerful features of CakePHP is the ability to link relational mapping provided by the model. In CakePHP, the links between models are handled through associations. Dening relations between different objects in your application should be a natural process. For example: in a recipe database, a recipe may have many reviews, reviews have a single author, and authors may have many recipes. Dening the way these relations work allows you to access your data in an intuitive and powerful way. The purpose of this section is to show you how to plan for, dene, and utilize associations between models in CakePHP. While data can come from a variety of sources, the most common form of storage in web applications is a relational database. Most of what this section covers will be in that context. For information on associations with Plugin models, see Plugin Models. Relationship Types The four association types in CakePHP are: hasOne, hasMany, belongsTo, and hasAndBelongsToMany (HABTM). Relationship one to one one to many many to one many to many Association Type hasOne hasMany belongsTo hasAndBelongsToMany Example A user has one prole. A user can have multiple recipes. Many recipes belong to a user. Recipes have, and belong to many ingredients.

Associations are dened by creating a class variable named after the association you are dening. The class variable can sometimes be as simple as a string, but can be as complete as a multidimensional array used to dene association specics.
class User extends AppModel { public $hasOne = Profile; public $hasMany = array( Recipe => array( className => Recipe, conditions => array(Recipe.approved => 1), order => Recipe.created DESC ) ); }

In the above example, the rst instance of the word Recipe is what is termed an Alias. This is an identier for the relationship and can be anything you choose. Usually, you will choose the same name as the class that it references. However, aliases for each model must be unique app wide. For example it is appropriate to have:

More on models

97

CakePHP Cookbook Documentation, Release 2.x

class User extends AppModel { public $hasMany = array( MyRecipe => array( className => Recipe, ) ); public $hasAndBelongsToMany => array( MemberOf => array( className => Group, ) ); } class Group extends AppModel { public $hasMany = array( MyRecipe => array( className => Recipe, ) ); public $hasAndBelongsToMany => array( Member => array( className => User, ) ); }

but the following will not work well in all circumstances:


class User extends AppModel { public $hasMany = array( MyRecipe => array( className => Recipe, ) ); public $hasAndBelongsToMany => array( Member => array( className => Group, ) ); } class Group extends AppModel { public $hasMany = array( MyRecipe => array( className => Recipe, ) ); public $hasAndBelongsToMany => array( Member => array( className => User, ) ); }

98

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

because here we have the alias Member referring to both the User (in Group) and the Group (in User) model in the HABTM associations. Choosing non-unique names for model aliases across models can cause unexpected behavior. Cake will automatically create links between associated model objects. So for example in your User model you can access the Recipe model as:
$this->Recipe->someFunction();

Similarly in your controller you can access an associated model simply by following your model associations:
$this->User->Recipe->someFunction();

Note: Remember that associations are dened one way. If you dene User hasMany Recipe that has no effect on the Recipe Model. You need to dene Recipe belongsTo User to be able to access the User model from your Recipe model

hasOne Lets set up a User model with a hasOne relationship to a Prole model. First, your database tables need to be keyed correctly. For a hasOne relationship to work, one table has to contain a foreign key that points to a record in the other. In this case the proles table will contain a eld called user_id. The basic pattern is: hasOne: the other model contains the foreign key. Relation Apple hasOne Banana User hasOne Prole Doctor hasOne Mentor Schema bananas.apple_id proles.user_id mentors.doctor_id

Note: It is not mandatory to follow CakePHP conventions, you can easily override the use of any foreignKey in your associations denitions. Nevertheless sticking to conventions will make your code less repetitive, easier to read and to maintain. The User model le will be saved in /app/Model/User.php. To dene the User hasOne Prole association, add the $hasOne property to the model class. Remember to have a Prole model in /app/Model/Prole.php, or the association wont work:
class User extends AppModel { public $hasOne = Profile; }

There are two ways to describe this relationship in your model les. The simplest method is to set the $hasOne attribute to a string containing the classname of the associated model, as weve done above. If you need more control, you can dene your associations using array syntax. For example, you might want to limit the association to include only certain records. More on models 99

CakePHP Cookbook Documentation, Release 2.x

class User extends AppModel { public $hasOne = array( Profile => array( className => Profile, conditions => array(Profile.published => 1), dependent => true ) ); }

Possible keys for hasOne association arrays include: className: the classname of the model being associated to the current model. If youre dening a User hasOne Prole relationship, the className key should equal Prole. foreignKey: the name of the foreign key found in the other model. This is especially handy if you need to dene multiple hasOne relationships. The default value for this key is the underscored, singular name of the current model, sufxed with _id. In the example above it would default to user_id. conditions: an array of nd() compatible conditions or SQL strings such as array(Prole.approved => true) elds: A list of elds to be retrieved when the associated model data is fetched. Returns all elds by default. order: an array of nd() compatible order clauses or SQL strings such as array(Prole.last_name => ASC) dependent: When the dependent key is set to true, and the models delete() method is called with the cascade parameter set to true, associated model records are also deleted. In this case we set it true so that deleting a User will also delete her associated Prole. Once this association has been dened, nd operations on the User model will also fetch a related Prole record if it exists:
//Sample results from a $this->User->find() call. Array ( [User] => Array ( [id] => 121 [name] => Gwoo the Kungwoo [created] => 2007-05-01 10:31:01 ) [Profile] => Array ( [id] => 12 [user_id] => 121 [skill] => Baking Cakes [created] => 2007-05-01 10:31:01 ) )

100

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

belongsTo Now that we have Prole data access from the User model, lets dene a belongsTo association in the Prole model in order to get access to related User data. The belongsTo association is a natural complement to the hasOne and hasMany associations: it allows us to see the data from the other direction. When keying your database tables for a belongsTo relationship, follow this convention: belongsTo: the current model contains the foreign key. Relation Banana belongsTo Apple Prole belongsTo User Mentor belongsTo Doctor Schema bananas.apple_id proles.user_id mentors.doctor_id

Tip: If a model(table) contains a foreign key, it belongsTo the other model(table). We can dene the belongsTo association in our Prole model at /app/Model/Prole.php using the string syntax as follows:
class Profile extends AppModel { public $belongsTo = User; }

We can also dene a more specic relationship using array syntax:


class Profile extends AppModel { public $belongsTo = array( User => array( className => User, foreignKey => user_id ) ); }

Possible keys for belongsTo association arrays include: className: the classname of the model being associated to the current model. If youre dening a Prole belongsTo User relationship, the className key should equal User. foreignKey: the name of the foreign key found in the current model. This is especially handy if you need to dene multiple belongsTo relationships. The default value for this key is the underscored, singular name of the other model, sufxed with _id. conditions: an array of nd() compatible array(User.active => true) conditions or SQL strings such as

type: the type of the join to use in the SQL query, default is LEFT which may not t your needs in all situations, INNER may be helpful when you want everything from your main and associated models or nothing at all! (effective when used with some conditions of course). (NB: type value is in lower case - i.e. left, inner) elds: A list of elds to be retrieved when the associated model data is fetched. Returns all elds by default. More on models 101

CakePHP Cookbook Documentation, Release 2.x

order: an array of nd() compatible array(User.username => ASC)

order

clauses

or

SQL

strings

such

as

counterCache: If set to true the associated Model will automatically increase or decrease the [singular_model_name]_count eld in the foreign table whenever you do a save() or delete(). If its a string then its the eld name to use. The value in the counter eld represents the number of related rows. You can also specify multiple counter caches by using an array where the key is eld name and value is the conditions. E.g.:
array( recipes_count => true, recipes_published => array(Recipe.published => 1) )

counterScope: Optional conditions array to use for updating counter cache eld. Once this association has been dened, nd operations on the Prole model will also fetch a related User record if it exists:
//Sample results from a $this->Profile->find() call. Array ( [Profile] => Array ( [id] => 12 [user_id] => 121 [skill] => Baking Cakes [created] => 2007-05-01 10:31:01 ) [User] => Array ( [id] => 121 [name] => Gwoo the Kungwoo [created] => 2007-05-01 10:31:01 ) )

hasMany Next step: dening a User hasMany Comment association. A hasMany association will allow us to fetch a users comments when we fetch a User record. When keying your database tables for a hasMany relationship, follow this convention: hasMany: the other model contains the foreign key. Relation User hasMany Comment Cake hasMany Virtue Product hasMany Option Schema Comment.user_id Virtue.cake_id Option.product_id

102

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

We can dene the hasMany association in our User model at /app/Model/User.php using the string syntax as follows:
class User extends AppModel { public $hasMany = Comment; }

We can also dene a more specic relationship using array syntax:


class User extends AppModel { public $hasMany = array( Comment => array( className => foreignKey => conditions => order => limit => dependent => ) ); }

Comment, user_id, array(Comment.status => 1), Comment.created DESC, 5, true

Possible keys for hasMany association arrays include: className: the classname of the model being associated to the current model. If youre dening a User hasMany Comment relationship, the className key should equal Comment. foreignKey: the name of the foreign key found in the other model. This is especially handy if you need to dene multiple hasMany relationships. The default value for this key is the underscored, singular name of the actual model, sufxed with _id. conditions: an array of nd() compatible conditions or SQL strings such as array(Comment.visible => true) order: an array of nd() compatible order clauses or SQL strings such as array(Prole.last_name => ASC) limit: The maximum number of associated rows you want returned. offset: The number of associated rows to skip over (given the current conditions and order) before fetching and associating. dependent: When dependent is set to true, recursive model deletion is possible. In this example, Comment records will be deleted when their associated User record has been deleted. exclusive: When exclusive is set to true, recursive model deletion does the delete with a deleteAll() call, instead of deleting each entity separately. This greatly improves performance, but may not be ideal for all circumstances. nderQuery: A complete SQL query CakePHP can use to fetch associated model records. This should be used in situations that require very custom results. If a query youre building requires a reference to the associated model ID, use the special {$__cakeID__$} marker in the query. For example, if your Apple model hasMany Orange, the query should look something like this: SELECT Orange.* from oranges as Orange WHERE Orange.apple_id = {$__cakeID__$}; More on models 103

CakePHP Cookbook Documentation, Release 2.x

Once this association has been dened, nd operations on the User model will also fetch related Comment records if they exist:
//Sample results from a $this->User->find() call. Array ( [User] => Array ( [id] => 121 [name] => Gwoo the Kungwoo [created] => 2007-05-01 10:31:01 ) [Comment] => Array ( [0] => Array ( [id] => 123 [user_id] => 121 [title] => On Gwoo the Kungwoo [body] => The Kungwooness is not so Gwooish [created] => 2006-05-01 10:31:01 ) [1] => Array ( [id] => 124 [user_id] => 121 [title] => More on Gwoo [body] => But what of the Nut? [created] => 2006-05-01 10:41:01 ) ) )

One thing to remember is that youll need a complimentary Comment belongsTo User association in order to get the data from both directions. What weve outlined in this section empowers you to get Comment data from the User. Adding the Comment belongsTo User association in the Comment model empowers you to get User data from the Comment model - completing the connection and allowing the ow of information from either models perspective. counterCache - Cache your count() This function helps you cache the count of related data. Instead of counting the records manually via find(count), the model itself tracks any addition/deleting towards the associated $hasMany model and increases/decreases a dedicated integer eld within the parent model table. The name of the eld consists of the singular model name followed by a underscore and the word count:
my_model_count

Lets say you have a model called ImageComment and a model called Image, you would add a new INT-eld to the images table and name it image_comment_count.

104

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Here are some more examples: Model User Image BlogEntry Associated Model Image ImageComment BlogEntryComment Example users.image_count images.image_comment_count blog_entries.blog_entry_comment_count

Once you have added the counter eld you are good to go. Activate counter-cache in your association by adding a counterCache key and set the value to true:
class ImageComment extends AppModel { public $belongsTo = array( Image => array( counterCache => true, ) ); }

From now on, every time you add or remove a ImageComment associated to Image, the number within image_comment_count is adjusted automatically. You can also specify counterScope. It allows you to specify a simple condition which tells the model when to update (or when not to, depending on how you look at it) the counter value. Using our Image model example, we can specify it like so:

class ImageComment extends AppModel { public $belongsTo = array( Image => array( counterCache => true, counterScope => array(Image.active => 1) // only count if "Image" is active ) ); }

hasAndBelongsToMany (HABTM) Alright. At this point, you can already call yourself a CakePHP model associations professional. Youre already well versed in the three associations that take up the bulk of object relations. Lets tackle the nal relationship type: hasAndBelongsToMany, or HABTM. This association is used when you have two models that need to be joined up, repeatedly, many times, in many different ways. The main difference between hasMany and HABTM is that a link between models in HABTM is not exclusive. For example, were about to join up our Recipe model with an Ingredient model using HABTM. Using tomatoes as an Ingredient for my grandmas spaghetti recipe doesnt use up the ingredient. I can also use it for a salad Recipe. Links between hasMany associated objects are exclusive. If my User hasMany Comments, a comment is only linked to a specic user. Its no longer up for grabs. Moving on. Well need to set up an extra table in the database to handle HABTM associations. This new join tables name needs to include the names of both models involved, in alphabetical order, and separated

More on models

105

CakePHP Cookbook Documentation, Release 2.x

with an underscore ( _ ). The contents of the table should be two elds, each foreign keys (which should be integers) pointing to both of the primary keys of the involved models. To avoid any issues - dont dene a combined primary key for these two elds, if your application requires it you can dene a unique index. If you plan to add any extra information to this table, or use a with model, you should add an additional primary key eld (by convention id). HABTM requires a separate join table that includes both model names. Relationship Recipe HABTM Ingredient Cake HABTM Fan Foo HABTM Bar HABTM Table Fields ingredients_recipes.id, ingredients_recipes.ingredient_id, ingredients_recipes.recipe_id cakes_fans.id, cakes_fans.cake_id, cakes_fans.fan_id bars_foos.id, bars_foos.foo_id, bars_foos.bar_id

Note: Table names are by convention in alphabetical order. It is possible to dene a custom table name in association denition Make sure primary keys in tables cakes and recipes have id elds as assumed by convention. If theyre different than assumed, it has to be changed in models primaryKey Once this new table has been created, we can dene the HABTM association in the model les. Were gonna skip straight to the array syntax this time:
class Recipe extends AppModel { public $hasAndBelongsToMany = array( Ingredient => array( className joinTable foreignKey associationForeignKey unique conditions fields order limit offset finderQuery deleteQuery insertQuery ) ); }

=> => => => => => => => => => => => =>

Ingredient, ingredients_recipes, recipe_id, ingredient_id, true, , , , , , , ,

Possible keys for HABTM association arrays include: className: the classname of the model being associated to the current model. If youre dening a Recipe HABTM Ingredient relationship, the className key should equal Ingredient. joinTable: The name of the join table used in this association (if the current table doesnt adhere to the naming convention for HABTM join tables). with: Denes the name of the model for the join table. By default CakePHP will auto-create a model

106

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

for you. Using the example above it would be called IngredientsRecipe. By using this key you can override this default name. The join table model can be used just like any regular model to access the join table directly. By creating a model class with such name and lename you can add any custom behavior to the join table searches, such as adding more information/columns to it foreignKey: the name of the foreign key found in the current model. This is especially handy if you need to dene multiple HABTM relationships. The default value for this key is the underscored, singular name of the current model, sufxed with _id. associationForeignKey: the name of the foreign key found in the other model. This is especially handy if you need to dene multiple HABTM relationships. The default value for this key is the underscored, singular name of the other model, sufxed with _id. unique: boolean or string keepExisting. If true (default value) cake will rst delete existing relationship records in the foreign keys table before inserting new ones. Existing associations need to be passed again when updating. When false, cake will insert the relationship record, and that no join records are deleted during a save operation. When set to keepExisting, the behavior is similar to true, but existing associations are not deleted. conditions: an array of nd() compatible conditions or SQL string. If you have conditions on an associated table, you should use a with model, and dene the necessary belongsTo associations on it. elds: A list of elds to be retrieved when the associated model data is fetched. Returns all elds by default. order: an array of nd() compatible order clauses or SQL strings limit: The maximum number of associated rows you want returned. offset: The number of associated rows to skip over (given the current conditions and order) before fetching and associating. nderQuery, deleteQuery, insertQuery: A complete SQL query CakePHP can use to fetch, delete, or create new associated model records. This should be used in situations that require very custom results. Once this association has been dened, nd operations on the Recipe model will also fetch related Tag records if they exist:
// Sample results from a $this->Recipe->find() call. Array ( [Recipe] => Array ( [id] => 2745 [name] => Chocolate Frosted Sugar Bombs [created] => 2007-05-01 10:31:01

More on models

107

CakePHP Cookbook Documentation, Release 2.x

[user_id] => 2346 ) [Ingredient] => Array ( [0] => Array ( [id] => 123 [name] => Chocolate ) [1] => Array ( [id] => 124 [name] => Sugar ) [2] => Array ( [id] => 125 [name] => Bombs ) ) )

Remember to dene a HABTM association in the Ingredient model if youd like to fetch Recipe data when using the Ingredient model. Note: HABTM data is treated like a complete set, each time a new data association is added the complete set of associated rows in database is dropped and created again so you will always need to pass the whole data set for saving. For an alternative to using HABTM see hasMany through (The Join Model)

Tip: For more information on saving HABTM objects see Saving Related Model Data (HABTM)

hasMany through (The Join Model) It is sometimes desirable to store additional data with a many to many association. Consider the following Student hasAndBelongsToMany Course Course hasAndBelongsToMany Student In other words, a Student can take many Courses and a Course can be taken by many Students. This is a simple many to many association demanding a table such as this:
id | student_id | course_id

Now what if we want to store the number of days that were attended by the student on the course and their nal grade? The table wed want would be:
id | student_id | course_id | days_attended | grade

108

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

The trouble is, hasAndBelongsToMany will not support this type of scenario because when hasAndBelongsToMany associations are saved, the association is deleted rst. You would lose the extra data in the columns as it is not replaced in the new insert. Changed in version 2.1. You can set unique setting to keepExisting circumvent losing extra data during the save operation. See unique key in HABTM association arrays. The way to implement our requirement is to use a join model, otherwise known as a hasMany through association. That is, the association is a model itself. So, we can create a new model CourseMembership. Take a look at the following models.:
// Student.php class Student extends AppModel { public $hasMany = array( CourseMembership ); } // Course.php class Course extends AppModel { public $hasMany = array( CourseMembership ); } // CourseMembership.php class CourseMembership extends AppModel { public $belongsTo = array( Student, Course ); }

The CourseMembership join model uniquely identies a given Students participation on a Course in addition to extra meta-information. Join models are pretty useful things to be able to use and Cake makes it easy to do so with its built-in hasMany and belongsTo associations and saveAll feature. Creating and Destroying Associations on the Fly Sometimes it becomes necessary to create and destroy model associations on the y. This may be for any number of reasons: You want to reduce the amount of associated data fetched, but all your associations are on the rst level of recursion. You want to change the way an association is dened in order to sort or lter associated data. This association creation and destruction is done using the CakePHP model bindModel() and unbindModel() methods. (There is also a very helpful behavior called Containable, please refer to manual section about

More on models

109

CakePHP Cookbook Documentation, Release 2.x

Built-in behaviors for more information). Lets set up a few models so we can see how bindModel() and unbindModel() work. Well start with two models:
class Leader extends AppModel { public $hasMany = array( Follower => array( className => Follower, order => Follower.rank ) ); } class Follower extends AppModel { public $name = Follower; }

Now, in the LeadersController, we can use the nd() method in the Leader model to fetch a Leader and its associated followers. As you can see above, the association array in the Leader model denes a Leader hasMany Followers relationship. For demonstration purposes, lets use unbindModel() to remove that association in a controller action:
public function some_action() { // This fetches Leaders, and their associated Followers $this->Leader->find(all); // Lets remove the hasMany... $this->Leader->unbindModel( array(hasMany => array(Follower)) ); // Now using a find function will return // Leaders, with no Followers $this->Leader->find(all); // NOTE: unbindModel only affects the very next // find function. An additional find call will use // the configured association information. // Weve already used find(all) after unbindModel(), // so this will fetch Leaders with associated // Followers once again... $this->Leader->find(all); }

Note: Removing or adding associations using bind- and unbindModel() only works for the next nd operation only unless the second parameter has been set to false. If the second parameter has been set to false, the bind remains in place for the remainder of the request. Heres the basic usage pattern for unbindModel():
$this->Model->unbindModel( array(associationType => array(associatedModelClassName))

110

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

);

Now that weve successfully removed an association on the y, lets add one. Our as-of-yet unprincipled Leader needs some associated Principles. The model le for our Principle model is bare, except for the public $name statement. Lets associate some Principles to our Leader on the y (but rememberonly for just the following nd operation). This function appears in the LeadersController:
public function another_action() { // There is no Leader hasMany Principles in // the leader.php model file, so a find here, // only fetches Leaders. $this->Leader->find(all); // Lets use bindModel() to add a new association // to the Leader model: $this->Leader->bindModel( array(hasMany => array( Principle => array( className => Principle ) ) ) ); // Now that were associated correctly, // we can use a single find function to fetch // Leaders with their associated principles: $this->Leader->find(all); }

There you have it. The basic usage for bindModel() is the encapsulation of a normal association array inside an array whose key is named after the type of association you are trying to create:
$this->Model->bindModel( array(associationName => array( associatedModelClassName => array( // normal association keys go here... ) ) ) );

Even though the newly bound model doesnt need any sort of association denition in its model le, it will still need to be correctly keyed in order for the new association to work properly. Multiple relations to the same model There are cases where a Model has more than one relation to another Model. For example you might have a Message model that has two relations to the User model. One relation to the user that sends a message, and a second to the user that receives the message. The messages table will have a eld user_id, but also a eld recipient_id. Now your Message model can look something like:

More on models

111

CakePHP Cookbook Documentation, Release 2.x

class Message extends AppModel { public $belongsTo = array( Sender => array( className => User, foreignKey => user_id ), Recipient => array( className => User, foreignKey => recipient_id ) ); }

Recipient is an alias for the User model. Now lets see what the User model would look like:
class User extends AppModel { public $hasMany = array( MessageSent => array( className => Message, foreignKey => user_id ), MessageReceived => array( className => Message, foreignKey => recipient_id ) ); }

It is also possible to create self associations as shown below:


class Post extends AppModel { public $belongsTo = array( Parent => array( className => Post, foreignKey => parent_id ) ); public $hasMany = array( Children => array( className => Post, foreignKey => parent_id ) ); }

Fetching a nested array of associated records: If your table has parent_id eld you can also use nd(threaded) to fetch nested array of records using a single query without setting up any associations.

112

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Joining tables In SQL you can combine related tables using the JOIN statement. This allows you to perform complex searches across multiples tables (i.e: search posts given several tags). In CakePHP some associations (belongsTo and hasOne) performs automatic joins to retrieve data, so you can issue queries to retrieve models based on data in the related one. But this is not the case with hasMany and hasAndBelongsToMany associations. Here is where forcing joins comes to the rescue. You only have to dene the necessary joins to combine tables and get the desired results for your query. Note: Remember you need to set the recursion to -1 for this to work. I.e: $this->Channel->recursive = -1; To force a join between tables you need to use the modern syntax for Model::nd(), adding a joins key to the $options array. For example:
$options[joins] = array( array(table => channels, alias => Channel, type => LEFT, conditions => array( Channel.id = Item.channel_id, ) ) ); $Item->find(all, $options);

Note: Note that the join arrays are not keyed. In the above example, a model called Item is left joined to the channels table. You can alias the table with the Model name, so the retrieved data complies with the CakePHP data structure. The keys that dene the join are the following: table: The table for the join. alias: An alias to the table. The name of the model associated with the table is the best bet. type: The type of join: inner, left or right. conditions: The conditions to perform the join. With joins, you could add conditions based on Related model elds:
$options[joins] = array( array(table => channels, alias => Channel, type => LEFT, conditions => array( Channel.id = Item.channel_id,

More on models

113

CakePHP Cookbook Documentation, Release 2.x

) ) ); $options[conditions] = array( Channel.private => 1 ); $privateItems = $Item->find(all, $options);

You could perform several joins as needed in hasAndBelongsToMany: Suppose a Book hasAndBelongsToMany Tag association. This relation uses a books_tags table as join table, so you need to join the books table to the books_tags table, and this with the tags table:
$options[joins] = array( array(table => books_tags, alias => BooksTag, type => inner, conditions => array( Books.id = BooksTag.books_id ) ), array(table => tags, alias => Tag, type => inner, conditions => array( BooksTag.tag_id = Tag.id ) ) ); $options[conditions] = array( Tag.tag => Novel ); $books = $Book->find(all, $options);

Using joins allows you to have a maximum exibility in how CakePHP handles associations and fetch the data, however in most cases you can use other tools to achieve the same results such as correctly dening associations, binding models on the y and using the Containable behavior. This feature should be used with care because it could lead, in a few cases, into bad formed SQL queries if combined with any of the former techniques described for associating models.

Retrieving Your Data


As stated before, one of the roles of the Model layer is to get data from multiple types of storage. The CakePHP Model class comes with some functions that will help you search for this data, sort it, paginate it, and lter it. The most common function you will use in models is Model::find()

114

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

nd find(string $type = first, array $params = array()) Find is the multifunctional workhorse of all model data-retrieval functions. $type can be either all, first, count, list, neighbors or threaded or any custom nder you can dene. Keep in mind that $type is case sensitive. Using an upper case character (for example All) will not produce the expected results. $params is used to pass all parameters to the various nds, and has the following possible keys by default - all of which are optional:

array( conditions => array(Model.field => $thisValue), //array of conditions recursive => 1, //int fields => array(Model.field1, DISTINCT Model.field2), //array of field names order => array(Model.created, Model.field3 DESC), //string or array defining orde group => array(Model.field), //fields to GROUP BY limit => n, //int page => n, //int offset => n, //int callbacks => true //other possible values are false, before, after )

Its also possible to add and use other parameters, as is made use of by some nd types, behaviors and of course possibly with your own model methods. nd(rst) find(first, $params) will return one result, youd use this for any case where you expect only one result. Below are a couple of simple (controller code) examples:
public function some_function() { // ... $semiRandomArticle = $this->Article->find(first); $lastCreated = $this->Article->find(first, array( order => array(Article.created => desc) )); $specificallyThisOne = $this->Article->find(first, array( conditions => array(Article.id => 1) )); // ... }

In the rst example, no parameters at all are passed to nd - therefore no conditions or sort order will be used. The format returned from find(first) call is of the form:
Array ( [ModelName] => Array ( [id] => 83 [field1] => value1

More on models

115

CakePHP Cookbook Documentation, Release 2.x

[field2] => value2 [field3] => value3 ) [AssociatedModelName] => Array ( [id] => 1 [field1] => value1 [field2] => value2 [field3] => value3 ) )

nd(count) find(count, $params) returns an integer value. Below are a couple of simple (controller code) examples:
public function some_function() { // ... $total = $this->Article->find(count); $pending = $this->Article->find(count, array( conditions => array(Article.status => pending) )); $authors = $this->Article->User->find(count); $publishedAuthors = $this->Article->find(count, array( fields => DISTINCT Article.user_id, conditions => array(Article.status != => pending) )); // ... }

Note: Dont pass fields as an array to find(count). You would only need to specify elds for a DISTINCT count (since otherwise, the count is always the same - dictated by the conditions).

nd(all) find(all, $params) returns an array of (potentially multiple) results. It is in fact the mechanism used by all find() variants, as well as paginate. Below are a couple of simple (controller code) examples:
public function some_function() { // ... $allArticles = $this->Article->find(all); $pending = $this->Article->find(all, array( conditions => array(Article.status => pending) )); $allAuthors = $this->Article->User->find(all);

116

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

$allPublishedAuthors = $this->Article->User->find(all, array( conditions => array(Article.status != => pending) )); // ... }

Note: In the above example $allAuthors will contain every user in the users table. There will be no condition applied to the nd as none were passed. The results of a call to find(all) will be of the following form:
Array ( [0] => Array ( [ModelName] => Array ( [id] => 83 [field1] => value1 [field2] => value2 [field3] => value3 ) [AssociatedModelName] => Array ( [id] => 1 [field1] => value1 [field2] => value2 [field3] => value3 ) ) )

nd(list) find(list, $params) returns an indexed array, useful for any place where you would want a list such as for populating input select boxes. Below are a couple of simple (controller code) examples:
public function some_function() { // ... $allArticles = $this->Article->find(list); $pending = $this->Article->find(list, array( conditions => array(Article.status => pending) )); $allAuthors = $this->Article->User->find(list); $allPublishedAuthors = $this->Article->find(list, array( fields => array(User.id, User.name), conditions => array(Article.status != => pending), recursive => 0

More on models

117

CakePHP Cookbook Documentation, Release 2.x

)); // ... }

Note: In the above example $allAuthors will contain every user in the users table. There will be no condition applied to the nd as none were passed. The results of a call to find(list) will be in the following form:
Array ( //[id] [1] => [2] => [4] => [5] => [6] => [3] => )

=> displayValue, displayValue1, displayValue2, displayValue4, displayValue5, displayValue6, displayValue3,

When calling find(list) the fields passed are used to determine what should be used as the array key, value and optionally what to group the results by. By default the primary key for the model is used for the key, and the display eld (which can be congured using the model attribute displayField) is used for the value. Some further examples to clarify:
public function some_function() { // ... $justusernames = $this->Article->User->find(list, array( fields => array(User.username) )); $usernameMap = $this->Article->User->find(list, array( fields => array(User.username, User.first_name) )); $usernameGroups = $this->Article->User->find(list, array( fields => array(User.username, User.first_name, User.group) )); // ... }

With the above code example, the resultant vars would look something like this:
$justusernames = Array ( //[id] => username, [213] => AD7six, [25] => _psychic_, [1] => PHPNut, [2] => gwoo, [400] => jperras, ) $usernameMap = Array

118

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

( //[username] => firstname, [AD7six] => Andy, [_psychic_] => John, [PHPNut] => Larry, [gwoo] => Gwoo, [jperras] => Jol, ) $usernameGroups = Array ( [User] => Array ( [PHPNut] => Larry, [gwoo] => Gwoo, ) [Admin] => Array ( [_psychic_] => John, [AD7six] => Andy, [jperras] => Jol, ) )

nd(threaded) find(threaded, $params) returns a nested array, and is appropriate if you want to use the parent_id eld of your model data to build nested results. Below are a couple of simple (controller code) examples:
public function some_function() { // ... $allCategories = $this->Category->find(threaded); $comments = $this->Comment->find(threaded, array( conditions => array(article_id => 50) )); // ... }

Tip: A better way to deal with nested data is using the Tree behavior In the above code example, $allCategories will contain a nested array representing the whole category structure. The results of a call to find(threaded) will be of the following form:
Array ( [0] => Array (

More on models

119

CakePHP Cookbook Documentation, Release 2.x

[ModelName] => Array ( [id] => 83 [parent_id] => null [field1] => value1 [field2] => value2 [field3] => value3 ) [AssociatedModelName] => Array ( [id] => 1 [field1] => value1 [field2] => value2 [field3] => value3 ) [children] => Array ( [0] => Array ( [ModelName] => Array ( [id] => 42 [parent_id] => 83 [field1] => value1 [field2] => value2 [field3] => value3 ) [AssociatedModelName] => Array ( [id] => 2 [field1] => value1 [field2] => value2 [field3] => value3 ) [children] => Array ( ) ) ... ) ) )

The order results appear can be changed as it is inuenced by the order of processing. For example, if order => name ASC is passed in the params to find(threaded), the results will appear in name order. Likewise any order can be used, there is no inbuilt requirement of this method for the top result to be returned rst.

120

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Warning: If you specify fields, you need to always include the parent_id (or its current alias):
public function some_function() { $categories = $this->Category->find(threaded, array( fields => array(id, name, parent_id) )); }

Otherwise the returned array will not be of the expected nested structure from above.

nd(neighbors) find(neighbors, $params) will perform a nd similar to rst, but will return the row before and after the one you request. Below is a simple (controller code) example:
public function some_function() { $neighbors = $this->Article->find(neighbors, array(field => id, value => 3)); }

You can see in this example the two required elements of the $params array: eld and value. Other elements are still allowed as with any other nd (Ex: If your model acts as containable, then you can specify contain in $params). The format returned from a find(neighbors) call is in the form:
Array ( [prev] => Array ( [ModelName] => Array ( [id] => 2 [field1] => value1 [field2] => value2 ... ) [AssociatedModelName] => Array ( [id] => 151 [field1] => value1 [field2] => value2 ... ) ) [next] => Array ( [ModelName] => Array ( [id] => 4 [field1] => value1 [field2] => value2 ... ) [AssociatedModelName] => Array

More on models

121

CakePHP Cookbook Documentation, Release 2.x

( [id] => 122 [field1] => value1 [field2] => value2 ... ) ) )

Note: Note how the result always contains only two root elements: prev and next. This function does not honor a models default recursive var. The recursive setting must be passed in the parameters on each call.

Creating custom nd types The find method is exible enough to accept your custom nders, this is done by declaring your own types in a model variable and by implementing a special function in your model class. A Model Find Type is a shortcut to nd options. For example, the following two nds are equivalent
$this->User->find(first); $this->User->find(all, array(limit => 1));

The following are core nd types: first all count list threaded neighbors But what about other types? Lets say you want a nder for all published articles in your database. The rst change you need to do is add your type to the Model::$findMethods variable in the model
class Article extends AppModel { public $findMethods = array(available => } true);

Basically this is just telling CakePHP to accept the value available as the rst argument of the find function. Next step is to implement the function _findAvailable. This is done by convention, if you wanted to implement a nder called myFancySearch then the method to implement would be named _findMyFancySearch.
class Article extends AppModel { public $findMethods = array(available => true);

protected function _findAvailable($state, $query, $results = array()) {

122

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

if ($state == before) { $query[conditions][Article.published] = true; return $query; } return $results; } }

This all comes together in the following example (controller code):


class ArticlesController extends AppController { // Will find all published articles and order them by the created column public function index() { $articles = $this->Article->find(available, array( order => array(created => desc) )); } }

The special _find[Type] methods receive 3 arguments as shown above. The rst one means the state of the query execution, which could be either before or after. It is done this way because this function is just a sort of callback function that has the ability to modify the query before it is done, or to modify the results after they are fetched. Typically the rst thing to check in our custom nd function is the state of the query. The before state is the moment to modify the query, bind new associations, apply more behaviors, and interpret any special key that is passed in the second argument of find. This state requires you to return the $query argument (modied or not). The after state is the perfect place to inspect the results, inject new data, process it to return it in another format, or do whatever you like to the recently fetched data. This state requires you to return the $results array (modied or not). You can create as many custom nders as you like, and they are a great way of reusing code in your application across models. It is also possible to paginate via a custom nd type as follows:
class ArticlesController extends AppController { // Will paginate all published articles public function index() { $this->paginate = array(available); $articles = $this->paginate(); $this->set(compact(articles)); } }

Setting the $this->paginate property as above on the controller will result in the type of the nd becoming available, and will also allow you to continue to modify the nd results.

More on models

123

CakePHP Cookbook Documentation, Release 2.x

If your pagination page count is becoming corrupt, it may be necessary to add the following code to your AppModel, which should x pagination count:
class AppModel extends Model { /** * Removes fields key from count query on custom finds when it is an array, * as it will completely break the Model::_findCount() call * * @param string $state Either "before" or "after" * @param array $query * @param array $results * @return int The number of records found, or false * @access protected * @see Model::find() */ protected function _findCount($state, $query, $results = array()) { if ($state === before) { if (isset($query[type]) && isset($this->findMethods[$query[type]])) { $query = $this->{_find . ucfirst($query[type])}(before, $query); if (!empty($query[fields]) && is_array($query[fields])) { if (!preg_match(/^count/i, current($query[fields]))) { unset($query[fields]); } } } } return parent::_findCount($state, $query, $results); } } ?>

Changed in version 2.2. You no longer need to override _ndCount for xing incorrect count results. The before state of your custom nder will now be called again with $query[operation] = count. The returned $query will be used in _findCount() If needed you can distinguish by checking for operation key and return a different $query:
protected function _findAvailable($state, $query, $results = array()) { if ($state == before) { $query[conditions][Article.published] = true; if (!empty($query[operation]) && $query[operation] == count) { return $query; } $query[joins] = array( //array of required joins ); return $query; } return $results; }

124

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Magic Find Types These magic functions can be used as a shortcut to search your tables by a certain eld. Just add the name of the eld (in CamelCase format) to the end of these functions, and supply the criteria for that eld as the rst parameter. ndAllBy() functions will return results in a format like find(all), while ndBy() return in the same format as find(first)
ndAllBy

findAllBy<fieldName>(string $value, array $fields, array $order, int $limit, int $page, int $recursive) ndAllBy<x> Example Corresponding SQL Fragment $this->Product->findAllByOrderStatus(3); Product.order_status = 3 $this->Recipe->findAllByType(Cookie); Recipe.type = Cookie $this->User->findAllByLastName(Anderson);User.last_name = Anderson $this->Cake->findAllById(7); Cake.id = 7 $this->User->findAllByEmailOrUsername(jhon); User.email = jhon OR User.username = jhon; $this->User->findAllByUsernameAndPassword(jhon, User.username = jhon AND 123); User.password = 123; $this->User->findAllByLastName(psychic, User.last_name = psychic array(), array(User.user_name => ORDER BY User.user_name asc)); ASC The returned result is an array formatted just as it would be from find(all).
ndBy

findBy<fieldName>(string $value); The ndBy magic functions also accept some optional parameters: findBy<fieldName>(string $value[, mixed $fields[, mixed $order]]); ndBy<x> Example Corresponding SQL Fragment $this->Product->findByOrderStatus(3); Product.order_status = 3 $this->Recipe->findByType(Cookie); Recipe.type = Cookie $this->User->findByLastName(Anderson); User.last_name = Anderson; $this->User->findByEmailOrUsername(jhon); User.email = jhon OR User.username = jhon; $this->User->findByUsernameAndPassword(jhon, User.username = jhon AND 123); User.password = 123; $this->Cake->findById(7); Cake.id = 7 ndBy() functions return results like find(first)

More on models

125

CakePHP Cookbook Documentation, Release 2.x

Model::query() query(string $query) SQL calls that you cant or dont want to make via other model methods (this should only rarely be necessary) can be made using the models query() method. If youre ever using this method in your application, be sure to check out CakePHPs Data Sanitization, which aids in cleaning up user-provided data from injection and cross-site scripting attacks. Note: query() does not honor $Model->cacheQueries as its functionality is inherently disjoint from that of the calling model. To avoid caching calls to query, supply a second argument of false, ie: query($query, $cachequeries = false) query() uses the table name in the query as the array key for the returned data, rather than the model name. For example:
$this->Picture->query("SELECT * FROM pictures LIMIT 2;");

might return:
Array ( [0] => Array ( [pictures] => Array ( [id] => 1304 [user_id] => 759 ) ) [1] => Array ( [pictures] => Array ( [id] => 1305 [user_id] => 759 ) ) )

To use the model name as the array key, and get a result consistent with that returned by the Find methods, the query can be rewritten:
$this->Picture->query("SELECT * FROM pictures AS Picture LIMIT 2;");

which returns:
Array ( [0] => Array (

126

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

[Picture] => Array ( [id] => 1304 [user_id] => 759 ) ) [1] => Array ( [Picture] => Array ( [id] => 1305 [user_id] => 759 ) ) )

Note: This syntax and the corresponding array structure is valid for MySQL only. Cake does not provide any data abstraction when running queries manually, so exact results will vary between databases.

Model::field() field(string $name, array $conditions = null, string $order = null) Returns the value of a single eld, specied as $name, from the rst record matched by $conditions as ordered by $order. If no conditions are passed and the model id is set, will return the eld value for the current model result. If no matching record is found returns false.
$this->Post->id = 22; echo $this->Post->field(name); // echo the name for row id 22

echo $this->Post->field(name, array(created < => date(Y-m-d H:i:s)), created DESC); // echo the name of the last created instance

Model::read() read($fields, $id) read() is a method used to set the current model data (Model::$data)such as during editsbut it can also be used in other circumstances to retrieve a single record from the database. $fields is used to pass a single eld name, as a string, or an array of eld names; if left empty, all elds will be fetched. $id species the ID of the record to be read. By default, the currently selected record, as specied by Model::$id, is used. Passing a different value to $id will cause that record to be selected. read() always returns an array (even if only a single eld name is requested). Use field to retrieve the value of a single eld.

More on models

127

CakePHP Cookbook Documentation, Release 2.x

Warning: As the read method overwrites any information stored in the data and id property of the model, you should be very careful when using this function in general, especially using it in the model callback functions such as beforeValidate and beforeSave. Generally the find function provides a more robust and easy to work with API than the read method.

Complex Find Conditions Most of the models nd calls involve passing sets of conditions in one way or another. In general CakePHP prefers using arrays for expressing any conditions that needs to be put after the WHERE clause in any SQL query. Using arrays is clearer and easier to read, and also makes it very easy to build queries. This syntax also breaks out the elements of your query (elds, values, operators, etc.) into discrete, manipulatable parts. This allows CakePHP to generate the most efcient query possible, ensure proper SQL syntax, and properly escape each individual part of the query. Using the array syntax also enables CakePHP to secure your queries against any SQL injection attack At its most basic, an array-based query looks like this:
$conditions = array("Post.title" => "This is a post", "Post.author_id" => 1); // Example usage with a model: $this->Post->find(first, array(conditions => $conditions));

The structure here is fairly self-explanatory: it will nd any post where the title equals This is a post. Note that we could have used just title as the eld name, but when building queries, it is good practice to always specify the model name, as it improves the clarity of the code, and helps prevent collisions in the future, should you choose to change your schema. What about other types of matches? These are equally simple. Lets say we wanted to nd all the posts where the title is not This is a post:
array("Post.title !=" => "This is a post")

Notice the != that follows the eld name. CakePHP can parse out any valid SQL comparison operator, including match expressions using LIKE, BETWEEN, or REGEX, as long as you leave a space between eld name and the operator. The one exception here is IN (...)-style matches. Lets say you wanted to nd posts where the title was in a given set of values:
array( "Post.title" => array("First post", "Second post", "Third post") )

To do a NOT IN(...) match to nd posts where the title is not in the given set of values:
array( "NOT" => array("Post.title" => array("First post", "Second post", "Third post")) )

Adding additional lters to the conditions is as simple as adding additional key/value pairs to the array:

128

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

array ( "Post.title" => array("First post", "Second post", "Third post"), "Post.created >" => date(Y-m-d, strtotime("-2 weeks")) )

You can also create nds that compare two elds in the database:
array("Post.created = Post.modified")

This above example will return posts where the created date is equal to the modied date (ie it will return posts that have never been modied). Remember that if you nd yourself unable to form a WHERE clause in this method (ex. boolean operations), you can always specify it as a string like:
array( Model.field & 8 = 1, // other conditions as usual )

By default, CakePHP joins multiple conditions with boolean AND; which means, the snippet above would only match posts that have been created in the past two weeks, and have a title that matches one in the given set. However, we could just as easily nd posts that match either condition:
array("OR" => array( "Post.title" => array("First post", "Second post", "Third post"), "Post.created >" => date(Y-m-d, strtotime("-2 weeks")) ))

Cake accepts all valid SQL boolean operations, including AND, OR, NOT, XOR, etc., and they can be upper or lower case, whichever you prefer. These conditions are also innitely nest-able. Lets say you had a belongsTo relationship between Posts and Authors. Lets say you wanted to nd all the posts that contained a certain keyword (magic) or were created in the past two weeks, but you want to restrict your search to posts written by Bob:
array( "Author.name" => "Bob", "OR" => array( "Post.title LIKE" => "%magic%", "Post.created >" => date(Y-m-d, strtotime("-2 weeks")) ) )

If you need to set multiple conditions on the same eld, like when you want to do a LIKE search with multiple terms, you can do so by using conditions similar to:
array(OR => array( array(Post.title LIKE => %one%), array(Post.title LIKE => %two%) ))

Cake can also check for null elds. In this example, the query will return records where the post title is not null:

More on models

129

CakePHP Cookbook Documentation, Release 2.x

array("NOT" => array( "Post.title" => null ) )

To handle BETWEEN queries, you can use the following:


array(Post.read_count BETWEEN ? AND ? => array(1,10))

Note: CakePHP will quote the numeric values depending on the eld type in your DB. How about GROUP BY?:
array( fields => array( Product.type, MIN(Product.price) as price ), group => Product.type )

The data returned for this would be in the following format:


Array ( [0] => Array ( [Product] => Array ( [type] => Clothing ) [0] => Array ( [price] => 32 ) ) [1] => Array ...

A quick example of doing a DISTINCT query. You can use other operators, such as MIN(), MAX(), etc., in a similar fashion:
array( fields => array(DISTINCT (User.name) AS my_column_name), order = >array(User.id DESC) )

You can create very complex conditions, by nesting multiple condition arrays:
array( OR => array( array(Company.name => Future Holdings), array(Company.city => CA)

130

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

), AND => array( array( OR => array( array(Company.status => active), NOT => array( array(Company.status => array(inactive, suspended)) ) ) ) ) )

Which produces the following SQL:


SELECT Company.id, Company.name, Company.description, Company.location, Company.created, Company.status, Company.size FROM companies AS Company WHERE ((Company.name = Future Holdings) OR (Company.name = Steel Mega Works)) AND ((Company.status = active) OR (NOT (Company.status IN (inactive, suspended))))

Sub-queries

For this example, imagine we have a users table with id, name and status. The status can be A, B or C. And we want to get all the users that have status other than B using sub-query. In order to achieve that we are going to get the model data source and ask it to build the query as if we were calling a nd method, but it will just return the SQL statement. After that we make an expression and add it to the conditions array:
$conditionsSubQuery["User2"."status"] = B; $db = $this->User->getDataSource(); $subQuery = $db->buildStatement( array( fields => array("User2"."id"), table => $db->fullTableName($this->User), alias => User2, limit => null, offset => null, joins => array(), conditions => $conditionsSubQuery, order => null, group => null

More on models

131

CakePHP Cookbook Documentation, Release 2.x

), $this->User ); $subQuery = "User"."id" NOT IN ( . $subQuery . ) ; $subQueryExpression = $db->expression($subQuery); $conditions[] = $subQueryExpression; $this->User->find(all, compact(conditions));

This should generate the following SQL:


SELECT "User"."id" AS "User__id", "User"."name" AS "User__name", "User"."status" AS "User__status" FROM "users" AS "User" WHERE "User"."id" NOT IN ( SELECT "User2"."id" FROM "users" AS "User2" WHERE "User2"."status" = B )

Also, if you need to pass just part of your query as raw SQL as the above, datasource expressions with raw SQL work for any part of the nd query.
Prepared Statements

Should you need even more control over your queries, you can make use of prepared statements. This allows you to talk directly to the database driver and send any custom query you like:
$db = $this->getDataSource(); $db->fetchAll( SELECT * from users where username = ? AND password = ?, array(jhon, 12345) ); $db->fetchAll( SELECT * from users where username = :username AND password = :password, array(username => jhon,password => 12345) );

Saving Your Data


CakePHP makes saving model data a snap. Data ready to be saved should be passed to the models save() method using the following basic format:

132

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Array ( [ModelName] => Array ( [fieldname1] => value [fieldname2] => value ) )

Most of the time you wont even need to worry about this format: CakePHPs FormHelper, and model nd methods all package data in this format. If youre using either of the helpers, the data is also conveniently available in $this->request->data for quick usage. Heres a quick example of a controller action that uses a CakePHP model to save data to a database table:
public function edit($id) { // Has any form data been POSTed? if ($this->request->is(post)) { // If the form data can be validated and saved... if ($this->Recipe->save($this->request->data)) { // Set a session flash message and redirect. $this->Session->setFlash(Recipe Saved!); $this->redirect(/recipes); } } // If no form data, find the recipe to be edited // and hand it to the view. $this->set(recipe, $this->Recipe->findById($id)); }

When save is called, the data passed to it in the rst parameter is validated using CakePHP validation mechanism (see Data Validation chapter for more information). If for some reason your data isnt saving, be sure to check to see if some validation rules are being broken. You can debug this situation by outputting Model::$validationErrors:
if ($this->Recipe->save($this->request->data)) { // handle the success. } debug($this->Recipe->validationErrors);

There are a few other save-related methods in the model that youll nd useful: Model::set($one, $two = null) Model::set() can be used to set one or many elds of data to the data array inside a model. This is useful when using models with the ActiveRecord features offered by Model:
$this->Post->read(null, 1); $this->Post->set(title, New title for the article); $this->Post->save();

More on models

133

CakePHP Cookbook Documentation, Release 2.x

Is an example of how you can use set() to update and save single elds, in an ActiveRecord approach. You can also use set() to assign new values to multiple elds:
$this->Post->read(null, 1); $this->Post->set(array( title => New title, published => false )); $this->Post->save();

The above would update the title and published elds and save them to the database. Model::save(array $data = null, boolean $validate = true, array $fieldList = array()) Featured above, this method saves array-formatted data. The second parameter allows you to sidestep validation, and the third allows you to supply a list of model elds to be saved. For added security, you can limit the saved elds to those listed in $fieldList. Note: If $fieldList is not supplied, a malicious user can add additional elds to the form data (if you are not using SecurityComponent), and by this change elds that were not originally intended to be changed. The save method also has an alternate syntax:
save(array $data = null, array $params = array())

$params array can have any of the following available options as keys: validate Set to true/false to enable disable validation. fieldList An array of elds you want to allow for saving. callbacks Set to false to disable callbacks. Using before or after will enable only those callbacks. More information about model callbacks is available here Tip: If you dont want the updated eld to be updated when saving some data add updated => false to your $data array Once a save has been completed, the ID for the object can be found in the $id attribute of the model object - something especially handy when creating new objects.
$this->Ingredient->save($newData); $newIngredientId = $this->Ingredient->id;

Creating or updating is controlled by the models id eld. If $Model->id is set, the record with this primary key is updated. Otherwise a new record is created:

134

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

// Create: id isnt set or is null $this->Recipe->create(); $this->Recipe->save($this->request->data); // Update: id is set to a numerical value $this->Recipe->id = 2; $this->Recipe->save($this->request->data);

Tip: When calling save in a loop, dont forget to call create(). If you want to update a value, rather than create a new one, make sure your are passing the primary key eld into the data array:
$data = array(id => 10, title => My new title); // This will update Recipe with id 10 $this->Recipe->save($data);

Model::create(array $data = array()) This method resets the model state for saving new information. It does not actually create a record in the database but clears Model::$id if previously set and sets the default values in Model::$data based on your database eld defaults. If the $data parameter (using the array format outlined above) is passed, the model instance will be ready to save with that data (accessible at $this->data). If false is passed instead of an array, the model instance will not initialize elds from the model schema that are not already set, it will only reset elds that have already been set, and leave the rest unset. Use this to avoid updating elds in the database that were already set. Tip: If you want to insert a new row instead of updating an existing one you should always call create() rst. This avoids conicts with possible prior save calls in callbacks or other places.

Model::saveField(string $fieldName, string $fieldValue, $validate = false) Used to save a single eld value. Set the ID of the model ($this->ModelName->id = $id) just before calling saveField(). When using this method, $fieldName should only contain the name of the eld, not the name of the model and eld. For example, to update the title of a blog post, the call to saveField from a controller might look something like this:
$this->Post->saveField(title, A New Title for a New Day);

Warning: You cant stop the updated eld being updated with this method, you need to use the save() method.

More on models

135

CakePHP Cookbook Documentation, Release 2.x

The saveField method also has an alternate syntax:


saveField(string $fieldName, string $fieldValue, array $params = array())

$params array can have any of the following available options as keys: validate Set to true/false to enable disable validation. callbacks Set to false to disable callbacks. Using before or after will enable only those callbacks. Model::updateAll(array $fields, array $conditions) Updates one or more records in a single call. Records to be updated are identied by the $conditions array, and elds to be updated, along with their values, are identied by the $fields array. For example, to approve all bakers who have been members for over a year, the update call might look something like:
$this_year = date(Y-m-d h:i:s, strtotime(-1 year)); $this->Baker->updateAll( array(Baker.approved => true), array(Baker.created <= => $this_year) );

Tip: The $elds array accepts SQL expressions. Literal values should be quoted manually using Sanitize::escape().

Note: Even if the modied eld exists for the model being updated, it is not going to be updated automatically by the ORM. Just add it manually to the array if you need it to be updated. For example, to close all tickets that belong to a certain customer:
$this->Ticket->updateAll( array(Ticket.status => "closed"), array(Ticket.customer_id => 453) );

By default, updateAll() will automatically join any belongsTo association for databases that support joins. To prevent this, temporarily unbind the associations. Model::saveMany(array $data = null, array $options = array()) Method used to save multiple rows of the same model at once. The following options may be used: validate: Set to false to disable validation, true to validate each record before saving, rst to validate all records before any are saved (default),

136

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

atomic: If true (default), will attempt to save all records in a single transaction. Should be set to false if database/table does not support transactions. fieldList: Equivalent to the $eldList parameter in Model::save() deep: (since 2.1) If set to true, also associated data is saved, see also saveAssociated For saving multiple records of single model, $data needs to be a numerically indexed array of records like this:
$data = array( array(title => title 1), array(title => title 2), );

Note: Note that we are passing numerical indices instead of usual $data containing the Article key. When saving multiple records of same model the records arrays should be just numerically indexed without the model key. It is also acceptable to have the data in the following format:
$data = array( array(Article => array(title => title 1)), array(Article => array(title => title 2)), );

To save also associated data with $options[deep] = true (since 2.1), the two above examples would look like:
$data = array( array(title => title 1, Assoc array(title => title 2), ); $data = array( array(Article => array(title => array(Article => array(title => ); $Model->saveMany($data, array(deep => => array(field => value)),

title 1), Assoc => array(field => value)), title 2)), true));

Keep in mind that if you want to update a record instead of creating a new one you just need to add the primary key index to the data row:

$data = array( array(Article => array(title => New article)), // This creates a new row array(Article => array(id => 2, title => title 2)), // This updates an existing );

Model::saveAssociated(array $data = null, array $options = array()) Method used to save multiple model associations at once. The following options may be used:

More on models

137

CakePHP Cookbook Documentation, Release 2.x

validate: Set to false to disable validation, true to validate each record before saving, rst to validate all records before any are saved (default), atomic: If true (default), will attempt to save all records in a single transaction. Should be set to false if database/table does not support transactions. fieldList: Equivalent to the $eldList parameter in Model::save() deep: (since 2.1) If set to true, not only directly associated data is saved, but deeper nested associated data as well. Defaults to false. For saving a record along with its related record having a hasOne or belongsTo association, the data array should be like this:
$data = array( User => array(username => billy), Profile => array(sex => Male, occupation => Programmer), );

For saving a record along with its related records having hasMany association, the data array should be like this:
$data = array( Article => array(title => My first article), Comment => array( array(body => Comment 1, user_id => 1), array(body => Comment 2, user_id => 12), array(body => Comment 3, user_id => 40), ), );

And for saving a record along with its related records having hasMany with more than two levels deep associations, the data array should be as follow:
$data = array( User => array(email => [email protected]), Cart => array( array( payment_status_id => 2, total_cost => 250, CartItem => array( array( cart_product_id => 3, quantity => 1, cost => 100, ), array( cart_product_id => 5, quantity => 1, cost => 150, ) ) ) ) );

138

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Note: If successful, the foreign key of the main model will be stored in the related models id eld, i.e. $this->RelatedModel->id. Warning: Be careful when checking saveAssociated calls with atomic option set to false. It returns an array instead of boolean. Changed in version 2.1: You can now save deeper associated data as well with setting $options[deep] = true; For saving a record along with its related records having hasMany association and deeper associated Comment belongsTo User data as well, the data array should be like this:

$data = array( Article => array(title => My first article), Comment => array( array(body => Comment 1, user_id => 1), array(body => Save a new user as well, User => array(first => mad, last ), );

And save this data with:


$Article->saveAssociated($data, array(deep => true));

Changed in version 2.1: Model::saveAll() and friends now support passing the eldList for multiple models. Example of using fieldList with multiple models:
$this->SomeModel->saveAll($data, array( fieldList => array( SomeModel => array(field_1), AssociatedModel => array(field_2, field_3) ) ));

The eldList will be an array of model aliases as keys and arrays with elds as values. The model names are not nested like in the data to be saved. Model::saveAll(array $data = null, array $options = array()) The saveAll function is just a wrapper around the saveMany and saveAssociated methods. it will inspect the data and determine what type of save it should perform. If data is formatted in a numerical indexed array, saveMany will be called, otherwise saveAssociated is used. This function receives the same options as the former two, and is generally a backwards compatible function. It is recommended using either saveMany or saveAssociated depending on the case. Saving Related Model Data (hasOne, hasMany, belongsTo) When working with associated models, it is important to realize that saving model data should always be done by the corresponding CakePHP model. If you are saving a new Post and its associated Comments, then you would use both Post and Comment models during the save operation. More on models 139

CakePHP Cookbook Documentation, Release 2.x

If neither of the associated model records exists in the system yet (for example, you want to save a new User and their related Prole records at the same time), youll need to rst save the primary, or parent model. To get an idea of how this works, lets imagine that we have an action in our UsersController that handles the saving of a new User and a related Prole. The example action shown below will assume that youve POSTed enough data (using the FormHelper) to create a single User and a single Prole:
public function add() { if (!empty($this->request->data)) { // We can save the User data: // it should be in $this->request->data[User] $user = $this->User->save($this->request->data); // If the user was saved, Now we add this information to the data // and save the Profile. if (!empty($user)) { // The ID of the newly created user has been set // as $this->User->id. $this->request->data[Profile][user_id] = $this->User->id; // Because our User hasOne Profile, we can access // the Profile model through the User model: $this->User->Profile->save($this->request->data); } } }

As a rule, when working with hasOne, hasMany, and belongsTo associations, its all about keying. The basic idea is to get the key from one model and place it in the foreign key eld on the other. Sometimes this might involve using the $id attribute of the model class after a save(), but other times it might just involve gathering the ID from a hidden input on a form thats just been POSTed to a controller action. To supplement the basic approach used above, CakePHP also offers a very handy method saveAssociated(), which allows you to validate and save multiple models in one shot. In addition, saveAssociated() provides transactional support to ensure data integrity in your database (i.e. if one model fails to save, the other models will not be saved either). Note: For transactions to work correctly in MySQL your tables must use InnoDB engine. Remember that MyISAM tables do not support transactions. Lets see how we can use saveAssociated() to save Company and Account models at the same time. First, you need to build your form for both Company and Account models (well assume that Company hasMany Account):
echo echo echo echo $this->Form->create(Company, array(action => add)); $this->Form->input(Company.name, array(label => Company name)); $this->Form->input(Company.description); $this->Form->input(Company.location);

140

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

echo $this->Form->input(Account.0.name, array(label => Account name)); echo $this->Form->input(Account.0.username); echo $this->Form->input(Account.0.email); echo $this->Form->end(Add);

Take a look at the way we named the form elds for the Account model. If Company is our main model, saveAssociated() will expect the related models (Account) data to arrive in a specic format. And having Account.0.fieldName is exactly what we need. Note: The above eld naming is required for a hasMany association. If the association between the models is hasOne, you have to use ModelName.eldName notation for the associated model. Now, in our CompaniesController we can create an add() action:
public function add() { if (!empty($this->request->data)) { // Use the following to avoid validation errors: unset($this->Company->Account->validate[company_id]); $this->Company->saveAssociated($this->request->data); } }

Thats all there is to it. Now our Company and Account models will be validated and saved all at the same time. By default saveAssociated will validate all values passed and then try to perform a save for each. Saving hasMany through data Lets see how data stored in a join table for two models is saved. As shown in the hasMany through (The Join Model) section, the join table is associated to each model using a hasMany type of relationship. Our example involves the Head of Cake School asking us to write an application that allows him to log a students attendance on a course with days attended and grade. Take a look at the following code.:
// Controller/CourseMembershipController.php class CourseMembershipsController extends AppController { public $uses = array(CourseMembership); public function index() { $this->set(courseMembershipsList, $this->CourseMembership->find(all)); } public function add() { if ($this->request->is(post)) { if ($this->CourseMembership->saveAssociated($this->request->data)) { $this->redirect(array(action => index)); } } } }

More on models

141

CakePHP Cookbook Documentation, Release 2.x

// View/CourseMemberships/add.ctp <?php echo $this->Form->create(CourseMembership); ?> <?php echo $this->Form->input(Student.first_name); ?> <?php echo $this->Form->input(Student.last_name); ?> <?php echo $this->Form->input(Course.name); ?> <?php echo $this->Form->input(CourseMembership.days_attended); ?> <?php echo $this->Form->input(CourseMembership.grade); ?> <button type="submit">Save</button> <?php echo $this->Form->end(); ?>

The data array will look like this when submitted.:


Array ( [Student] => Array ( [first_name] => Joe [last_name] => Bloggs ) [Course] => Array ( [name] => Cake ) [CourseMembership] => Array ( [days_attended] => 5 [grade] => A ) )

Cake will happily be able to save the lot together and assign the foreign keys of the Student and Course into CourseMembership with a saveAssociated call with this data structure. If we run the index action of our CourseMembershipsController the data structure received now from a nd(all) is:
Array ( [0] => Array ( [CourseMembership] => Array ( [id] => 1 [student_id] => 1 [course_id] => 1 [days_attended] => 5 [grade] => A ) [Student] => Array ( [id] => 1

142

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

[first_name] => Joe [last_name] => Bloggs ) [Course] => Array ( [id] => 1 [name] => Cake ) ) )

There are of course many ways to work with a join model. The version above assumes you want to save everything at-once. There will be cases where you want to create the Student and Course independently and at a later point associate the two together with a CourseMembership. So you might have a form that allows selection of existing students and courses from pick lists or ID entry and then the two meta-elds for the CourseMembership, e.g.:
// View/CourseMemberships/add.ctp

<?php echo $this->Form->create(CourseMembership); ?> <?php echo $this->Form->input(Student.id, array(type => text, label => Student <?php echo $this->Form->input(Course.id, array(type => text, label => Course I <?php echo $this->Form->input(CourseMembership.days_attended); ?> <?php echo $this->Form->input(CourseMembership.grade); ?> <button type="submit">Save</button> <?php echo $this->Form->end(); ?>

And the resultant POST:


Array ( [Student] => Array ( [id] => 1 ) [Course] => Array ( [id] => 1 ) [CourseMembership] => Array ( [days_attended] => 10 [grade] => 5 ) )

Again Cake is good to us and pulls the Student id and Course id into the CourseMembership with the saveAssociated.

More on models

143

CakePHP Cookbook Documentation, Release 2.x

Saving Related Model Data (HABTM)

Saving models that are associated by hasOne, belongsTo, and hasMany is pretty simple: you just populate the foreign key eld with the ID of the associated model. Once thats done, you just call the save() method on the model, and everything gets linked up correctly. An example of the required format for the data array passed to save() for the Tag model is shown below:
Array ( [Recipe] => Array ( [id] => 42 ) [Tag] => Array ( [name] => Italian ) )

You can also use this format to save several records and their HABTM associations with saveAll(), using an array like the following:
Array ( [0] => Array ( [Recipe] => Array ( [id] => 42 ) [Tag] => Array ( [name] => Italian ) ) [1] => Array ( [Recipe] => Array ( [id] => 42 ) [Tag] => Array ( [name] => Pasta ) ) [2] => Array ( [Recipe] => Array ( [id] => 51 ) [Tag] => Array

144

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

( [name] => Mexican ) ) [3] => Array ( [Recipe] => Array ( [id] => 17 ) [Tag] => Array ( [name] => American (new) ) ) )

Passing the above array to saveAll() will create the contained tags, each associated with their respective recipes. As an example, well build a form that creates a new tag and generates the proper data array to associate it on the y with some recipe. The simplest form might look something like this (well assume that $recipe_id is already set to something):
<?php echo $this->Form->create(Tag); ?> <?php echo $this->Form->input( Recipe.id, array(type => hidden, value => $recipe_id) ); ?> <?php echo $this->Form->input(Tag.name); ?> <?php echo $this->Form->end(Add Tag); ?>

In this example, you can see the Recipe.id hidden eld whose value is set to the ID of the recipe we want to link the tag to. When the save() method is invoked within the controller, itll automatically save the HABTM data to the database:
public function add() { // Save the association if ($this->Tag->save($this->request->data)) { // do something on success } }

With the preceding code, our new Tag is created and associated with a Recipe, whose ID was set in $this->request->data[Recipe][id]. Other ways we might want to present our associated data can include a select drop down list. The data can be pulled from the model using the find(list) method and assigned to a view variable of the model name. An input with the same name will automatically pull in this data into a <select>:

More on models

145

CakePHP Cookbook Documentation, Release 2.x

// in the controller: $this->set(tags, $this->Recipe->Tag->find(list)); // in the view: $this->Form->input(tags);

A more likely scenario with a HABTM relationship would include a <select> set to allow multiple selections. For example, a Recipe can have multiple Tags assigned to it. In this case, the data is pulled out of the model the same way, but the form input is declared slightly different. The tag name is dened using the ModelName convention:
// in the controller: $this->set(tags, $this->Recipe->Tag->find(list)); // in the view: $this->Form->input(Tag);

Using the preceding code, a multiple select drop down is created, allowing for multiple choices to automatically be saved to the existing Recipe being added or saved to the database. What to do when HABTM becomes complicated? By default when saving a HasAndBelongsToMany relationship, Cake will delete all rows on the join table before saving new ones. For example if you have a Club that has 10 Children associated. You then update the Club with 2 children. The Club will only have 2 Children, not 12. Also note that if you want to add more elds to the join (when it was created or meta information) this is possible with HABTM join tables, but it is important to understand that you have an easy option. HasAndBelongsToMany between two models is in reality shorthand for three models associated through both a hasMany and a belongsTo association. Consider this example:
Child hasAndBelongsToMany Club

Another way to look at this is adding a Membership model:


Child hasMany Membership Membership belongsTo Child, Club Club hasMany Membership.

These two examples are almost the exact same. They use the same amount of named elds in the database and the same amount of models. The important differences are that the join model is named differently and its behavior is more predictable. Tip: When your join table contains extra elds besides two foreign keys, you can prevent losing the extra eld values by setting unique array key to keepExisting. You could think of this similar to unique => true, but without losing data from the extra elds during save operation. See: HABTM association arrays.

146

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

However, in most cases its easier to make a model for the join table and setup hasMany, belongsTo associations as shown in example above instead of using HABTM association. Datatables While CakePHP can have datasources that arent database driven, most of the time, they are. CakePHP is designed to be agnostic and will work with MySQL, MSSQL, Oracle, PostgreSQL and others. You can create your database tables as you normally would. When you create your Model classes, theyll automatically map to the tables that youve created. Table names are by convention lowercase and pluralized with multiword table names separated by underscores. For example, a Model name of Ingredient expects the table name ingredients. A Model name of EventRegistration would expect a table name of event_registrations. CakePHP will inspect your tables to determine the data type of each eld and uses this information to automate various features such as outputting form elds in the view. Field names are by convention lowercase and separated by underscores.
Using created and modied

By dening a created and/or modified eld in your database table as datetime elds (default null), CakePHP will recognize those elds and populate them automatically whenever a record is created or saved to the database (unless the data being saved already contains a value for these elds). The created and modied elds will be set to the current date and time when the record is initially added. The modied eld will be updated with the current date and time whenever the existing record is saved. If you have updated, created or modied data in your $this->data (e.g. from a Model::read or Model::set) before a Model::save() then the values will be taken from $this->data and not automagically updated. Either use unset($this->data[Model][modified]), etc. Alternatively you can override the Model::save() to always do it for you:
class AppModel extends Model { public function save($data = null, $validate = true, $fieldList = array()) { // Clear modified field value before each save $this->set($data); if (isset($this->data[$this->alias][modified])) { unset($this->data[$this->alias][modified]); } return parent::save($this->data, $validate, $fieldList); } }

Deleting Data
CakePHPs Model class offers a few ways to delete records from your database.

More on models

147

CakePHP Cookbook Documentation, Release 2.x

delete delete(int $id = null, boolean $cascade = true); Deletes the record identied by $id. By default, also deletes records dependent on the record specied to be deleted. For example, when deleting a User record that is tied to many Recipe records (User hasMany or hasAndBelongsToMany Recipes): if $cascade is set to true, the related Recipe records are also deleted if the models dependent-value is set to true. if $cascade is set to false, the Recipe records will remain after the User has been deleted. If your database supports foreign keys and cascading deletes, its often more efcient to rely on that feature than CakePHPs cascading. The one benet to using the cascade feature of Model::delete() is that it allows you to leverage behaviors and model callbacks:
$this->Comment->delete($this->request->data(Comment.id));

You can hook custom logic into the delete process using the beforeDelete and afterDelete callbacks present in both Models and Behaviors. See Callback Methods for more information. deleteAll deleteAll(mixed $conditions, $cascade = true, $callbacks = false) deleteAll() is similar to delete(), except that deleteAll() will delete all records that match the supplied conditions. The $conditions array should be supplied as a SQL fragment or array. conditions Conditions to match cascade Boolean, Set to true to delete records that depend on this record callbacks Boolean, Run callbacks Return boolean True on success, false on failure. Example:
// Delete with array conditions similar to find() $this->Comment->deleteAll(array(Comment.spam => true), false);

If you delete with either callbacks and/or cascade, rows will be found and then deleted. This will often result in more queries being issued. Note: deleteAll() will return true even if no records are deleted, as the conditions for the delete query were successful and no matching records remain.

148

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Data Validation
Data validation is an important part of any application, as it helps to make sure that the data in a Model conforms to the business rules of the application. For example, you might want to make sure that passwords are at least eight characters long, or ensure that usernames are unique. Dening validation rules makes form handling much, much easier. There are many different aspects to the validation process. What well cover in this section is the model side of things. Essentially: what happens when you call the save() method of your model. For more information about how to handle the displaying of validation errors, check out FormHelper. The rst step to data validation is creating the validation rules in the Model. Model::validate array in the Model denition, for example:
class User extends AppModel { public $validate = array(); }

To do that, use the

In the example above, the $validate array is added to the User Model, but the array contains no validation rules. Assuming that the users table has login, password, email and born elds, the example below shows some simple validation rules that apply to those elds:
class User extends AppModel { public $validate = array( login => alphaNumeric, email => email, born => date ); }

This last example shows how validation rules can be added to model elds. For the login eld, only letters and numbers will be accepted, the email should be valid, and born should be a valid date. Dening validation rules enables CakePHPs automagic showing of error messages in forms if the data submitted does not follow the dened rules. CakePHP has many validation rules and using them can be quite easy. Some of the built-in rules allow you to verify the formatting of emails, URLs, and credit card numbers but well cover these in detail later on. Here is a more complex validation example that takes advantage of some of these built-in validation rules:
class User extends AppModel { public $validate = array( login => array( alphaNumeric => array( rule => alphaNumeric, required => true, message => Alphabets and numbers only ), between => array( rule => array(between, 5, 15), message => Between 5 to 15 characters ) ), password => array(

More on models

149

CakePHP Cookbook Documentation, Release 2.x

rule => array(minLength, 8), message => Minimum 8 characters long ), email => email, born => array( rule => date, message => Enter a valid date, allowEmpty => true ) ); }

Two validation rules are dened for login: it should contain letters and numbers only, and its length should be between 5 and 15. The password eld should be a minimum of 8 characters long. The email should be a valid email address, and born should be a valid date. Also, notice how you can dene specic error messages that CakePHP will use when these validation rules fail. As the example above shows, a single eld can have multiple validation rules. And if the built-in rules do not match your criteria, you can always add your own validation rules as required. Now that youve seen the big picture on how validation works, lets look at how these rules are dened in the model. There are three different ways that you can dene validation rules: simple arrays, single rule per eld, and multiple rules per eld. Simple Rules As the name suggests, this is the simplest way to dene a validation rule. The general syntax for dening rules this way is:
public $validate = array(fieldName => ruleName);

Where, eldName is the name of the eld the rule is dened for, and ruleName is a pre-dened rule name, such as alphaNumeric, email or isUnique. For example, to ensure that the user is giving a well formatted email address, you could use this rule:
public $validate = array(user_email => email);

One Rule Per Field This denition technique allows for better control of how the validation rules work. But before we discuss that, lets see the general usage pattern adding a rule for a single eld:
public $validate = array( fieldName1 => array( rule => ruleName, // or: array(ruleName, param1, param2 ...) required => true, allowEmpty => false, on => create, // or: update message => Your Error Message

150

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

) );

The rule key is required. If you only set required => true, the form validation will not function correctly. This is because required is not actually a rule. As you can see here, each eld (only one eld shown above) is associated with an array that contains ve keys: rule, required, allowEmpty, on and message. Lets have a closer look at these keys.
rule

The rule key denes the validation method and takes either a single value or an array. The specied rule may be the name of a method in your model, a method of the core Validation class, or a regular expression. For more information on the rules available by default, see Core Validation Rules. If the rule does not require any parameters, rule can be a single value e.g.:
public $validate = array( login => array( rule => alphaNumeric ) );

If the rule requires some parameters (like the max, min or range), rule should be an array:
public $validate = array( password => array( rule => array(minLength, 8) ) );

Remember, the rule key is required for array-based rule denitions.


required

This key accepts either a boolean, or create or update. Setting this key to true will make the eld always required. While setting it to create or update will make the eld required only for update or create operations. If required is evaluated to true, the eld must be present in the data array. For example, if the validation rule has been dened as follows:
public $validate = array( login => array( rule => alphaNumeric, required => true ) );

The data sent to the models save() method must contain data for the login eld. If it doesnt, validation will fail. The default value for this key is boolean false.

More on models

151

CakePHP Cookbook Documentation, Release 2.x

required => true does not mean the same as the validation rule notEmpty(). required => true indicates that the array key must be present - it does not mean it must have a value. Therefore validation will fail if the eld is not present in the dataset, but may (depending on the rule) succeed if the value submitted is empty (). Changed in version 2.1: Support for create and update were added.
allowEmpty

If set to false, the eld value must be nonempty, where nonempty is dened as !empty($value) || is_numeric($value). The numeric check is so that CakePHP does the right thing when $value is zero. The difference between required and allowEmpty can be confusing. required => true means that you cannot save the model without the key for this eld being present in $this->data (the check is performed with isset); whereas, allowEmpty => false makes sure that the current eld value is nonempty, as described above.
on

The on key can be set to either one of the following values: update or create. This provides a mechanism that allows a certain rule to be applied either during the creation of a new record, or during update of a record. If a rule has dened on => create, the rule will only be enforced during the creation of a new record. Likewise, if it is dened as on => update, it will only be enforced during the updating of a record. The default value for on is null. When on is null, the rule will be enforced during both creation and update.
message

The message key allows you to dene a custom validation error message for the rule:
public $validate = array( password => array( rule => array(minLength, 8), message => Password must be at least 8 characters long ) );

Multiple Rules per Field The technique outlined above gives us much more exibility than simple rules assignment, but theres an extra step we can take in order to gain more ne-grained control of data validation. The next technique well outline allows us to assign multiple validation rules per model eld. If you would like to assign multiple validation rules to a single eld, this is basically how it should look:

152

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

public $validate = array( fieldName => array( ruleName => array( rule => ruleName, // extra keys like on, required, etc. go here... ), ruleName2 => array( rule => ruleName2, // extra keys like on, required, etc. go here... ) ) );

As you can see, this is quite similar to what we did in the previous section. There, for each eld we had only one array of validation parameters. In this case, each eldName consists of an array of rule indices. Each ruleName contains a separate array of validation parameters. This is better explained with a practical example:
public $validate = array( login => array( loginRule-1 => array( rule => alphaNumeric, message => Only alphabets and numbers allowed, ), loginRule-2 => array( rule => array(minLength, 8), message => Minimum length of 8 characters ) ) );

The above example denes two rules for the login eld: loginRule-1 and loginRule-2. As you can see, each rule is identied with an arbitrary name. When using multiple rules per eld the required and allowEmpty keys need to be used only once in the rst rule.
last

In case of multiple rules per eld by default if a particular rule fails error message for that rule is returned and the following rules for that eld are not processed. If you want validation to continue in spite of a rule failing set key last to false for that rule. In the following example even if rule1 fails rule2 will be processed and error messages for both failing rules will be returned if rule2 also fails:
public $validate = array( login => array( rule1 => array( rule => alphaNumeric, message => Only alphabets and numbers allowed,

More on models

153

CakePHP Cookbook Documentation, Release 2.x

last => false ), rule2 => array( rule => array(minLength, 8), message => Minimum length of 8 characters ) ) );

When specifying validation rules in this array form its possible to avoid providing the message key. Consider this example:
public $validate = array( login => array( Only alphabets and numbers allowed => array( rule => alphaNumeric, ), ) );

If the alphaNumeric rules fails the array key for this rule Only alphabets and numbers allowed will be returned as error message since the message key is not set. Custom Validation Rules If you havent found what you need thus far, you can always create your own validation rules. There are two ways you can do this: by dening custom regular expressions, or by creating custom validation methods.
Custom Regular Expression Validation

If the validation technique you need to use can be completed by using regular expression matching, you can dene a custom expression as a eld validation rule:
public $validate = array( login => array( rule => /^[a-z0-9]{3,}$/i, message => Only letters and integers, min 3 characters ) );

The example above checks if the login contains only letters and integers, with a minimum of three characters. The regular expression in the rule must be delimited by slashes. The optional trailing i after the last slash means the reg-exp is case insensitive.
Adding your own Validation Methods

Sometimes checking data with regular expression patterns is not enough. For example, if you want to ensure that a promotional code can only be used 25 times, you need to add your own validation function, as shown 154 Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

below:
class User extends AppModel { public $validate = array( promotion_code => array( rule => array(limitDuplicates, 25), message => This code has been used too many times. ) ); public function limitDuplicates($check, $limit) { // $check will have value: array(promotion_code => some-value) // $limit will have value: 25 $existing_promo_count = $this->find(count, array( conditions => $check, recursive => -1 )); return $existing_promo_count < $limit; } }

The current eld to be validated is passed into the function as rst parameter as an associated array with eld name as key and posted data as value. If you want to pass extra parameters to your validation function, add elements onto the rule array, and handle them as extra params (after the main $check param) in your function. Your validation function can be in the model (as in the example above), or in a behavior that the model implements. This includes mapped methods. Model/behavior methods are checked rst, before looking for a method on the Validation class. This means that you can override existing validation methods (such as alphaNumeric()) at an application level (by adding the method to AppModel), or at model level. When writing a validation rule which can be used by multiple elds, take care to extract the eld value from the $check array. The $check array is passed with the form eld name as its key and the eld value as its value. The full record being validated is stored in $this->data member variable:
class Post extends AppModel { public $validate = array( slug => array( rule => alphaNumericDashUnderscore, message => Slug can only be letters, numbers, dash and underscore ) ); public function alphaNumericDashUnderscore($check) { // $data array is passed using the form field name as the key // have to extract the value to make the function generic $value = array_values($check); $value = $value[0];

More on models

155

CakePHP Cookbook Documentation, Release 2.x

return preg_match(|^[0-9a-zA-Z_-]*$|, $value); } }

Note: Your own validation methods must have public visibility. protected and private are not supported.

Validation methods that are

The method should return true if the value is valid. If the validation failed, return false. The other valid return value are strings which will be shown as the error message. Returning a string means the validation failed. The string will overwrite the message set in the $validate array and be shown in the views form as the reason why the eld was not valid. Dynamically change validation rules Using $validate property to declare validation rules is a good ways of dening statically rules for each model. Nevertheless there are cases when you want to dynamically add, change or remove validation rules from the predened set. All validation rules are stored in a ModelValidator object, which holds every rule set for each eld in your model. Dening new validation rules is as easy as telling this object to store new validation methods for the elds you want to.
Adding new validation rules

New in version 2.2. The ModelValidator objects allows several ways for adding new elds to the set. The rst one is using the add method:
// Inside a model class $this->validator()->add(password, required, array( rule => notEmpty, required => create ));

This will add a single rule to the password eld in the model. You can chain multiple calls to add to create as many rules as you like:
// Inside a model class $this->validator() ->add(password, required, array( rule => notEmpty, required => create )) ->add(password, size, array( rule => array(between, 8, 20), message => Password should be at least 8 chars long ));

It is also possible to add multiple rules at once for a single eld:

156

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

$this->validator()->add(password, array( required => array( rule => notEmpty, required => create ), size => array( rule => array(between, 8, 20), message => Password should be at least 8 chars long ) ));

Alternatively, you can use the validator object to set rules directly to elds using the array interface:
$validator = $this->validator(); $validator[username] = array( unique => array( rule => isUnique, required => create ), alphanumeric => array( rule => alphanumeric ) );

Modifying current validation rules

New in version 2.2. Modifying current validation rules is also possible using the validator object, there are several ways in which you can alter current rules, append methods to a eld or completely remove a rule from a eld rule set:
// In a model class $this->validator()->getField(password)->setRule(required, array( rule => required, required => true ));

You can also completely replace all the rules for a eld using a similar method:
// In a model class $this->validator()->getField(password)->setRules(array( required => array(...), otherRule => array(...) ));

If you wish to just modify a single property in a rule you can set properties directly into the CakeValidationRule object:
// In a model class $this->validator()->getField(password) ->getRule(required)->message = This field cannot be left blank;

More on models

157

CakePHP Cookbook Documentation, Release 2.x

Properties in any CakeValidationRule are named as the valid array keys you can use for dening such rules using the $validate property in the model. As with adding new rule to the set, it is also possible to modify existing rules using the array interface:
$validator = $this->validator(); $validator[username][unique] = array( rule => isUnique, required => create ); $validator[username][unique]->last = true; $validator[username][unique]->message = Name already taken;

Removing rules from the set

New in version 2.2. It is possible to both completely remove all rules for a eld and to delete a single rule in a elds rule set:
// Completely remove all rules for a field $this->validator()->remove(username); // Remove required rule from password $this->validator()->remove(password, required);

Optionally, you can use the array interface to delete rules from the set:
$validator = $this->validator(); // Completely remove all rules for a field unset($validator[username]); // Remove required rule from password unset($validator[password][required]);

Core Validation Rules class Validation The Validation class in CakePHP contains many validation rules that can make model data validation much easier. This class contains many oft-used validation techniques you wont need to write on your own. Below, youll nd a complete list of all the rules, along with usage examples. static Validation::alphaNumeric(mixed $check) The data for the eld must only contain letters and numbers.:
public $validate = array( login => array( rule => alphaNumeric, message => Usernames must only contain letters and numbers. ) );

158

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

static Validation::between(string $check, integer $min, integer $max) The length of the data for the eld must fall within the specied numeric range. Both minimum and maximum values must be supplied. Uses = not.:
public $validate = array( password => array( rule => array(between, 5, 15), message => Passwords must be between 5 and 15 characters long. ) );

The length of data is the number of bytes in the string representation of the data. Be careful that it may be larger than the number of characters when handling non-ASCII characters. static Validation::blank(mixed $check) This rule is used to make sure that the eld is left blank or only white space characters are present in its value. White space characters include space, tab, carriage return, and newline.:
public $validate = array( id => array( rule => blank, on => create ) );

static Validation::boolean(string $check) The data for the eld must be a boolean value. Valid values are true or false, integers 0 or 1 or strings 0 or 1.:
public $validate = array( myCheckbox => array( rule => array(boolean), message => Incorrect value for myCheckbox ) );

static Validation::cc(mixed $check, mixed $type = fast, boolean $deep = false, string $regex = null) This rule is used to check whether the data is a valid credit card number. It takes three parameters: type, deep and regex. The type key can be assigned to the values of fast, all or any of the following: amex bankcard diners disc electron enroute jcb More on models 159

CakePHP Cookbook Documentation, Release 2.x

maestro mc solo switch visa voyager If type is set to fast, it validates the data against the major credit cards numbering formats. Setting type to all will check with all the credit card types. You can also set type to an array of the types you wish to match. The deep key should be set to a boolean value. If it is set to true, the validation will check the Luhn algorithm of the credit card (http://en.wikipedia.org/wiki/Luhn_algorithm). It defaults to false. The regex key allows you to supply your own regular expression that will be used to validate the credit card number:
public $validate = array( ccnumber => array( rule => array(cc, array(visa, maestro), false, null), message => The credit card number you supplied was invalid. ) );

static Validation::comparison(mixed $check1, string $operator = null, integer $check2 = null) Comparison is used to compare numeric values. It supports is greater, is less, greater or equal, less or equal, equal to, and not equal. Some examples are shown below:
public $validate = array( age => array( rule => array(comparison, >=, 18), message => Must be at least 18 years old to qualify. ) ); public $validate = array( age => array( rule => array(comparison, greater or equal, 18), message => Must be at least 18 years old to qualify. ) );

static Validation::custom(mixed $check, string $regex = null) Used when a custom regular expression is needed:
public $validate = array( infinite => array( rule => array(custom, \u221E), message => Please enter an infinite number.

160

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

) );

static Validation::date(string $check, mixed $format = ymd, string $regex = null) This rule ensures that data is submitted in valid date formats. A single parameter (which can be an array) can be passed that will be used to check the format of the supplied date. The value of the parameter can be one of the following: dmy e.g. 27-12-2006 or 27-12-06 (separators can be a space, period, dash, forward slash) mdy e.g. 12-27-2006 or 12-27-06 (separators can be a space, period, dash, forward slash) ymd e.g. 2006-12-27 or 06-12-27 (separators can be a space, period, dash, forward slash) dMy e.g. 27 December 2006 or 27 Dec 2006 Mdy e.g. December 27, 2006 or Dec 27, 2006 (comma is optional) My e.g. (December 2006 or Dec 2006) my e.g. 12/2006 or 12/06 (separators can be a space, period, dash, forward slash) If no keys are supplied, the default key that will be used is ymd:
public $validate = array( born => array( rule => array(date, ymd), message => Enter a valid date in YY-MM-DD format., allowEmpty => true ) );

While many data stores require a certain date format, you might consider doing the heavy lifting by accepting a wide-array of date formats and trying to convert them, rather than forcing users to supply a given format. The more work you can do for your users, the better. static Validation::datetime(array $check, mixed $dateFormat = ymd, string $regex = null) This rule ensures that the data is a valid datetime format. A parameter (which can be an array) can be passed to specify the format of the date. The value of the parameter can be one or more of the following: dmy e.g. 27-12-2006 or 27-12-06 (separators can be a space, period, dash, forward slash) mdy e.g. 12-27-2006 or 12-27-06 (separators can be a space, period, dash, forward slash) ymd e.g. 2006-12-27 or 06-12-27 (separators can be a space, period, dash, forward slash) dMy e.g. 27 December 2006 or 27 Dec 2006 Mdy e.g. December 27, 2006 or Dec 27, 2006 (comma is optional) My e.g. (December 2006 or Dec 2006) my e.g. 12/2006 or 12/06 (separators can be a space, period, dash, forward slash) If no keys are supplied, the default key that will be used is ymd:

More on models

161

CakePHP Cookbook Documentation, Release 2.x

public $validate = array( birthday => array( rule => array(datetime, dmy), message => Please enter a valid date and time. ) );

Also a second parameter can be passed to specify a custom regular expression. If this parameter is used, this will be the only validation that will occur. Note that unlike date(), datetime() will validate a date and a time. static Validation::decimal(integer $check, integer $places = null, string $regex = null) This rule ensures that the data is a valid decimal number. A parameter can be passed to specify the number of digits required after the decimal point. If no parameter is passed, the data will be validated as a scientic oat, which will cause validation to fail if no digits are found after the decimal point:
public $validate = array( price => array( rule => array(decimal, 2) ) );

static Validation::email(string $check, boolean $deep = false, string $regex = null) This checks whether the data is a valid email address. Passing a boolean true as the second parameter for this rule will also attempt to verify that the host for the address is valid:
public $validate = array(email => array(rule => email)); public $validate = array( email => array( rule => array(email, true), message => Please supply a valid email address. ) );

static Validation::equalTo(mixed $check, mixed $compareTo) This rule will ensure that the value is equal to, and of the same type as the given value.
public $validate = array( food => array( rule => array(equalTo, cake), message => This value must be the string cake ) );

static Validation::extension(mixed $check, array $extensions = array(gif, jpeg, png, jpg)) This rule checks for valid le extensions like .jpg or .png. Allow multiple extensions by passing them in array form.
public $validate = array( image => array( rule => array(extension, array(gif, jpeg, png, jpg)),

162

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

message => Please supply a valid image. ) );

static Validation::fileSize($check, $operator = null, $size = null) This rule allows you to check lesizes. You can use $operator to decide the type of comparison you want to use. All the operators supported by comparison() are supported here as well. This method will automatically handle array values from $_FILES by reading from the tmp_name key if $check is an array an contains that key:
public $validate = array( image => array( rule => array(filesize, <=, 1MB), message => Image must be less than 1MB ) );

New in version 2.3: This method was added in 2.3 static Validation::inList(string $check, array $list) This rule will ensure that the value is in a given set. It needs an array of values. The eld is valid if the elds value matches one of the values in the given array. Example:
public $validate = array( function => array( allowedChoice => array( rule => array(inList, array(Foo, Bar)), message => Enter either Foo or Bar. ) ) );

static Validation::ip(string $check, string $type = both) This rule will ensure that a valid IPv4 or IPv6 address has been submitted. Accepts as option both (default), IPv4 or IPv6.
public $validate = array( clientip => array( rule => array(ip, IPv4), // or IPv6 or both (default) message => Please supply a valid IP address. ) );

static Validation::isUnique The data for the eld must be unique, it cannot be used by any other rows.
public $validate = array( login => array( rule => isUnique, message => This username has already been taken. ) );

More on models

163

CakePHP Cookbook Documentation, Release 2.x

static Validation::luhn(string|array $check, boolean $deep = false) The Luhn algorithm: A checksum formula to validate a variety of identication numbers. See http://en.wikipedia.org/wiki/Luhn_algorithm for more information. static Validation::maxLength(string $check, integer $max) This rule ensures that the data stays within a maximum length requirement.
public $validate = array( login => array( rule => array(maxLength, 15), message => Usernames must be no larger than 15 characters long. ) );

The length here is the number of bytes in the string representation of the data. Be careful that it may be larger than the number of characters when handling non-ASCII characters. static Validation::mimeType(mixed $check, array $mimeTypes) New in version 2.2. This rule checks for valid mimeType
public $validate = array( image => array( rule => array(mimeType, array(image/gif)), message => Invalid mime type. ), );

static Validation::minLength(string $check, integer $min) This rule ensures that the data meets a minimum length requirement.
public $validate = array( login => array( rule => array(minLength, 8), message => Usernames must be at least 8 characters long. ) );

The length here is the number of bytes in the string representation of the data. Be careful that it may be larger than the number of characters when handling non-ASCII characters. static Validation::money(string $check, string $symbolPosition = left) This rule will ensure that the value is in a valid monetary amount. Second parameter denes where symbol is located (left/right).
public $validate = array( salary => array( rule => array(money, left), message => Please supply a valid monetary amount. ) );

static Validation::multiple(mixed $check, mixed $options = array()) Use this for validating a multiple select input. It supports parameters in, max and min.

164

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

public $validate = array( multiple => array( rule => array(multiple, array( in => array(do, ray, me, fa, so, la, ti), min => 1, max => 3 )), message => Please select one, two or three options ) );

static Validation::notEmpty(mixed $check) The basic rule to ensure that a eld is not empty.:
public $validate = array( title => array( rule => notEmpty, message => This field cannot be left blank ) );

Do not use this for a multiple select input as it will cause an error. Instead, use multiple. static Validation::numeric(string $check) Checks if the data passed is a valid number.:
public $validate = array( cars => array( rule => numeric, message => Please supply the number of cars. ) );

static Validation::naturalNumber(mixed $check, boolean $allowZero = false) New in version 2.2. This rule checks if the data passed is a valid natural number. If $allowZero is set to true, zero is also accepted as a value.
public $validate = array( wheels => array( rule => naturalNumber, message => Please supply the number of wheels. ), airbags => array( rule => array(naturalNumber, true), message => Please supply the number of airbags. ), );

static Validation::phone(mixed $check, string $regex = null, string $country = all) Phone validates US phone numbers. If you want to validate non-US phone numbers, you can provide a regular expression as the second parameter to cover additional number formats.

More on models

165

CakePHP Cookbook Documentation, Release 2.x

public $validate = array( phone => array( rule => array(phone, null, us) ) );

static Validation::postal(mixed $check, string $regex = null, string $country = us) Postal is used to validate ZIP codes from the U.S. (us), Canada (ca), U.K (uk), Italy (it), Germany (de) and Belgium (be). For other ZIP code formats, you may provide a regular expression as the second parameter.
public $validate = array( zipcode => array( rule => array(postal, null, us) ) );

static Validation::range(string $check, integer $lower = null, integer $upper = null) This rule ensures that the value is in a given range. If no range is supplied, the rule will check to ensure the value is a legal nite on the current platform.
public $validate = array( number => array( rule => array(range, -1, 11), message => Please enter a number between 0 and 10 ) );

The above example will accept any value which is larger than 0 (e.g., 0.01) and less than 10 (e.g., 9.99). Note: The range lower/upper are not inclusive static Validation::ssn(mixed $check, string $regex = null, string $country = null) Ssn validates social security numbers from the U.S. (us), Denmark (dk), and the Netherlands (nl). For other social security number formats, you may provide a regular expression.
public $validate = array( ssn => array( rule => array(ssn, null, us) ) );

static Validation::time(string $check) Time validation, determines if the string passed is a valid time. Validates time as 24hr (HH:MM) or am/pm ([H]H:MM[a|p]m) Does not allow/validate seconds. static Validation::uploadError(mixed $check) New in version 2.2. This rule checks if a le upload has an error.

166

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

public $validate = array( image => array( rule => uploadError, message => Something went wrong with the upload. ), );

static Validation::url(string $check, boolean $strict = false) This rule checks for valid URL formats. Supports http(s), ftp(s), le, news, and gopher protocols:
public $validate = array( website => array( rule => url ) );

To ensure that a protocol is in the url, strict mode can be enabled like so:
public $validate = array( website => array( rule => array(url, true) ) );

static Validation::userDefined(mixed $check, object $object, string $method, array $args = null) Runs an user-dened validation. static Validation::uuid(string $check) Checks that a value is a valid uuid: http://tools.ietf.org/html/rfc4122 Localized Validation The validation rules phone() and postal() will pass off any country prex they do not know how to handle to another class with the appropriate name. For example if you lived in the Netherlands you would create a class like:
class NlValidation { public static function phone($check) { // ... } public static function postal($check) { // ... } }

This le could be placed in APP/Validation/ or App/PluginName/Validation/, but must be imported via App::uses() before attempting to use it. In your model validation you could use your NlValidation class by doing the following:
public $validate = array( phone_no => array(rule => array(phone, null, nl)),

More on models

167

CakePHP Cookbook Documentation, Release 2.x

postal_code => array(rule => array(postal, null, nl)), );

When your model data is validated, Validation will see that it cannot handle the nl locale and will attempt to delegate out to NlValidation::postal() and the return of that method will be used as the pass/fail for the validation. This approach allows you to create classes that handle a subset or group of locales, something that a large switch would not have. The usage of the individual validation methods has not changed, the ability to pass off to another validator has been added. Tip: The Localized Plugin already contains a lot of rules ready to use: https://github.com/cakephp/localized Also feel free to contribute with your localized validation rules.

Validating Data from the Controller

While normally you would just use the save method of the model, there may be times where you wish to validate the data without saving it. For example, you may wish to display some additional information to the user before actually saving the data to the database. Validating data requires a slightly different process than just saving the data. First, set the data to the model:
$this->ModelName->set($this->request->data);

Then, to check if the data validates, use the validates method of the model, which will return true if it validates and false if it doesnt:
if ($this->ModelName->validates()) { // it validated logic } else { // didnt validate logic $errors = $this->ModelName->validationErrors; }

It may be desirable to validate your model only using a subset of the validations specied in your model. For example say you had a User model with elds for rst_name, last_name, email and password. In this instance when creating or editing a user you would want to validate all 4 eld rules. Yet when a user logs in you would validate just email and password rules. To do this you can pass an options array specifying the elds to validate:
if ($this->User->validates(array(fieldList => array(email, password)))) { // valid } else { // invalid }

The validates method invokes the invalidFields method which populates the validationErrors property of the model. The invalidFields method also returns that data as the result:

168

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

$errors = $this->ModelName->invalidFields(); // contains validationErrors array

The validation errors list is not cleared between successive calls to invalidFields() So if you are validating in a loop and want each set of errors separately dont use invalidFields(). Instead use validates() and access the validationErrors model property. It is important to note that the data must be set to the model before the data can be validated. This is different from the save method which allows the data to be passed in as a parameter. Also, keep in mind that it is not required to call validates prior to calling save as save will automatically validate the data before actually saving. To validate multiple models, the following approach should be used:
if ($this->ModelName->saveAll($this->request->data, array(validate => only))) { // validates } else { // does not validate }

If you have validated data before save, you can turn off validation to avoid second check:
if ($this->ModelName->saveAll($this->request->data, array(validate => false))) { // saving without validation }

Callback Methods
If you want to sneak in some logic just before or after a CakePHP model operation, use model callbacks. These functions can be dened in model classes (including your AppModel) class. Be sure to note the expected return values for each of these special functions. beforeFind beforeFind(array $queryData) Called before any nd-related operation. The $queryData passed to this callback contains information about the current query: conditions, elds, etc. If you do not wish the nd operation to begin (possibly based on a decision relating to the $queryData options), return false. Otherwise, return the possibly modied $queryData, or anything you want to get passed to nd and its counterparts. You might use this callback to restrict nd operations based on a users role, or make caching decisions based on the current load. afterFind afterFind(array $results, boolean $primary = false)

More on models

169

CakePHP Cookbook Documentation, Release 2.x

Use this callback to modify results that have been returned from a nd operation, or to perform any other post-nd logic. The $results parameter passed to this callback contains the returned results from the models nd operation, i.e. something like:
$results = array( 0 => array( ModelName => array( field1 => value1, field2 => value2, ), ), );

The return value for this callback should be the (possibly modied) results for the nd operation that triggered this callback. The $primary parameter indicates whether or not the current model was the model that the query originated on or whether or not this model was queried as an association. If a model is queried as an association the format of $results can differ; instead of the result you would normally get from a nd operation, you may get this:
$results = array( field_1 => value1, field_2 => value2 );

Warning: Code expecting $primary to be true will probably get a Cannot use string offset as an array fatal error from PHP if a recursive nd is used. Below is an example of how afternd can be used for date formatting:

public function afterFind($results, $primary = false) { foreach ($results as $key => $val) { if (isset($val[Event][begindate])) { $results[$key][Event][begindate] = $this->dateFormatAfterFind($val[Event] } } return $results; } public function dateFormatAfterFind($dateString) { return date(d-m-Y, strtotime($dateString)); }

beforeValidate beforeValidate(array $options = array()) Use this callback to modify model data before it is validated, or to modify validation rules if required. This function must also return true, otherwise the current save() execution will abort.

170

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

beforeSave beforeSave(array $options = array()) Place any pre-save logic in this function. This function executes immediately after model data has been successfully validated, but just before the data is saved. This function should also return true if you want the save operation to continue. This callback is especially handy for any data-massaging logic that needs to happen before your data is stored. If your storage engine needs dates in a specic format, access it at $this->data and modify it. Below is an example of how beforeSave can be used for date conversion. The code in the example is used for an application with a begindate formatted like YYYY-MM-DD in the database and is displayed like DD-MM-YYYY in the application. Of course this can be changed very easily. Use the code below in the appropriate model.

public function beforeSave($options = array()) { if (!empty($this->data[Event][begindate]) && !empty($this->data[Event][enddate] $this->data[Event][begindate] = $this->dateFormatBeforeSave($this->data[Event $this->data[Event][enddate] = $this->dateFormatBeforeSave($this->data[Event][ } return true; } public function dateFormatBeforeSave($dateString) { return date(Y-m-d, strtotime($dateString)); }

Tip: Be sure that beforeSave() returns true, or your save is going to fail.

afterSave afterSave(boolean $created) If you have logic you need to be executed just after every save operation, place it in this callback method. The value of $created will be true if a new record was created (rather than an update). beforeDelete beforeDelete(boolean $cascade = true) Place any pre-deletion logic in this function. This function should return true if you want the deletion to continue, and false if you want to abort. The value of $cascade will be true if records that depend on this record will also be deleted. Tip: Be sure that beforeDelete() returns true, or your delete is going to fail.

More on models

171

CakePHP Cookbook Documentation, Release 2.x

// using app/Model/ProductCategory.php // In the following example, do not let a product category be deleted if it still contains // A call of $this->Product->delete($id) from ProductsController.php has set $this->id . // Assuming ProductCategory hasMany Product, we can access $this->Product in the model. public function beforeDelete($cascade = true) { $count = $this->Product->find("count", array( "conditions" => array("product_category_id" => $this->id) )); if ($count == 0) { return true; } else { return false; } }

afterDelete afterDelete() Place any logic that you want to be executed after every deletion in this callback method. onError onError() Called if any problems occur.

Behaviors
Model behaviors are a way to organize some of the functionality dened in CakePHP models. They allow us to separate and reuse logic that creates a type of behavior, and they do this without requiring inheritance. For example creating tree structures. By providing a simple yet powerful way to enhance models, behaviors allow us to attach functionality to models by dening a simple class variable. Thats how behaviors allow models to get rid of all the extra weight that might not be part of the business contract they are modeling, or that is also needed in different models and can then be extrapolated. As an example, consider a model that gives us access to a database table which stores structural information about a tree. Removing, adding, and migrating nodes in the tree is not as simple as deleting, inserting, and editing rows in the table. Many records may need to be updated as things move around. Rather than creating those tree-manipulation methods on a per model basis (for every model that needs that functionality), we could simply tell our model to use the TreeBehavior, or in more formal terms, we tell our model to behave as a Tree. This is known as attaching a behavior to a model. With just one line of code, our CakePHP model takes on a whole new set of methods that allow it to interact with the underlying structure. CakePHP already includes behaviors for tree structures, translated content, access control list interaction, not to mention the community-contributed behaviors already available in the CakePHP Bakery (http://bakery.cakephp.org). In this section, well cover the basic usage pattern for adding behaviors to models, how to use CakePHPs built-in behaviors, and how to create our own.

172

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

In essence, Behaviors are Mixins (http://en.wikipedia.org/wiki/Mixin) with callbacks. Using Behaviors Behaviors are attached to models through the $actsAs model class variable:
class Category extends AppModel { public $actsAs = array(Tree); }

This example shows how a Category model could be managed in a tree structure using the TreeBehavior. Once a behavior has been specied, use the methods added by the behavior as if they always existed as part of the original model:
// Set ID $this->Category->id = 42; // Use behavior method, children(): $kids = $this->Category->children();

Some behaviors may require or allow settings to be dened when the behavior is attached to the model. Here, we tell our TreeBehavior the names of the left and right elds in the underlying database table:
class Category extends AppModel { public $actsAs = array(Tree => array( left => left_node, right => right_node )); }

We can also attach several behaviors to a model. Theres no reason why, for example, our Category model should only behave as a tree, it may also need internationalization support:
class Category extends AppModel { public $actsAs = array( Tree => array( left => left_node, right => right_node ), Translate ); }

So far we have been adding behaviors to models using a model class variable. That means that our behaviors will be attached to our models throughout the models lifetime. However, we may need to detach behaviors from our models at runtime. Lets say that on our previous Category model, which is acting as a Tree and a Translate model, we need for some reason to force it to stop acting as a Translate model:
// Detach a behavior from our model: $this->Category->Behaviors->unload(Translate);

That will make our Category model stop behaving as a Translate model from thereon. We may need, instead, to just disable the Translate behavior from acting upon our normal model operations: our nds, our saves, More on models 173

CakePHP Cookbook Documentation, Release 2.x

etc. In fact, we are looking to disable the behavior from acting upon our CakePHP model callbacks. Instead of detaching the behavior, we then tell our model to stop informing of these callbacks to the Translate behavior:
// Stop letting the behavior handle our model callbacks $this->Category->Behaviors->disable(Translate);

We may also need to nd out if our behavior is handling those model callbacks, and if not we then restore its ability to react to them:
// If our behavior is not handling model callbacks if (!$this->Category->Behaviors->enabled(Translate)) { // Tell it to start doing so $this->Category->Behaviors->enable(Translate); }

Just as we could completely detach a behavior from a model at runtime, we can also attach new behaviors. Say that our familiar Category model needs to start behaving as a Christmas model, but only on Christmas day:
// If today is Dec 25 if (date(m/d) == 12/25) { // Our model needs to behave as a Christmas model $this->Category->Behaviors->load(Christmas); }

We can also use the load method to override behavior settings:


// We will change one setting from our already attached behavior $this->Category->Behaviors->load(Tree, array(left => new_left_node));

Theres also a method to obtain the list of behaviors a model has attached. If we pass the name of a behavior to the method, it will tell us if that behavior is attached to the model, otherwise it will give us the list of attached behaviors:
// If the Translate behavior is not attached if (!$this->Category->Behaviors->loaded(Translate)) { // Get the list of all behaviors the model has attached $behaviors = $this->Category->Behaviors->loaded(); }

Creating Behaviors Behaviors that are attached to Models get their callbacks called automatically. The callbacks are similar to those found in Models: beforeFind, afterFind, beforeSave, afterSave, beforeDelete, afterDelete and onError - see Callback Methods. Your behaviors should be placed in app/Model/Behavior. They are named in CamelCase and postxed by Behavior, ex. NameBehavior.php. Its often helpful to use a core behavior as a template when creating your own. Find them in lib/Cake/Model/Behavior/. Every callback and behavior method takes a reference to the model it is being called from as the rst parameter. 174 Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Besides implementing the callbacks, you can add settings per behavior and/or model behavior attachment. Information about specifying settings can be found in the chapters about core behaviors and their conguration. A quick example that illustrates how behavior settings can be passed from the model to the behavior:
class Post extends AppModel { public $actsAs = array( YourBehavior => array( option1_key => option1_value ) ); }

Since behaviors are shared across all the model instances that use them, its a good practice to store the settings per alias/model name that is using the behavior. When created behaviors will have their setup() method called:
public function setup(Model $Model, $settings = array()) { if (!isset($this->settings[$Model->alias])) { $this->settings[$Model->alias] = array( option1_key => option1_default_value, option2_key => option2_default_value, option3_key => option3_default_value, ); } $this->settings[$Model->alias] = array_merge( $this->settings[$Model->alias], (array)$settings); }

Creating behavior methods Behavior methods are automatically available on any model acting as the behavior. For example if you had:
class Duck extends AppModel { public $actsAs = array(Flying); }

You would be able to call FlyingBehavior methods as if they were methods on your Duck model. When creating behavior methods you automatically get passed a reference of the calling model as the rst parameter. All other supplied parameters are shifted one place to the right. For example:
$this->Duck->fly(toronto, montreal);

Although this method takes two parameters, the method signature should look like:
public function fly(Model $Model, $from, $to) { // Do some flying. }

Keep in mind that methods called in a $this->doIt() fashion from inside a behavior method will not get the $model parameter automatically appended.

More on models

175

CakePHP Cookbook Documentation, Release 2.x

Mapped methods

In addition to providing mixin methods, behaviors can also provide pattern matching methods. Behaviors can also dene mapped methods. Mapped methods use pattern matching for method invocation. This allows you to create methods similar to Model::findAllByXXX methods on your behaviors. Mapped methods need to be declared in your behaviors $mapMethods array. The method signature for a mapped method is slightly different than a normal behavior mixin method:
class MyBehavior extends ModelBehavior { public $mapMethods = array(/do(\w+)/ => doSomething); public function doSomething(Model $model, $method, $arg1, $arg2) { debug(func_get_args()); //do something } }

The above will map every doXXX() method call to the behavior. As you can see, the model is still the rst parameter, but the called method name will be the 2nd parameter. This allows you to munge the method name for additional information, much like Model::findAllByXX. If the above behavior was attached to a model the following would happen:
$model->doReleaseTheHounds(homer, lenny); // would output ReleaseTheHounds, homer, lenny

Behavior callbacks Model Behaviors can dene a number of callbacks that are triggered before/after the model callbacks of the same name. Behavior callbacks allow your behaviors to capture events in attached models and augment the parameters or splice in additional behavior. The available callbacks are: beforeValidate is red before a models beforeValidate beforeFind is red before a models beforeFind afterFind is red before a models afterFind beforeSave is red before a models beforeSave afterSave is red before a models afterSave beforeDelete is red after a models beforeDelete afterDelete is red before a models afterDelete
Creating a behavior callback

class ModelBehavior 176 Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Model behavior callbacks are dened as simple methods in your behavior class. Much like regular behavior methods, they receive a $Model parameter as the rst argument. This parameter is the model that the behavior method was invoked on. ModelBehavior::setup(Model $Model, array $settings = array()) Called when a behavior is attached to a model. The settings come from the attached models $actsAs property. ModelBehavior::cleanup(Model $Model) Called when a behavior is detached from a model. The base method removes model settings based on $model->alias. You can override this method and provide custom cleanup functionality. ModelBehavior::beforeFind(Model $Model, array $query) If a behaviors beforeFind returns false it will abort the nd(). Returning an array will augment the query parameters used for the nd operation. ModelBehavior::afterFind(Model $Model, mixed $results, boolean $primary) You can use the afterFind to augment the results of a nd. The return value will be passed on as the results to either the next behavior in the chain or the models afterFind. ModelBehavior::beforeDelete(Model $Model, boolean $cascade = true) You can return false from a behaviors beforeDelete to abort the delete. Return true to allow it continue. ModelBehavior::afterDelete(Model $Model) You can use afterDelete to perform clean up operations related to your behavior. ModelBehavior::beforeSave(Model $Model) You can return false from a behaviors beforeSave to abort the save. Return true to allow it continue. ModelBehavior::afterSave(Model $Model, boolean $created) You can use afterSave to perform clean up operations related to your behavior. $created will be true when a record is created, and false when a record is updated. ModelBehavior::beforeValidate(Model $Model) You can use beforeValidate to modify a models validate array or handle any other pre-validation logic. Returning false from a beforeValidate callback will abort the validation and cause it to fail.

DataSources
DataSources are the link between models and the source of data that models represent. In many cases, the data is retrieved from a relational database such as MySQL, PostgreSQL or MSSQL. CakePHP is distributed with several database-specic datasources (see the class les in lib/Cake/Model/Datasource/Database), a summary of which is listed here for your convenience: MySql Postgres Sqlite Sqlserver

More on models

177

CakePHP Cookbook Documentation, Release 2.x

Note: You can nd additional community contributed datasources in the CakePHP DataSources repository at github (https://github.com/cakephp/datasources/tree/2.0). When specifying a database connection conguration in app/Config/database.php, CakePHP transparently uses the corresponding database datasource for all model operations. So, even though you might not have known about datasources, youve been using them all along. All of the above sources derive from a base DboSource class, which aggregates some logic that is common to most relational databases. If you decide to write a RDBMS datasource, working from one of these (e.g. Mysql, or Sqlite is your best bet.) Most people, however, are interested in writing datasources for external sources of data, such as remote REST APIs or even an LDAP server. So thats what were going to look at now. Basic API For DataSources A datasource can, and should implement at least one of the following methods: create, read, update and/or delete (the actual method signatures & implementation details are not important for the moment, and will be described later). You need not implement more of the methods listed above than necessary - if you need a read-only datasource, theres no reason to implement create, update, and delete. Methods that must be implemented for all CRUD methods: describe($model) listSources($data = null) calculate($model, $func, $params) At least one of: create(Model $model, $fields = null, $values = null) read(Model $model, $queryData = array(), $recursive = null) update(Model $model, $fields = null, $values = null, $conditions = null) delete(Model $model, $id = null) It is also possible (and sometimes quite useful) to dene the $_schema class attribute inside the datasource itself, instead of in the model. And thats pretty much all there is to it. By coupling this datasource to a model, you are then able to use Model::find()/save()/delete() as you would normally, and the appropriate data and/or parameters used to call those methods will be passed on to the datasource itself, where you can decide to implement whichever features you need (e.g. Model::nd options such as conditions parsing, limit or even your own custom parameters).

178

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

An Example A common reason you would want to write your own datasource is when you would like to access a 3rd party API using the usual Model::find()/save()/delete() methods. Lets write a datasource that will access a ctitious remote JSON based API. Well call it FarAwaySource and well put it in app/Model/Datasource/FarAwaySource.php:
App::uses(HttpSocket, Network/Http); class FarAwaySource extends DataSource { /** * An optional description of your datasource */ public $description = A far away datasource; /** * Our default config options. These options will be customized in our * app/Config/database.php and will be merged in the __construct(). */ public $config = array( apiKey => , ); /** * If we want to create() or update() we need to specify the fields * available. We use the same array keys as we do with CakeSchema, eg. * fixtures and schema migrations. */ protected $_schema = array( id => array( type => integer, null => false, key => primary, length => 11, ), name => array( type => string, null => true, length => 255, ), message => array( type => text, null => true, ), ); /** * Create our HttpSocket and handle any config tweaks. */ public function __construct($config) { parent::__construct($config); $this->Http = new HttpSocket();

More on models

179

CakePHP Cookbook Documentation, Release 2.x

} /** * Since datasources normally connect to a database there are a few things * we must change to get them to work without a database. */ /** * listSources() is for caching. Youll likely want to implement caching in * your own way with a custom datasource. So just return null. */ public function listSources($data = null) { return null; } /** * describe() tells the model your schema for Model::save(). * * You may want a different schema for each model but still use a single * datasource. If this is your case then set a schema property on your * models and simply return $model->schema here instead. */ public function describe($model) { return $this->_schema; } /** * calculate() is for determining how we will count the records and is * required to get update() and delete() to work. * * We dont count the records here but return a string to be passed to * read() which will do the actual counting. The easiest way is to just * return the string COUNT and check for it in read() where * $data[fields] == COUNT. */ public function calculate(Model $model, $func, $params = array()) { return COUNT; } /** * Implement the R in CRUD. Calls to Model::find() arrive here. */ public function read(Model $model, $queryData = array(), $recursive = null) { /** * Here we do the actual count as instructed by our calculate() * method above. We could either check the remote source or some * other way to get the record count. Here well simply return 1 so * update() and delete() will assume the record exists. */ if ($queryData[fields] == COUNT) { return array(array(array(count => 1))); } /**

180

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

* Now we get, decode and return the remote data. */ $queryData[conditions][apiKey] = $this->config[apiKey]; $json = $this->Http->get(http://example.com/api/list.json, $queryData[conditions $res = json_decode($json, true); if (is_null($res)) { $error = json_last_error(); throw new CakeException($error); } return array($model->alias => $res); } /** * Implement the C in CRUD. Calls to Model::save() without $model->id * set arrive here. */ public function create(Model $model, $fields = null, $values = null) { $data = array_combine($fields, $values); $data[apiKey] = $this->config[apiKey]; $json = $this->Http->post(http://example.com/api/set.json, $data); $res = json_decode($json, true); if (is_null($res)) { $error = json_last_error(); throw new CakeException($error); } return true; }

/** * Implement the U in CRUD. Calls to Model::save() with $Model->id * set arrive here. Depending on the remote source you can just call * $this->create(). */ public function update(Model $model, $fields = null, $values = null, $conditions = null return $this->create($model, $fields, $values); } /** * Implement the D in CRUD. Calls to Model::delete() arrive here. */ public function delete(Model $model, $id = null) { $json = $this->Http->get(http://example.com/api/remove.json, array( id => $id[$model->alias . .id], apiKey => $this->config[apiKey], )); $res = json_decode($json, true); if (is_null($res)) { $error = json_last_error(); throw new CakeException($error); } return true; }

More on models

181

CakePHP Cookbook Documentation, Release 2.x

We can then congure the datasource in our app/Config/database.php le by adding something like this:
public $faraway = array( datasource => FarAwaySource, apiKey => 1234abcd, );

Then use the database cong in our models like this:


class MyModel extends AppModel { public $useDbConfig = faraway; }

We can retrieve data from our remote source using the familiar model methods:
// Get all messages from Some Person $messages = $this->MyModel->find(all, array( conditions => array(name => Some Person), ));

Similarly we can save a new message:


$this->MyModel->save(array( name => Some Person, message => New Message, ));

Update the previous message:


$this->MyModel->id = 42; $this->MyModel->save(array( message => Updated message, ));

And delete the message:


$this->MyModel->delete(42);

Plugin DataSources You can also package Datasources into plugins. Simply place your datasource le into Plugin/[YourPlugin]/Model/Datasource/[YourSource].php and refer to it using the plugin notation:
public $faraway = array( datasource => MyPlugin.FarAwaySource, apiKey => abcd1234, );

182

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Model Attributes
Model attributes allow you to set properties that can override the default model behavior. For a complete list of model attributes and their descriptions visit the CakePHP API. Check out http://api20.cakephp.org/class/model. useDbCong The useDbConfig property is a string that species the name of the database connection to use to bind your model class to the related database table. You can set it to any of the database connections dened within your database conguration le. The database conguration le is stored in /app/Cong/database.php. The useDbConfig property is defaulted to the default database connection. Example usage:
class Example extends AppModel { public $useDbConfig = alternate; }

useTable The useTable property species the database table name. By default, the model uses the lowercase, plural form of the models class name. Set this attribute to the name of an alternate table, or set it to false if you wish the model to use no database table. Example usage:
class Example extends AppModel { public $useTable = false; // This model does not use a database table }

Alternatively:
class Example extends AppModel { public $useTable = exmp; // This model uses a database table exmp }

tablePrex The name of the table prex used for the model. The table prex is initially set in the database connection le at /app/Cong/database.php. The default is no prex. You can override the default by setting the tablePrefix attribute in the model. Example usage:
class Example extends AppModel { public $tablePrefix = alternate_; // will look for alternate_examples }

More on models

183

CakePHP Cookbook Documentation, Release 2.x

primaryKey Each table normally has a primary key, id. You may change which eld name the model uses as its primary key. This is common when setting CakePHP to use an existing database table. Example usage:
class Example extends AppModel { public $primaryKey = example_id; // example_id is the field name in the database }

displayField The displayField attribute species which database eld should be used as a label for the record. The label is used in scaffolding and in find(list) calls. The model will use name or title, by default. For example, to use the username eld:
class User extends AppModel { public $displayField = username; }

Multiple eld names cannot be combined into a single display eld. For example, you cannot specify, array(first_name, last_name) as the display eld. Instead create a virtual eld with the Model attribute virtualFields recursive The recursive property denes how deep CakePHP should go to fetch associated model data via find(), findAll() and read() methods. Imagine your application features Groups which belong to a domain and have many Users which in turn have many Articles. You can set $recursive to different values based on the amount of data you want back from a $this->Group->nd() call: -1 Cake fetches Group data only, no joins. 0 Cake fetches Group data and its domain 1 Cake fetches a Group, its domain and its associated Users 2 Cake fetches a Group, its domain, its associated Users, and the Users associated Articles Set it no higher than you need. Having CakePHP fetch data you arent going to use slows your app unnecessarily. Also note that the default recursive level is 1. Note: If you want to combine $recursive with the fields functionality, you will have to add the columns containing the required foreign keys to the fields array manually. In the example above, this could mean adding domain_id.

184

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

order The default ordering of data for any nd operation. Possible values include:
$order $order $order $order $order $order = = = = = = "field" "Model.field"; "Model.field asc"; "Model.field ASC"; "Model.field DESC"; array("Model.field" => "asc", "Model.field2" => "DESC");

data The container for the models fetched data. While data returned from a model class is normally used as returned from a nd() call, you may need to access information stored in $data inside of model callbacks. _schema Contains metadata describing the models database table elds. Each eld is described by: name type (integer, string, datetime, etc.) null default value length Example Usage:
public $_schema = array( first_name => array( type => string, length => 30 ), last_name => array( type => string, length => 30 ), email => array( type => string, length => 30 ), message => array(type => text) );

More on models

185

CakePHP Cookbook Documentation, Release 2.x

validate This attribute holds rules that allow the model to make data validation decisions before saving. Keys named after elds hold regex values allowing the model to try to make matches. Note: It is not necessary to call validate() before save() as save() will automatically validate your data before actually saving. For more information on validation, see the Data Validation later on in this manual. virtualFields Array of virtual elds this model has. Virtual elds are aliased SQL expressions. Fields added to this property will be read as other elds in a model but will not be saveable. Example usage for MySQL:
public $virtualFields = array( name => "CONCAT(User.first_name, , User.last_name)" );

In subsequent nd operations, your User results would contain a name key with the result of the concatenation. It is not advisable to create virtual elds with the same names as columns on the database, this can cause SQL errors. For more information on the virtualFields property, its proper usage, as well as limitations, see Virtual elds. name Name of the model. If you do not specify it in your model le it will be set to the class name by constructor. Example usage:
class Example extends AppModel { public $name = Example; }

cacheQueries If set to true, data fetched by the model during a single request is cached. This caching is in-memory only, and only lasts for the duration of the request. Any duplicate requests for the same data is handled by the cache.

186

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Additional Methods and Properties


While CakePHPs model functions should get you where you need to go, dont forget that model classes are just that: classes that allow you to write your own methods or dene your own properties. Any operation that handles the saving and fetching of data is best housed in your model classes. This concept is often referred to as the fat model.

class Example extends AppModel { public function getRecent() { $conditions = array( created BETWEEN (curdate() - interval 7 day) and (curdate() - interval 0 day)) ); return $this->find(all, compact(conditions)); } }

This getRecent() method can now be used within the controller.


$recent = $this->Example->getRecent();

Model::associations() Get associations:


$result = $this->Example->associations(); // $result equals array(belongsTo, hasOne, hasMany, hasAndBelongsToMany)

Model::buildQuery(string $type = first, array $query = array()) Builds the query array that is used by the data source to generate the query to fetch the data. Model::deconstruct(string $field, mixed $data) Deconstructs a complex data type (array or object) into a single eld value. Model::escapeField(string $field = null, string $alias = null) Escapes the eld name and prepends the model name. Escaping is done according to the current database drivers rules. Model::exists($id) Returns true if a record with the particular ID exists. If ID is not provided it calls Model::getID() to obtain the current record ID to verify, and then performs a Model::find(count) on the currently congured datasource to ascertain the existence of the record in persistent storage. More on models 187

CakePHP Cookbook Documentation, Release 2.x

Note: Parameter $id was added in 2.1. Prior to that it does not take any parameter.
$this->Example->id = 9; if ($this->Example->exists()) { // ... } $exists = $this->Foo->exists(2);

Model::getAffectedRows() Returns the number of rows affected by the last query. Model::getAssociated(string $type = null) Gets all the models with which this model is associated. Model::getColumnType(string $column) Returns the column type of a column in the model. Model::getColumnTypes() Returns an associative array of eld names and column types. Model::getID(integer $list = 0) Returns the current records ID. Model::getInsertID() Returns the ID of the last record this model inserted. Model::getLastInsertID() Alias to getInsertID().

Virtual elds
Virtual elds allow you to create arbitrary SQL expressions and assign them as elds in a Model. These elds cannot be saved, but will be treated like other model elds for read operations. They will be indexed under the models key alongside other model elds. 188 Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

Creating virtual elds Creating virtual elds is easy. In each model you can dene a $virtualFields property that contains an array of eld => expressions. An example of a virtual eld denition using MySQL would be:
public $virtualFields = array( name => CONCAT(User.first_name, " ", User.last_name) );

And with PostgreSQL:


public $virtualFields = array( name => User.first_name || \ \ || User.last_name );

In subsequent nd operations, your User results would contain a name key with the result of the concatenation. It is not advisable to create virtual elds with the same names as columns on the database, this can cause SQL errors. It is not always useful to have User.rst_name fully qualied. If you do not follow the convention (i.e. you have multiple relations to other tables) this would result in an error. In this case it may be better to just use first_name || \ \ || last_name without the Model Name. Using virtual elds Creating virtual elds is straightforward and easy, interacting with virtual elds can be done through a few different methods.
Model::hasField()

Model::hasField() will return true if the model has a concrete eld passed by the rst parameter. By setting the second parameter of hasField() to true, virtualFields will also be checked when checking if a model has a eld. Using the example eld above:

$this->User->hasField(name); // Will return false, as there is no concrete field called n $this->User->hasField(name, true); // Will return true as there is a virtual field called

Model::isVirtualField()

This method can be used to check if a eld/column is a virtual eld or a concrete eld. Will return true if the column is virtual:
$this->User->isVirtualField(name); //true $this->User->isVirtualField(first_name); //false

More on models

189

CakePHP Cookbook Documentation, Release 2.x

Model::getVirtualField()

This method can be used to access the SQL expression that comprises a virtual eld. If no argument is supplied it will return all virtual elds in a Model:

$this->User->getVirtualField(name); //returns CONCAT(User.first_name, , User.last_nam

Model::nd() and virtual elds

As stated earlier Model::find() will treat virtual elds much like any other eld in a model. The value of a virtual eld will be placed under the models key in the resultset:
$results = $this->User->find(first); // results contains the following array( User => array( first_name => Mark, last_name => Story, name => Mark Story, //more fields. ) );

Pagination and virtual elds

Since virtual elds behave much like regular elds when doing nds, Controller::paginate() will be able to sort by virtual elds too. Virtual elds and model aliases When you are using virtualFields and models with aliases that are not the same as their name, you can run into problems as virtualFields do not update to reect the bound alias. If you are using virtualFields in models that have more than one alias it is best to dene the virtualFields in your models constructor:

public function __construct($id = false, $table = null, $ds = null) { parent::__construct($id, $table, $ds); $this->virtualFields[name] = sprintf(CONCAT(%s.first_name, " ", %s.last_name), $thi }

This will allow your virtualFields to work for any alias you give a model. Virtual elds in SQL queries Using functions in direct SQL queries will prevent data from being returned in the same array as your models data. For example this:

190

Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

$this->Timelog->query("SELECT project_id, SUM(id) as TotalHours FROM timelogs AS Timelog GR

would return something like this:


Array ( [0] => Array ( [Timelog] => Array ( [project_id] => 1234 ) [0] => Array ( [TotalHours] => 25.5 ) ) )

If we want to group TotalHours into our Timelog array we should specify a virtual eld for our aggregate column. We can add this new virtual eld on the y rather than permanently declaring it in the model. We will provide a default value of 0 in case another query attempts to use this virtual eld. If that were to occur, 0 would be returned in the TotalHours column:
$this->Timelog->virtualFields[TotalHours] = 0;

In addition to adding the virtual eld we also need to alias our column using the form of MyModel__MyField like this:

$this->Timelog->query("SELECT project_id, SUM(id) as Timelog__TotalHours FROM timelogs AS T

Running the query again after specifying the virtual eld should result in a cleaner grouping of values:
Array ( [0] => Array ( [Timelog] => Array ( [project_id] => 1234 [TotalHours] => 25.5 ) ) )

Limitations of virtualFields The implementation of virtualFields has a few limitations. First you cannot use virtualFields on associated models for conditions, order, or elds arrays. Doing so will generally result in an SQL error as the elds are not replaced by the ORM. This is because it difcult to estimate the depth at which an associated model might be found.

More on models

191

CakePHP Cookbook Documentation, Release 2.x

A common workaround for this implementation issue is to copy virtualFields from one model to another at runtime when you need to access them:
$this->virtualFields[name] = $this->Author->virtualFields[name];

or:
$this->virtualFields += $this->Author->virtualFields;

Transactions
To perform a transaction, a models tables must be of a type that supports transactions. All transaction methods must be performed on a models DataSource object. To get a models DataSource from within the model, use:
$dataSource = $this->getDataSource();

You can then use the data source to start, commit, or roll back transactions.
$dataSource->begin(); // Perform some tasks if (/*alls well*/) { $dataSource->commit(); } else { $dataSource->rollback(); }

Nested Transactions It is possible to start a transaction several times using the Datasource::begin() method. The transaction will nish only when the number of commit and rollback match with begins.
$dataSource->begin(); // Perform some tasks $dataSource->begin(); // More few tasks if (/*latest task ok*/) { $dataSource->commit(); } else { $dataSource->rollback(); // Change something in main task } $dataSource->commit();

This will perform the real nested transaction if your database supports it and it is enabled in the datasource. The methods will always return true when in transaction mode and the nested is not supported or disabled. If you want to use multiple begins but not use the nested transaction from database, disable it using $dataSource->useNestedTransactions = false;. It will use only the global transaction. 192 Chapter 6. Models

CakePHP Cookbook Documentation, Release 2.x

The real nested transaction is disabled by $dataSource->useNestedTransactions = true;.

default.

Enable

it

using

More on models

193

CakePHP Cookbook Documentation, Release 2.x

194

Chapter 6. Models

CHAPTER 7

Core Libraries

CakePHP comes with a plethora of built-in functions and classes. These classes and functions try to cover some of the most common features required in web applications.

General Purpose
General purpose libraries are available and reused in many places across CakePHP.

General Purpose
Global Constants and Functions While most of your day-to-day work in CakePHP will be utilizing core classes and methods, CakePHP features a number of global convenience functions that may come in handy. Many of these functions are for use with CakePHP classes (loading model or component classes), but many others make working with arrays or strings a little easier. Well also cover some of the constants available in CakePHP applications. Using these constants will help make upgrades more smooth, but are also convenient ways to point to certain les or directories in your CakePHP application.
Global Functions

Here are CakePHPs globally available functions. Most of them are just convenience wrappers for other CakePHP functionality, such as debugging and translating content. __(string $string_id[, $formatArgs ]) This function handles localization in CakePHP applications. The $string_id identies the ID for a translation. Strings used for translations are treated as format strings for sprintf(). You can supply additional arguments to replace placeholders in your string:

195

CakePHP Cookbook Documentation, Release 2.x

__(You have %s unread messages, $number);

Note: Check out the Internationalization & Localization section for more information. __c(string $msg, integer $category, mixed $args = null) Note that the category must be specied with a numeric value, instead of the constant name. The values are: 0 - LC_ALL 1 - LC_COLLATE 2 - LC_CTYPE 3 - LC_MONETARY 4 - LC_NUMERIC 5 - LC_TIME 6 - LC_MESSAGES __d(string $domain, string $msg, mixed $args = null) Allows you to override the current domain for a single message lookup. Useful when internationalizing a plugin: plugin); echo __d(PluginName, This is my

__dc(string $domain, string $msg, integer $category, mixed $args = null) Allows you to override the current domain for a single message lookup. It also allows you to specify a category. Note that the category must be specied with a numeric value, instead of the constant name. The values are: 0 - LC_ALL 1 - LC_COLLATE 2 - LC_CTYPE 3 - LC_MONETARY 4 - LC_NUMERIC 5 - LC_TIME 6 - LC_MESSAGES __dcn(string $domain, string $singular, string $plural, integer $count, integer $category, mixed $args = null) Allows you to override the current domain for a single plural message lookup. It also allows you to specify a category. Returns correct plural form of message identied by $singular and $plural for count $count from domain $domain.

196

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

Note that the category must be specied with a numeric value, instead of the constant name. The values are: 0 - LC_ALL 1 - LC_COLLATE 2 - LC_CTYPE 3 - LC_MONETARY 4 - LC_NUMERIC 5 - LC_TIME 6 - LC_MESSAGES __dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null) Allows you to override the current domain for a single plural message lookup. Returns correct plural form of message identied by $singular and $plural for count $count from domain $domain. __n(string $singular, string $plural, integer $count, mixed $args = null) Returns correct plural form of message identied by $singular and $plural for count $count. Some languages have more than one form for plural messages dependent on the count. am(array $one, $two, $three...) Merges all the arrays passed as parameters and returns the merged array. config() Can be used to load les from your application config-folder via include_once. Function checks for existence before include and returns boolean. Takes an optional number of arguments. Example: config(some_file, myconfig); convertSlash(string $string) Converts forward slashes to underscores and removes the rst and last underscores in a string. Returns the converted string. debug(mixed $var, boolean $showHtml = null, $showFrom = true) If the applications DEBUG level is non-zero, $var is printed out. If $showHTML is true or left as null, the data is rendered to be browser-friendly. If $showFrom is not set to false, the debug output will start with the line from which it was called Also see Debugging env(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, its a good idea to always use env() instead of $_SERVER or getenv() (especially if you plan to distribute the code), since its a full emulation wrapper. fileExistsInPath(string $le) Checks to make sure that the supplied le is within the current PHP include_path. Returns a boolean result. h(string $text, boolean $double = true, string $charset = null) Convenience wrapper for htmlspecialchars(). General Purpose 197

CakePHP Cookbook Documentation, Release 2.x

LogError(string $message) Shortcut to Log::write(). pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null) Splits a dot syntax plugin name into its plugin and classname. If $name does not have a dot, then index 0 will be null. Commonly used like list($plugin, $name) = pluginSplit(Users.User); pr(mixed $var) Convenience wrapper for print_r(), with the addition of wrapping <pre> tags around the output. sortByKey(array &$array, string $sortby, SORT_NUMERIC) Sorts given $array by key $sortby. string $order = asc, integer $type =

stripslashes_deep(array $value) Recursively strips slashes from the supplied $value. Returns the modied array.
Core Denition Constants

Most of the following constants refer to paths in your application. constant APP Path to the applications directory. constant APP_DIR Equals app or the name of your application directory. constant APPLIBS Path to the applications Lib directory. constant CACHE Path to the cache les directory. It can be shared between hosts in a multi-server setup. constant CAKE Path to the cake directory. constant CAKE_CORE_INCLUDE_PATH Path to the root lib directory. constant CORE_PATH Path to the root directory with ending directory slash. constant CSS Path to the public CSS directory. constant CSS_URL Web path to the CSS les directory. constant DS Short for PHPs DIRECTORY_SEPARATOR, which is / on Linux and \ on windows. constant FULL_BASE_URL Full url prex. Such as https://example.com 198 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

constant IMAGES Path to the public images directory. constant IMAGES_URL Web path to the public images directory. constant JS Path to the public JavaScript directory. constant JS_URL Web path to the js les directory. constant LOGS Path to the logs directory. constant ROOT Path to the root directory. constant TESTS Path to the tests directory. constant TMP Path to the temporary les directory. constant VENDORS Path to the vendors directory. constant WEBROOT_DIR Equals webroot or the name of your webroot directory. constant WWW_ROOT Full path to the webroot.
Timing Denition Constants

constant TIME_START Unix timestamp in microseconds as a oat from when the application started. constant SECOND Equals 1 constant MINUTE Equals 60 constant HOUR Equals 3600 constant DAY Equals 86400 constant WEEK Equals 604800 constant MONTH Equals 2592000 General Purpose 199

CakePHP Cookbook Documentation, Release 2.x

constant YEAR Equals 31536000 App Class class App The app class is responsible for path management, class location and class loading. Make sure you follow the File and Classname Conventions.
Packages

CakePHP is organized around the idea of packages, each class belongs to a package or folder where other classes reside. You can congure each package location in your application using App::build(APackage/SubPackage, $paths) to inform the framework where should each class be loaded. Almost every class in the CakePHP framework can be swapped with your own compatible implementation. If you wish to use you own class instead of the classes the framework provides, just add the class to your libs folder emulating the directory location of where CakePHP expects to nd it. For instance if youd like to use your own HttpSocket class, put it under:
app/Lib/Network/Http/HttpSocket.php

Once youve done this App will load your override le instead of the le inside CakePHP.
Loading classes

static App::uses(string $class, string $package) Return type void Classes are lazily loaded in CakePHP, however before the autoloader can nd your classes you need to tell App, where it can nd the les. By telling App which package a class can be found in, it can properly locate the le and load it the rst time a class is used. Some examples for common types of classes are: Controller App::uses(PostsController, Controller); Component App::uses(AuthComponent, Controller/Component); Model App::uses(MyModel, Model); Behaviors App::uses(TreeBehavior, Model/Behavior); Views App::uses(ThemeView, View); Helpers App::uses(HtmlHelper, View/Helper); Libs App::uses(PaymentProcessor, Lib); Vendors App::uses(Textile, Vendor);

200

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

Utility App::uses(String, Utility); So basically the second param should simply match the folder path of the class le in core or app. Note: Loading vendors usually means you are loading packages that do not follow conventions. For most vendor packages using App::import() is recommended.

Loading les from plugins Loading classes in plugins works much the same as loading app and core classes except you must specify the plugin you are loading from:
// Load the class Comment in app/Plugin/PluginName/Model/Comment.php App::uses(Comment, PluginName.Model);

// Load the class CommentComponent in app/Plugin/PluginName/Controller/Component/CommentCom App::uses(CommentComponent, PluginName.Controller/Component);

Finding paths to packages using App::path()

static App::path(string $package, string $plugin = null) Return type array Used to read information stored path:
// return the model paths in your application App::path(Model);

This can be done for all packages that are apart of your application. You can also fetch paths for a plugin:
// return the component paths in DebugKit App::path(Component, DebugKit);

static App::paths() Return type array Get all the currently loaded paths from App. Useful for inspecting or storing all paths App knows about. For a paths to a specic package use App::path() static App::core(string $package) Return type array Used for nding the path to a package inside CakePHP:
// Get the path to Cache engines. App::core(Cache/Engine);

static App::location(string $className) Return type string

General Purpose

201

CakePHP Cookbook Documentation, Release 2.x

Returns the package name where a class was dened to be located at.
Adding paths for App to nd packages in

static App::build(array $paths = array(), mixed $mode = App::PREPEND) Return type void Sets up each package location on the le system. You can congure multiple search paths for each package, those will be used to look for les one folder at a time in the specied order. All paths should be terminated with a directory separator. Adding additional controller paths for example would alter where CakePHP looks for controllers. This allows you to split your application up across the lesystem. Usage:
//will setup a new search path for the Model package App::build(array(Model => array(/a/full/path/to/models/))); //will setup the path as the only valid path for searching models App::build(array(Model => array(/path/to/models/)), App::RESET); //will setup multiple search paths for helpers App::build(array(View/Helper => array(/path/to/helpers/, /another/path/)));

If reset is set to true, all loaded plugins will be forgotten and they will be needed to be loaded again. Examples:
App::build(array(controllers => array(/full/path/to/controllers))); //becomes App::build(array(Controller => array(/full/path/to/Controller))); App::build(array(helpers => array(/full/path/to/views/helpers))); //becomes App::build(array(View/Helper => array(/full/path/to/View/Helper)));

Changed in version 2.0: App::build() will not merge app paths with core paths anymore. Add new packages to an application App::build() can be used to add new package locations. This is useful when you want to add new top level packages or, sub-packages to your application:
App::build(array( Service => array(%s . Service . DS) ), App::REGISTER);

The %s in newly registered packages will be replaced with the APP path. You must include a trailing / in registered packages. Once packages are registered, you can use App::build() to append/prepend/reset paths like any other package. Changed in version 2.1: Registering packages was added in 2.1

202

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

Finding which objects CakePHP knows about

static App::objects(string $type, mixed $path = null, boolean $cache = true) Return type mixed Returns an array of objects of the given type or false if incorrect. You can nd out which objects App knows about using App::objects(Controller) for example to nd which application controllers App knows about. Example usage:
//returns array(DebugKit, Blog, User); App::objects(plugin); //returns array(PagesController, BlogController); App::objects(Controller);

You can also search only within a plugins objects by using the plugin dot syntax.:
// returns array(MyPluginPost, MyPluginComment); App::objects(MyPlugin.Model);

Changed in version 2.0. 1.Returns array() instead of false for empty results or invalid types 2.Does not return core objects anymore, App::objects(core) will return array(). 3.Returns the complete class name
Locating plugins

static App::pluginPath(string $plugin) Return type string Plugins can be located with App as well. Using App::pluginPath(DebugKit); for example, will give you the full path to the DebugKit plugin:
$path = App::pluginPath(DebugKit);

Locating themes

static App::themePath(string $theme) Return type string Themes can be found App::themePath(purple);, would give the full path to the purple theme.

General Purpose

203

CakePHP Cookbook Documentation, Release 2.x

Including les with App::import()

static App::import(mixed $type = null, string $name = null, mixed $parent = true, array $search = array(), string $le = null, boolean $return = false) Return type boolean At rst glance App::import seems complex, however in most use cases only 2 arguments are required. Note: This method is equivalent to requireing the le. It is important to realize that the class subsequently needs to be initialized.
// The same as require(Controller/UsersController.php); App::import(Controller, Users); // We need to load the class $Users = new UsersController(); // If we want the model associations, components, etc to be loaded $Users->constructClasses();

All classes that were loaded in the past using App::import(Core, $class) will need to be loaded using App::uses() referring to the correct package. This change has provided large performance gains to the framework. Changed in version 2.0. The method no longer looks for classes recursively, it strictly uses the values for the paths dened in App::build() It will not be able to load App::import(Component, Component) use App::uses(Component, Controller);. Using App::import(Lib, CoreClass); to load core classes is no longer possible. Importing a non-existent le, supplying a wrong type or package name, or null values for $name and $file parameters will result in a false return value. App::import(Core, CoreClass) is no longer supported, use App::uses() instead and let the class autoloading do the rest. Loading Vendor les does not look recursively in the vendors folder, it will also not convert the le to underscored anymore as it did in the past.
Overriding classes in CakePHP

You can override almost every class in the framework, exceptions are the App and Configure classes. Whenever you like to perform such overriding, just add your class to your app/Lib folder mimicking the internal structure of the framework. Some examples to follow To override the Dispatcher class, create app/Lib/Routing/Dispatcher.php To override the CakeRoute class, create app/Lib/Routing/Route/CakeRoute.php 204 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

To override the Model class, create app/Lib/Model/Model.php When you load the replaced les, the app/Lib les will be loaded instead of the built-in core classes.
Loading Vendor Files

You can use App::uses() to load classes in vendors directories. It follows the same conventions as loading other les:
// Load the class Geshi in app/Vendor/Geshi.php App::uses(Geshi, Vendor);

To load classes in subdirectories, youll need to add those paths with App::build():
// Load the class ClassInSomePackage in app/Vendor/SomePackage/ClassInSomePackage.php App::build(array(Vendor => array(APP . Vendor . DS . SomePackage))); App::uses(ClassInSomePackage, Vendor);

Your vendor les may not follow conventions, have a class that differs from the le name or does not contain classes. You can load those les using App::import(). The following examples illustrate how to load vendor les from a number of path structures. These vendor les could be located in any of the vendor folders. To load app/Vendor/geshi.php:
App::import(Vendor, geshi);

Note: The geshi le must be a lower-case le name as Cake will not nd it otherwise. To load app/Vendor/ickr/ickr.php:
App::import(Vendor, flickr/flickr);

To load app/Vendor/some.name.php:
App::import(Vendor, SomeName, array(file => some.name.php));

To load app/Vendor/services/well.named.php:
App::import(Vendor, WellNamed, array(file => services . DS . well.named.php));

It wouldnt make a difference if your vendor les are inside your /vendors directory. Cake will automatically nd it. To load vendors/vendorName/libFile.php:

App::import(Vendor, aUniqueIdentifier, array(file => vendorName . DS . libFile.php

App Init/Load/Shutdown Methods

static App::init() General Purpose 205

CakePHP Cookbook Documentation, Release 2.x

Return type void Initializes the cache for App, registers a shutdown function. static App::load(string $className) Return type boolean Method to handle the automatic class loading. It will look for each class package dened using App::uses() and with this information it will resolve the package name to a full path to load the class from. File name for each class should follow the class name. For instance, if a class is name MyCustomClass the le name should be MyCustomClass.php static App::shutdown() Return type void Object destructor. Writes cache le if changes have been made to the $_map. Events System New in version 2.1. Creating maintainable applications is both a science and an art. It is well-known that a key for having good quality code is making your objects loosely coupled and strongly cohesive at the same time. Cohesion means that all methods and properties for a class are strongly related to the class itself and it is not trying to do the job other objects should be doing, while loosely coupling is the measure of how little a class is wired to external objects, and how much that class is depending on them. While most of the CakePHP structure and default libraries will help you achieve this goal, there are certain cases where you need to cleanly communicate with other parts in the system without having to hard code those dependencies, thus losing cohesion and increasing class coupling. A very successful design pattern in software engineering is the Observer pattern, where objects can generate events and notify possibly anonymous listeners about changes in the internal state. Listeners in the observer pattern can subscribe to such events and choose to act upon them, modify the subject state or simply log stuff. If you have used javascript in the past, the chances are that you are already familiar with event driven programming. CakePHP emulates several aspects of how events are triggered and managed in popular javascript frameworks such as jQuery, while remaining loyal to its object oriented design. In this implementation, an event object is carried across all listeners holding the information and the ability to stop the event propagation at any point. Listeners can register themselves or can delegate this task to other objects and have the chance to alter the state and the event itself for the rest of the callbacks.
Interacting with the event managers

Lets suppose you are building a Cart plugin, but you dont really want to mess with shipping logic, emailing the user or decrementing the item from the stock, it is your wish to handle those things separately in another plugin or in app code. Typically, when not directly using the observer pattern you would do this by attaching behaviors on the y to your models, and perhaps some components to the controllers. Doing so represents a challenge most of the time, since you would have to come up with the code for externally loading those behaviors or attaching hooks to your plugin controllers. Prior to CakePHP 2.1 206 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

some developers chose to implement generic event systems to solve this problem, and some of those system were offered as plugins. Now, you can benet from a standard general purpose event system that will let you cleanly separate the concerns of your plugins and application code with the built in events manager. Dispatching events So back to our example, we would have an Order model that will manage the buying logic, and probably a place method to save the order details and do any other logic:
// Cart/Model/Order.php class Order extends AppModel { public function place($order) { if ($this->save($order)) { $this->Cart->remove($order); $this->sendNotificationEmail(); $this->decrementFromStock(); $this->updateUserStatistics(); // ... return true; } return false; } }

Well, that does not look right at all. A plugin should not make any assumption about sending emails, and may not even have the inventory data to decrement the item from it, and denitely tracking usage statistics is not the best place to do it. So we need another solution, lets rewrite that using the event manager:
// Cart/Model/Order.php App::uses(CakeEvent, Event); class Order extends AppModel {

public function place($order) { if ($this->save($order)) { $this->Cart->remove($order); $this->getEventManager()->dispatch(new CakeEvent(Model.Order.afterPlace, $thi order => $order ))); return true; } return false; } }

That looks a lot cleaner, at gives us the opportunity to introduce the event classes and methods. The rst thing you may notice is the call to getEventManager() this is a method that is available by default in all Models, Controller and Views. This method will not return the same manager instance across models, and it is not shared between controllers and models, but they are between controllers and views, nevertheless. We will review later how to overcome this implementation detail. The getEventManager method returns an instance of CakeEventManager, and to dispatch events you use CakeEventManager::dispatch() which receives an instance of the CakeEvent class. Lets dissect now the process of dispatching an event: General Purpose 207

CakePHP Cookbook Documentation, Release 2.x

new CakeEvent(Model.Order.afterPlace, $this, array( order => $order ));

CakeEvent receives 3 arguments in its constructor. The rst one is the event name, you should try to keep this name as unique as possible, while making it readable. We suggest a convention as follows: Layer.eventName for general events happening at a layer level (e.g. Controller.startup, View.beforeRender) and Layer.Class.eventName for events happening in specic classes on a layer, for example Model.User.afterRegister or Controller.Courses.invalidAccess. The second argument is the subject, meaning the object associated to the event, usually when it is the same class triggering events about itself, using $this will be the most common case. Although a Component could trigger controller events too. The subject class is important because listeners will get immediate access to the object properties and have the chance to inspect or change them on the y. Finally, the third argument is the events params. This can be any data you consider useful to pass around so listeners can act upon it. While this can be an argument of any type, we recommend passing an associative array, to make inspection easier. CakeEventManager::dispatch() method accepts the event object as argument and noties all listener and callbacks passing this object along. So the listeners will handle all the extra logic around the afterPlace event, you can log the time, send emails, update user statistics possibly in separate objects and even delegating it to ofine tasks if you have the need. Registering callbacks How do we register callbacks or observers to our new afterPlace event? This is subject to a wide variety of different implementations, but they all have to call the CakeEventManager::attach() method to register new actors. For simplicitys sake, lets imagine we know in the plugin what the callbacks are available in the controller, and say this controller is responsible for attaching them. The possible code would look like this:

// Listeners configured somewhere else, maybe a config file: Configure::write(Order.afterPlace, array( email-sending => EmailSender::sendBuyEmail, inventory => array($this->InventoryManager, decrement), logger => function($event) { // Anonymous function are only available in PHP 5.3+ CakeLog::write(info, A new order was placed with id: . $event->subject()->id); } )); // Cart/Controller/OrdersController.php class OrdersController extends AppController { public function finish() { foreach (Configure::read(Order.afterPlace) as $l) { $this->Order->getEventManager()->attach($l, Model.Order.afterPlace); } if ($this->Order->place($this->Cart->items())) { // ... } }

208

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

This may not be the cleanest way to do it, so you can come up with your own ways for attaching listeners to an objects event manager. This simple way of dening them using the Congure class is intended for didactic purposes only. This little example allows us to showcase what type of callbacks can be attached to the manager. As you may already have gured out, the attach method takes any valid PHP callback type, this is a string representing a static function call, an array having a class instance and a method, an anonymous function if you use PHP 5.3, etc. Attached callbacks will all receive the event object as rst argument CakeEventManager::attach() Accepts three arguments. The leftmost one is the callback itself, anything that PHP can treat as a callable function. The second argument is the event name, and the callback will only get red if the CakeEvent object dispatched has a matching name. The last argument is an array of options to congure the callback priority, and the preference of arguments to be passed. Registering listeners Listeners are an alternative, and often cleaner way of registering callbacks for an event. This is done by implementing the CakeEventListener interface in any class you wish to register some callbacks. Classes implementing it need to provide the implementedEvents() method and return an associative array with all event names that the class will handle. To keep up with our previous example, lets imagine we have a UserStatistic class responsible for calculating useful information and compiling into the global site statistics. It would be natural to pass an instance of this class as a callback, instead of implementing a custom static function or converting any other workaround to trigger methods in this class. A listener is created as follows:
App::uses(CakeEventListener, Event); class UserStatistic implements CakeEventListener { public function implementedEvents() { return array( Model.Order.afterPlace => updateBuyStatistic, ); } public function updateBuyStatistic($event) { // Code to update statistics } } // Attach the UserStatistic object to the Orders event manager $statistics = new UserStatistic(); $this->Order->getEventManager()->attach($statistics);

As you can see in the above code, the attach function can handle instances of the CakeEventListener interface. Internally, the event manager will read the array returned by implementedEvents method and wire the callbacks accordingly. Establishing priorities In some cases youd want to run a callback and make sure it gets executed before, or after all the other callbacks have been run. For instance, think again about our user statistics example. It would make sense to run this method only when we can make sure the event was not cancelled, there

General Purpose

209

CakePHP Cookbook Documentation, Release 2.x

were no errors and the other callbacks did not change the state of the order itself. For those cases you use priorities. Priorities are handled using a number associated to the callback itself. The higher the number, the later the method will be red. Default priority for all callbacks and listener methods are set to 10. If you need your method to be run before, then using any value below this default will help you do it, even setting the priority to 1 or a negative value should work. On the other hand if you desire to run the callback after the others, using a number above 10 will do. If two callbacks happen to be allocated in the same priority queue, they will be executed with a FIFO policy, the rst listener method to be attached is called rst and so on. You set priorities using the attach method for callbacks, and declaring it in the implementedEvents function for event listeners:

// Setting priority for a callback $callback = array($this, doSomething); $this->getEventManager()->attach($callback, Model.Order.afterPlace, array(priority => 2

// Setting priority for a listener class UserStatistic implements CakeEventListener { public function implementedEvents() { return array( Model.Order.afterPlace => array(callable => updateBuyStatistic, priority ); } }

As you see, the main difference for CakeEventListener objects is that you need to use an array for specifying the callable method and the priority preference. The callable key is an special array entry that the manager will read to know what function in the class it should be calling. Getting event data as function params Some developers might prefer having the event data passed as function parameters instead of receiving the event object. While this is an odd preference and using the event object is a lot more powerful, this was needed to provide backwards compatibility with the previous event system and to offer seasoned developers an alternative to what they were used to. In order to toggle this option you have to add the passParams option to the third argument of the attach method, or declare it in the implementedEvents returned array similar to what you do with priorities:

// Setting priority for a callback $callback = array($this, doSomething); $this->getEventManager()->attach($callback, Model.Order.afterPlace, array(passParams =>

// Setting priority for a listener class UserStatistic implements CakeEventListener { public function implementedEvents() { return array( Model.Order.afterPlace => array(callable => updateBuyStatistic, passPara ); } public function updateBuyStatistic($orderData) { // ...

210

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

} }

In the above code the doSomething function and updateBuyStatistic method will receive $orderData instead of the $event object. This is so, because in our previous example we trigger the Model.Order.afterPlace event with some data:
$this->getEventManager()->dispatch(new CakeEvent(Model.Order.afterPlace, $this, array( order => $order )));

Note: The params can only be passed as function arguments if the event data is an array. Any other data type cannot be converted to function parameters, thus not using this option is often the most adequate choice.

Stopping events There are circumstances where you will need to stop events so the operation that started it is cancelled. You see examples of this in the model callbacks (e.g. beforeSave) in which it is possible to stop the saving operation if the code detects it cannot proceed any further. In order to stop events you can either return false in your callbacks or call the stopPropagation method on the event object:
public function doSomething($event) { // ... return false; // stops the event } public function updateBuyStatistic($event) { // ... $event->stopPropagation(); }

Stopping an event can have two different effects. The rst one can always be expected: any callback after the event was stopped will not be called. The second consequence is optional and it depends on the code triggering the event, for instance, in our afterPlace example it would not make any sense to cancel the operation since the data was already saved and the cart emptied. Nevertheless, if we had a beforePlace stopping the event would have a valid meaning. To check if an event was stopped, you call the isStopped() method in the event object:
public function place($order) { $event = new CakeEvent(Model.Order.beforePlace, $this, array(order => $order)); $this->getEventManager()->dispatch($event); if ($event->isStopped()) { return false; } if ($this->Order->save($order)) { // ... } // ... }

General Purpose

211

CakePHP Cookbook Documentation, Release 2.x

In the previous example the order would not get saved if the event is stopped during the beforePlace process. Getting event results Every time a callback returns a value, it gets stored in the $result property of the event object. This is useful in some cases where letting callbacks modify the main process params enhances the ability of altering the execution aspect of any process. Lets take again our beforePlace example and let callbacks modify the $order data. Event results can be altered either using the event object result property directly or returning the value in the callback itself:
// A listener callback public function doSomething($event) { // ... $alteredData = $event->data[order] + $moreData; return $alteredData; } // Another listener callback public function doSomethingElse($event) { // ... $event->result[order] = $alteredData; } // Using the event result public function place($order) { $event = new CakeEvent(Model.Order.beforePlace, $this, array(order => $order)); $this->getEventManager()->dispatch($event); if (!empty($event->result[order])) { $order = $event->result[order]; } if ($this->Order->save($order)) { // ... } // ... }

As you also may have noticed it is possible to alter any event object property and be sure that this new data will get passed to the next callback. In most of the cases, providing objects as event data or result and directly altering the object is the best solution as the reference is kept the same and modications are shared across all callback calls. Removing callbacks and listeners If for any reason you want to remove any callback from the event manager just call the CakeEventManager::detach() method using as arguments the rst two params you used for attaching it:
// Attaching a function $this->getEventManager()->attach(array($this, doSomething), My.event); // Detaching the function $this->getEventManager()->detach(array($this, doSomething), My.event);

212

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

// Attaching an anonymous function (PHP 5.3+ only); $myFunction = function($event) { ... }; $this->getEventManager()->attach($myFunction, My.event); // Detaching the anonymous function $this->getEventManager()->detach($myFunction, My.event); // Attaching a CakeEventListener $listener = new MyCakeEventLister(); $this->getEventManager()->attach($listener); // Detaching a single event key from a listener $this->getEventManager()->detach($listener, My.event); // Detaching all callbacks implemented by a listener $this->getEventManager()->detach($listener);

The global event manager

As previously noted, it might get hard to attach observers to a particular event manager in an object. There are certain cases where having the ability to attach callbacks for an event is needed without having access to the object instance that will trigger it. Also, to prevent people from implementing each of them a different mechanism for loading callbacks into managers based on conguration, CakePHP provides the concept of the global event manager. The global manager is a singleton instance of a CakeEventManager class that receives every event that any event manager in the app dispatches. This is both powerful and exible, but if you use it you need to take more precautions when dealing with events. To set the concept right once again, and using our beforePlace example lets recall that we were using the local event manager that is returned by the getEventManager function. Internally this local event manager dispatches the event into the global one before it triggers the internal attached callbacks. The priority for each manager is independent, the global callbacks will re in their own priority queue and then the local callbacks will get called in the respective priority order. Accessing the global event manager is as easy as calling a static function, the following example will attach a global event to the beforePlace event:
// In any configuration file or piece of code that executes before the event App::uses(CakeEventManager, Event); CakeEventManager::instance()->attach($aCallback, Model.Order.beforePlace);

As you can see, we just change how we get access to an event manager instance, and we can apply the same concepts we learned before about triggering, attaching, detaching, stopping events, etc. One important thing you should consider is that there are events that will be triggered having the same name but different subjects, so checking it in the event object is usually required in any function that gets attached globally in order to prevent some bugs. Remember that extreme exibility implies extreme complexity. Consider this callback that wants to listen for all Model beforeFinds but in reality, it cannot do its logic if the model is the Cart:

General Purpose

213

CakePHP Cookbook Documentation, Release 2.x

App::uses(CakeEventManager, Event); CakeEventManager::instance()->attach(myCallback, Model.beforeFind); function myCallback($event) { if ($event->subject() instanceof Cart) { return; } return array(conditions => ...); }

Conclusion

Events are a great way of separating concerns in your application and make classes both cohesive and decoupled from each other, nevertheless using events is not the solution to all problems. Most applications actually wont need this feature at all, we recommend looking into other options when it comes to implementing callbacks such as using behaviors, components or helpers. Keep in mind that with great power comes great responsibility, decoupling your classes this way also means that you need to perform more and better integration testing on your code. Abusing this tool wont make your apps have a better architecture, quite the opposite, it will make the code harder to read. Whereas in contrast, if you use it wisely, only for the stuff your really need, it will make you code easier to work with, test and integrate.
Additional Reading

Collections Components, Helpers, Behaviors and Tasks all share a similar structure and set of behaviors. For 2.0, they were given a unied API for interacting with collections of similar objects. The collection objects in CakePHP, give you a uniform way to interact with several different kinds of objects in your application. While the examples below, will use Components, the same behavior can be expected for Helpers, Behaviors, and Tasks in addition to Components. Loading and unloading objects Loading objects on every kind of collection can be done using the load() method:
$this->Prg = $this->Components->load(Prg); $this->Prg->process();

When loading a component, if the component is not currently loaded into the collection, a new instance will be created. If the component is already loaded, another instance will not be created. When loading components, you can also provide additional conguration for them:
$this->Cookie = $this->Components->load(Cookie, array(name => sweet));

Any keys & values provided will be passed to the Components constructor. The one exception to this rule is className. ClassName is a special key that is used to alias objects in a collection. This allows you

214

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

to have component names that do not reect the classnames, which can be helpful when extending core components:
$this->Auth = $this->Components->load(Auth, array(className => MyCustomAuth)); $this->Auth->user(); // Actually using MyCustomAuth::user();

The inverse of loading an object, is unloading it. Unloaded objects are removed from memory, and will not have additional callbacks triggered on them:
$this->Components->unload(Cookie); $this->Cookie->read(); // Fatal error.

Triggering callbacks Callbacks are supported by collection objects. When a collection has a callback triggered, that method will be called on all enabled objects in the collection. You can pass parameters to the callback loop as well:
$this->Behaviors->trigger(afterFind, array($this, $results, $primary));

In the above $viewFile would be passed as the rst argument to every helpers beforeRender method. There are several options that can be used to control how callbacks are red: breakOn Set to the value or values you want the callback propagation to stop on. Can either be a scalar value, or an array of values to break on. Defaults to false. break Set to true to enabled breaking. When a trigger is broken, the last returned value will be returned. If used in combination with collectReturn the collected results will be returned. Defaults to false. collectReturn Set to true to collect the return of each object into an array. This array of return values will be returned from the trigger() call. Defaults to false. triggerDisabled Will trigger the callback on all objects in the collection even the non-enabled objects. Defaults to false. modParams Allows each object the callback gets called on to modify the parameters to the next object. Setting modParams to an integer value will allow you to modify the parameter with that index. Any non-null value will modify the parameter index indicated. Defaults to false. Canceling a callback loop Using the break and breakOn options you can cancel a callback loop midway similar to stopping event propagation in JavaScript:
$this->Behaviors->trigger( beforeFind, array($this, $query), array(break => true, breakOn => false), );

In the above example, if any behavior returns false from its beforeFind method, no further callbacks will be called. In addition, the return of trigger() will be false.

General Purpose

215

CakePHP Cookbook Documentation, Release 2.x

Enabling and disabling objects Once an object is loaded into a collection you may need to disable it. Disabling an object in a collection prevents future callbacks from being red on that object unless the triggerDisabled option is used:
// Disable the HtmlHelper $this->Helpers->disable(Html); // Re-enable the helper later on $this->Helpers->enable(Html);

Disabled objects can still have their normal methods and properties used. The primary difference between an enabled and disabled object is with regards to callbacks. You can interrogate a collection about the enabled objects, or check if a specic object is still enabled using enabled():
// Check whether or not a specific helper is enabled. $this->Helpers->enabled(Html); // $enabled will contain an array of helper currently enabled. $enabled = $this->Helpers->enabled();

Object callback priorities You can prioritize the triggering object callbacks similar to event callbacks. The handling of priority values and order of triggering is the same as explained here. Heres how you can specify priority at declaration time:
class SomeController { public $components = array( Foo, //Foo gets default priority 10 Bar => array(priority => 9) //Bars callbacks are triggered before Foos ); public $helpers = array( Cache => array(priority => 12), //Caches callbacks will be triggered last Asset, Utility //Utility has priority 10 same as Asset and its callbacks are trigger //after Assets ); }

class Post { public $actsAs = array( DoFirst => array(priority => 1), Media ); }

When dynamically loading objects to a collection you can specify the priority like this:
$this->MyComponent = $this->Components->load(MyComponent, array(priority => 9));

You can also change priorities at run time using the ObjectCollection::setPriority() function:

216

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

//For a single object $this->Components->setPriority(Foo, 2); //For multiple objects $this->Behaviors->setPriority(array(Object1 => 8, Object2 => 9));

Behaviors Model behaviors are a way to organize some of the functionality dened in CakePHP models. They allow us to separate and reuse logic that creates a type of behavior, and they do this without requiring inheritance. For example creating tree structures. By providing a simple yet powerful way to enhance models, behaviors allow us to attach functionality to models by dening a simple class variable. Thats how behaviors allow models to get rid of all the extra weight that might not be part of the business contract they are modeling, or that is also needed in different models and can then be extrapolated. As an example, consider a model that gives us access to a database table which stores structural information about a tree. Removing, adding, and migrating nodes in the tree is not as simple as deleting, inserting, and editing rows in the table. Many records may need to be updated as things move around. Rather than creating those tree-manipulation methods on a per model basis (for every model that needs that functionality), we could simply tell our model to use the TreeBehavior, or in more formal terms, we tell our model to behave as a Tree. This is known as attaching a behavior to a model. With just one line of code, our CakePHP model takes on a whole new set of methods that allow it to interact with the underlying structure. CakePHP already includes behaviors for tree structures, translated content, access control list interaction, not to mention the community-contributed behaviors already available in the CakePHP Bakery (http://bakery.cakephp.org). In this section, well cover the basic usage pattern for adding behaviors to models, how to use CakePHPs built-in behaviors, and how to create our own. In essence, Behaviors are Mixins (http://en.wikipedia.org/wiki/Mixin) with callbacks. Using Behaviors Behaviors are attached to models through the $actsAs model class variable:
class Category extends AppModel { public $actsAs = array(Tree); }

This example shows how a Category model could be managed in a tree structure using the TreeBehavior. Once a behavior has been specied, use the methods added by the behavior as if they always existed as part of the original model:
// Set ID $this->Category->id = 42; // Use behavior method, children(): $kids = $this->Category->children();

Some behaviors may require or allow settings to be dened when the behavior is attached to the model. Here, we tell our TreeBehavior the names of the left and right elds in the underlying database table:
class Category extends AppModel { public $actsAs = array(Tree => array( left => left_node,

General Purpose

217

CakePHP Cookbook Documentation, Release 2.x

right => right_node )); }

We can also attach several behaviors to a model. Theres no reason why, for example, our Category model should only behave as a tree, it may also need internationalization support:
class Category extends AppModel { public $actsAs = array( Tree => array( left => left_node, right => right_node ), Translate ); }

So far we have been adding behaviors to models using a model class variable. That means that our behaviors will be attached to our models throughout the models lifetime. However, we may need to detach behaviors from our models at runtime. Lets say that on our previous Category model, which is acting as a Tree and a Translate model, we need for some reason to force it to stop acting as a Translate model:
// Detach a behavior from our model: $this->Category->Behaviors->unload(Translate);

That will make our Category model stop behaving as a Translate model from thereon. We may need, instead, to just disable the Translate behavior from acting upon our normal model operations: our nds, our saves, etc. In fact, we are looking to disable the behavior from acting upon our CakePHP model callbacks. Instead of detaching the behavior, we then tell our model to stop informing of these callbacks to the Translate behavior:
// Stop letting the behavior handle our model callbacks $this->Category->Behaviors->disable(Translate);

We may also need to nd out if our behavior is handling those model callbacks, and if not we then restore its ability to react to them:
// If our behavior is not handling model callbacks if (!$this->Category->Behaviors->enabled(Translate)) { // Tell it to start doing so $this->Category->Behaviors->enable(Translate); }

Just as we could completely detach a behavior from a model at runtime, we can also attach new behaviors. Say that our familiar Category model needs to start behaving as a Christmas model, but only on Christmas day:
// If today is Dec 25 if (date(m/d) == 12/25) { // Our model needs to behave as a Christmas model $this->Category->Behaviors->load(Christmas); }

218

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

We can also use the load method to override behavior settings:


// We will change one setting from our already attached behavior $this->Category->Behaviors->load(Tree, array(left => new_left_node));

Theres also a method to obtain the list of behaviors a model has attached. If we pass the name of a behavior to the method, it will tell us if that behavior is attached to the model, otherwise it will give us the list of attached behaviors:
// If the Translate behavior is not attached if (!$this->Category->Behaviors->loaded(Translate)) { // Get the list of all behaviors the model has attached $behaviors = $this->Category->Behaviors->loaded(); }

Creating Behaviors Behaviors that are attached to Models get their callbacks called automatically. The callbacks are similar to those found in Models: beforeFind, afterFind, beforeSave, afterSave, beforeDelete, afterDelete and onError - see Callback Methods. Your behaviors should be placed in app/Model/Behavior. They are named in CamelCase and postxed by Behavior, ex. NameBehavior.php. Its often helpful to use a core behavior as a template when creating your own. Find them in lib/Cake/Model/Behavior/. Every callback and behavior method takes a reference to the model it is being called from as the rst parameter. Besides implementing the callbacks, you can add settings per behavior and/or model behavior attachment. Information about specifying settings can be found in the chapters about core behaviors and their conguration. A quick example that illustrates how behavior settings can be passed from the model to the behavior:
class Post extends AppModel { public $actsAs = array( YourBehavior => array( option1_key => option1_value ) ); }

Since behaviors are shared across all the model instances that use them, its a good practice to store the settings per alias/model name that is using the behavior. When created behaviors will have their setup() method called:
public function setup(Model $Model, $settings = array()) { if (!isset($this->settings[$Model->alias])) { $this->settings[$Model->alias] = array( option1_key => option1_default_value, option2_key => option2_default_value, option3_key => option3_default_value, ); } $this->settings[$Model->alias] = array_merge(

General Purpose

219

CakePHP Cookbook Documentation, Release 2.x

$this->settings[$Model->alias], (array)$settings); }

Creating behavior methods Behavior methods are automatically available on any model acting as the behavior. For example if you had:
class Duck extends AppModel { public $actsAs = array(Flying); }

You would be able to call FlyingBehavior methods as if they were methods on your Duck model. When creating behavior methods you automatically get passed a reference of the calling model as the rst parameter. All other supplied parameters are shifted one place to the right. For example:
$this->Duck->fly(toronto, montreal);

Although this method takes two parameters, the method signature should look like:
public function fly(Model $Model, $from, $to) { // Do some flying. }

Keep in mind that methods called in a $this->doIt() fashion from inside a behavior method will not get the $model parameter automatically appended. Mapped methods In addition to providing mixin methods, behaviors can also provide pattern matching methods. Behaviors can also dene mapped methods. Mapped methods use pattern matching for method invocation. This allows you to create methods similar to Model::findAllByXXX methods on your behaviors. Mapped methods need to be declared in your behaviors $mapMethods array. The method signature for a mapped method is slightly different than a normal behavior mixin method:
class MyBehavior extends ModelBehavior { public $mapMethods = array(/do(\w+)/ => doSomething); public function doSomething(Model $model, $method, $arg1, $arg2) { debug(func_get_args()); //do something } }

The above will map every doXXX() method call to the behavior. As you can see, the model is still the rst parameter, but the called method name will be the 2nd parameter. This allows you to munge the method name for additional information, much like Model::findAllByXX. If the above behavior was attached to a model the following would happen:
$model->doReleaseTheHounds(homer, lenny); // would output ReleaseTheHounds, homer, lenny

220

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

Behavior callbacks Model Behaviors can dene a number of callbacks that are triggered before/after the model callbacks of the same name. Behavior callbacks allow your behaviors to capture events in attached models and augment the parameters or splice in additional behavior. The available callbacks are: beforeValidate is red before a models beforeValidate beforeFind is red before a models beforeFind afterFind is red before a models afterFind beforeSave is red before a models beforeSave afterSave is red before a models afterSave beforeDelete is red after a models beforeDelete afterDelete is red before a models afterDelete Creating a behavior callback class ModelBehavior Model behavior callbacks are dened as simple methods in your behavior class. Much like regular behavior methods, they receive a $Model parameter as the rst argument. This parameter is the model that the behavior method was invoked on. ModelBehavior::setup(Model $Model, array $settings = array()) Called when a behavior is attached to a model. The settings come from the attached models $actsAs property. ModelBehavior::cleanup(Model $Model) Called when a behavior is detached from a model. The base method removes model settings based on $model->alias. You can override this method and provide custom cleanup functionality. ModelBehavior::beforeFind(Model $Model, array $query) If a behaviors beforeFind returns false it will abort the nd(). Returning an array will augment the query parameters used for the nd operation. ModelBehavior::afterFind(Model $Model, mixed $results, boolean $primary) You can use the afterFind to augment the results of a nd. The return value will be passed on as the results to either the next behavior in the chain or the models afterFind. ModelBehavior::beforeDelete(Model $Model, boolean $cascade = true) You can return false from a behaviors beforeDelete to abort the delete. Return true to allow it continue. ModelBehavior::afterDelete(Model $Model) You can use afterDelete to perform clean up operations related to your behavior. ModelBehavior::beforeSave(Model $Model) You can return false from a behaviors beforeSave to abort the save. Return true to allow it continue. ModelBehavior::afterSave(Model $Model, boolean $created) You can use afterSave to perform clean up operations related to your behavior. $created will be true when a record is created, and false when a record is updated. General Purpose 221

CakePHP Cookbook Documentation, Release 2.x

ModelBehavior::beforeValidate(Model $Model) You can use beforeValidate to modify a models validate array or handle any other pre-validation logic. Returning false from a beforeValidate callback will abort the validation and cause it to fail. Components Components are packages of logic that are shared between controllers. If you nd yourself wanting to copy and paste things between controllers, you might consider wrapping some functionality in a component. CakePHP also comes with a fantastic set of core components you can use to aid in: Security Sessions Access control lists Emails Cookies Authentication Request handling Pagination Each of these core components are detailed in their own chapters. For now, well show you how to create your own components. Creating components keeps controller code clean and allows you to reuse code between projects. Conguring Components Many of the core components require conguration. Some examples of components requiring conguration are Authentication, Cookie and EmailComponent. Conguration for these components, and for components in general, is usually done in the $components array or your controllers beforeFilter() method:
class PostsController extends AppController { public $components = array( Auth => array( authorize => array(controller), loginAction => array(controller => users, action => login) ), Cookie => array(name => CookieMonster) );

Would be an example of conguring a component with the $components array. All core components allow their conguration settings to be set in this way. In addition you can congure components in your controllers beforeFilter() method. This is useful when you need to assign the results of a function to a component property. The above could also be expressed as:
public function beforeFilter() { $this->Auth->authorize = array(controller); $this->Auth->loginAction = array(controller => users, action => login);

222

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

$this->Cookie->name = CookieMonster; }

Its possible, however, that a component requires certain conguration options to be set before the controllers beforeFilter() is run. To this end, some components allow conguration options be set in the $components array:
public $components = array( DebugKit.Toolbar => array(panels => array(history, session)) );

Consult the relevant documentation to determine what conguration options each component provides. One common setting to use is the className option, which allows you to alias components. This feature is useful when you want to replace $this->Auth or another common Component reference with a custom implementation:
// app/Controller/PostsController.php class PostsController extends AppController { public $components = array( Auth => array( className => MyAuth ) ); } // app/Controller/Component/MyAuthComponent.php App::uses(AuthComponent, Controller/Component); class MyAuthComponent extends AuthComponent { // Add your code to override the core AuthComponent }

The above would alias MyAuthComponent to $this->Auth in your controllers. Note: Aliasing a component replaces that instance anywhere that component is used, including inside other Components.

Using Components Once youve included some components in your controller, using them is pretty simple. Each component you use is exposed as a property on your controller. If you had loaded up the SessionComponent and the CookieComponent in your controller, you could access them like so:
class PostsController extends AppController { public $components = array(Session, Cookie); public function delete() { if ($this->Post->delete($this->request->data(Post.id)) { $this->Session->setFlash(Post deleted.); $this->redirect(array(action => index)); } }

General Purpose

223

CakePHP Cookbook Documentation, Release 2.x

Note: Since both Models and Components are added to Controllers as properties they share the same namespace. Be sure to not give a component and a model the same name.

Loading components on the y You might not need all of your components available on every controller action. In situations like this you can load a component at runtime using the Component Collection. From inside a controller you can do the following:
$this->OneTimer = $this->Components->load(OneTimer); $this->OneTimer->getTime();

Component Callbacks Components also offer a few request life-cycle callbacks that allow them to augment the request cycle. See the base Component API for more information on the callbacks components offer. Creating a Component Suppose our online application needs to perform a complex mathematical operation in many different parts of the application. We could create a component to house this shared logic for use in many different controllers. The rst step is to create a new component le and class. Create the le in /app/Controller/Component/MathComponent.php. The basic structure for the component would look something like this:
App::uses(Component, Controller); class MathComponent extends Component { public function doComplexOperation($amount1, $amount2) { return $amount1 + $amount2; } }

Note: All components must extend Component. Failing to do this will trigger an exception.

Including your component in your controllers Once our component is nished, we can use it in the applications controllers by placing the components name (minus the Component part) in the controllers $components array. The controller will automatically be given a new attribute named after the component, through which we can access an instance of it:
/* Make the new component available at $this->Math, as well as the standard $this->Session */ public $components = array(Math, Session);

Components declared in AppController will be merged with those in your other controllers. So there is no need to re-declare the same component twice. When including Components in a Controller you can also declare a set of parameters that will be passed on to the Components constructor. These parameters can then be handled by the Component: 224 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

public $components = array( Math => array( precision => 2, randomGenerator => srand ), Session, Auth );

The above would pass the array containing precision and randomGenerator to MathComponent::__construct() as the second parameter. By convention, any settings that have been passed that are also public properties on your component will have the values set based on the settings. Using other Components in your Component Sometimes one of your components may need to use another component. In this case you can include other components in your component the exact same way you include them in controllers - using the $components var:
// app/Controller/Component/CustomComponent.php App::uses(Component, Controller); class CustomComponent extends Component { // the other component your component uses public $components = array(Existing); public function initialize(Controller $controller) { $this->Existing->foo(); } public function bar() { // ... } } // app/Controller/Component/ExistingComponent.php App::uses(Component, Controller); class ExistingComponent extends Component { public function foo() { // ... } }

Component API class Component The base Component class offers a few methods for lazily loading other Components through ComponentCollection as well as dealing with common handling of settings. It also provides prototypes for all the component callbacks. Component::__construct(ComponentCollection $collection, $settings = array()) Constructor for the base component class. All $settings that are also public properties will have their values changed to the matching value in $settings.

General Purpose

225

CakePHP Cookbook Documentation, Release 2.x

Callbacks Component::initialize(Controller $controller) The initialize method is called before the controllers beforeFilter method. Component::startup(Controller $controller) The startup method is called after the controllers beforeFilter method but before the controller executes the current action handler. Component::beforeRender(Controller $controller) The beforeRender method is called after the controller executes the requested actions logic but before the controllers renders views and layout. Component::shutdown(Controller $controller) The shutdown method is called before output is sent to browser. Component::beforeRedirect(Controller $controller, $url, $status=null, $exit=true) The beforeRedirect method is invoked when the controllers redirect method is called but before any further action. If this method returns false the controller will not continue on to redirect the request. The $url, $status and $exit variables have same meaning as for the controllers method. You can also return a string which will be interpreted as the url to redirect to or return associative array with key url and optionally status and exit. Helpers Helpers are the component-like classes for the presentation layer of your application. They contain presentational logic that is shared between many views, elements, or layouts. This chapter will show you how to create your own helpers, and outline the basic tasks CakePHPs core helpers can help you accomplish. CakePHP features a number of helpers that aid in view creation. They assist in creating well-formed markup (including forms), aid in formatting text, times and numbers, and can even speed up Ajax functionality. For more information on the helpers included in CakePHP, check out Helpers. Using and Conguring Helpers You enable helpers in CakePHP by making a controller aware of them. Each controller has a $helpers property that lists the helpers to be made available in the view. To enable a helper in your view, add the name of the helper to the controllers $helpers array:
class BakeriesController extends AppController { public $helpers = array(Form, Html, Js, Time); }

Adding helpers from plugins uses the plugin syntax used elsewhere in CakePHP:
class BakeriesController extends AppController { public $helpers = array(Blog.Comment); }

You can also add helpers from within an action, so they will only be available to that action and not the other actions in the controller. This saves processing power for the other actions that do not use the helper as well as help keep the controller better organized:
class BakeriesController extends AppController { public function bake { $this->helpers[] = Time;

226

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

} public function mix { // The Time helper is not loaded here and thus not available } }

If you need to enable a helper for all controllers add the name of the helper to the $helpers array in /app/Controller/AppController.php (or create if not present). Remember to include the default Html and Form helpers:
class AppController extends Controller { public $helpers = array(Form, Html, Js, Time); }

You can pass options to helpers. These options can be used to set attribute values or modify behavior of a helper:
class AwesomeHelper extends AppHelper { public function __construct(View $view, $settings = array()) { parent::__construct($view, $settings); debug($settings); } } class AwesomeController extends AppController { public $helpers = array(Awesome => array(option1 => value1)); }

As of 2.3 the options are merged with the Helper::$settings property of the helper. One common setting to use is the className option, which allows you to create aliased helpers in your views. This feature is useful when you want to replace $this->Html or another common Helper reference with a custom implementation:
// app/Controller/PostsController.php class PostsController extends AppController { public $helpers = array( Html => array( className => MyHtml ) ); } // app/View/Helper/MyHtmlHelper.php App::uses(HtmlHelper, View/Helper); class MyHtmlHelper extends HtmlHelper { // Add your code to override the core HtmlHelper }

The above would alias MyHtmlHelper to $this->Html in your views. Note: Aliasing a helper replaces that instance anywhere that helper is used, including inside other Helpers.

General Purpose

227

CakePHP Cookbook Documentation, Release 2.x

Tip: Aliasing the Html or Session Helper while using the core PagesController will not work. It is better to copy lib/Cake/Controller/PagesController.php into your app/Controller/ folder. Using helper settings allows you to declaratively congure your helpers and keep conguration logic out of your controller actions. If you have conguration options that cannot be included as part of a class declaration, you can set those in your controllers beforeRender callback:
class PostsController extends AppController { public function beforeRender() { parent::beforeRender(); $this->helpers[CustomStuff] = $this->_getCustomStuffSettings(); } }

Using Helpers Once youve congured which helpers you want to use in your controller, each helper is exposed as a public property in the view. For example, if you were using the HtmlHelper you would be able to access it by doing the following:
echo $this->Html->css(styles);

The above would call the css method on the HtmlHelper. You can access any loaded helper using $this->{$helperName}. There may come a time where you need to dynamically load a helper from inside a view. You can use the views HelperCollection to do this:
$mediaHelper = $this->Helpers->load(Media, $mediaSettings);

The HelperCollection is a collection and supports the collection API used elsewhere in CakePHP. Callback methods Helpers feature several callbacks that allow you to augment the view rendering process. See the Helper API and the Collections documentation for more information. Creating Helpers If a core helper (or one showcased on github or the Bakery) doesnt t your needs, helpers are easy to create. Lets say we wanted to create a helper that could be used to output a specically crafted CSS-styled link you needed many different places in your application. In order to t your logic in to CakePHPs existing helper structure, youll need to create a new class in /app/View/Helper. Lets call our helper LinkHelper. The actual PHP class le would look something like this:
/* /app/View/Helper/LinkHelper.php */ App::uses(AppHelper, View/Helper); class LinkHelper extends AppHelper { public function makeEdit($title, $url) { // Logic to create specially formatted link goes here... } }

228

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

Note: Helpers must extend either AppHelper or Helper or implement all the callbacks in the Helper API.

Including other Helpers You may wish to use some functionality already existing in another helper. To do so, you can specify helpers you wish to use with a $helpers array, formatted just as you would in a controller:
/* /app/View/Helper/LinkHelper.php (using other helpers) */ App::uses(AppHelper, View/Helper); class LinkHelper extends AppHelper { public $helpers = array(Html); public function makeEdit($title, $url) { // Use the HTML helper to output // formatted data: $link = $this->Html->link($title, $url, array(class => edit)); return <div class="editOuter"> . $link . </div>; } }

Using your Helper Once youve created your helper and placed it in /app/View/Helper/, youll be able to include it in your controllers using the special variable $helpers:
class PostsController extends AppController { public $helpers = array(Link); }

Once your controller has been made aware of this new class, you can use it in your views by accessing an object named after the helper:
<!-- make a link using the new helper --> <?php echo $this->Link->makeEdit(Change this Recipe, /recipes/edit/5); ?>

Creating Functionality for All Helpers All helpers extend a special class, AppHelper (just like models extend AppModel and controllers extend AppController). To create functionality that would be available to all helpers, create /app/View/Helper/AppHelper.php:
App::uses(Helper, View); class AppHelper extends Helper { public function customMethod() { } }

General Purpose

229

CakePHP Cookbook Documentation, Release 2.x

Helper API class Helper The base class for Helpers. It provides a number of utility methods and features for loading other helpers. Helper::webroot($le) Resolve a le name to the webroot of the application. If a theme is active and the le exists in the current themes webroot, the path to the themed le will be returned. Helper::url($url, $full = false) Generates an HTML escaped URL, delegates to Router::url(). Helper::value($options = array(), $eld = null, $key = value) Get the value for a given input name. Helper::domId($options = null, $id = id) Generate a CamelCased id value for the currently selected eld. Overriding this method in your AppHelper will allow you to change how CakePHP generates ID attributes. Callbacks Helper::beforeRenderFile($viewFile) Is called before all view les are rendered. This includes elements, views, parent views, and layouts. Helper::afterRenderFile($viewFile, $content) Is called after all view les are rendered. This includes elements, views, parent views, and layouts. A callback can modify and return $content to change how the rendered content will be displayed in the browser. Helper::beforeRender($viewFile) The beforeRender method is called after the controllers beforeRender method but before the controller renders view and layout. Receives the le being rendered as an argument. Helper::afterRender($viewFile) Is called after the view has been rendered but before layout rendering has started. Helper::beforeLayout($layoutFile) Is called before layout rendering starts. Receives the layout lename as an argument. Helper::afterLayout($layoutFile) Is called after layout rendering is complete. Receives the layout lename as an argument. Core Helpers CacheHelper Used by the core to cache view content. FormHelper Creates HTML forms and form elements that self populate and handle validation problems. HtmlHelper Convenience methods for crafting well-formed markup. Images, links, tables, header tags and more. JsHelper Used to create Javascript compatible with various Javascript libraries. NumberHelper Number and currency formatting. Paginator Model data pagination and sorting. 230 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

RSS Convenience methods for outputting RSS feed XML data. SessionHelper Access for reading session values in views. TextHelper Smart linking, highlighting, word smart truncation. TimeHelper Proximity detection (is this next year?), nice string formatting(Today, 10:30 am) and time zone conversion. Collections Components, Helpers, Behaviors and Tasks all share a similar structure and set of behaviors. For 2.0, they were given a unied API for interacting with collections of similar objects. The collection objects in CakePHP, give you a uniform way to interact with several different kinds of objects in your application. While the examples below, will use Components, the same behavior can be expected for Helpers, Behaviors, and Tasks in addition to Components.
Loading and unloading objects

Loading objects on every kind of collection can be done using the load() method:
$this->Prg = $this->Components->load(Prg); $this->Prg->process();

When loading a component, if the component is not currently loaded into the collection, a new instance will be created. If the component is already loaded, another instance will not be created. When loading components, you can also provide additional conguration for them:
$this->Cookie = $this->Components->load(Cookie, array(name => sweet));

Any keys & values provided will be passed to the Components constructor. The one exception to this rule is className. ClassName is a special key that is used to alias objects in a collection. This allows you to have component names that do not reect the classnames, which can be helpful when extending core components:
$this->Auth = $this->Components->load(Auth, array(className => MyCustomAuth)); $this->Auth->user(); // Actually using MyCustomAuth::user();

The inverse of loading an object, is unloading it. Unloaded objects are removed from memory, and will not have additional callbacks triggered on them:
$this->Components->unload(Cookie); $this->Cookie->read(); // Fatal error.

Triggering callbacks

Callbacks are supported by collection objects. When a collection has a callback triggered, that method will be called on all enabled objects in the collection. You can pass parameters to the callback loop as well:

General Purpose

231

CakePHP Cookbook Documentation, Release 2.x

$this->Behaviors->trigger(afterFind, array($this, $results, $primary));

In the above $viewFile would be passed as the rst argument to every helpers beforeRender method. There are several options that can be used to control how callbacks are red: breakOn Set to the value or values you want the callback propagation to stop on. Can either be a scalar value, or an array of values to break on. Defaults to false. break Set to true to enabled breaking. When a trigger is broken, the last returned value will be returned. If used in combination with collectReturn the collected results will be returned. Defaults to false. collectReturn Set to true to collect the return of each object into an array. This array of return values will be returned from the trigger() call. Defaults to false. triggerDisabled Will trigger the callback on all objects in the collection even the non-enabled objects. Defaults to false. modParams Allows each object the callback gets called on to modify the parameters to the next object. Setting modParams to an integer value will allow you to modify the parameter with that index. Any non-null value will modify the parameter index indicated. Defaults to false. Canceling a callback loop Using the break and breakOn options you can cancel a callback loop midway similar to stopping event propagation in JavaScript:
$this->Behaviors->trigger( beforeFind, array($this, $query), array(break => true, breakOn => false), );

In the above example, if any behavior returns false from its beforeFind method, no further callbacks will be called. In addition, the return of trigger() will be false.
Enabling and disabling objects

Once an object is loaded into a collection you may need to disable it. Disabling an object in a collection prevents future callbacks from being red on that object unless the triggerDisabled option is used:
// Disable the HtmlHelper $this->Helpers->disable(Html); // Re-enable the helper later on $this->Helpers->enable(Html);

Disabled objects can still have their normal methods and properties used. The primary difference between an enabled and disabled object is with regards to callbacks. You can interrogate a collection about the enabled objects, or check if a specic object is still enabled using enabled():
// Check whether or not a specific helper is enabled. $this->Helpers->enabled(Html);

232

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

// $enabled will contain an array of helper currently enabled. $enabled = $this->Helpers->enabled();

Object callback priorities

You can prioritize the triggering object callbacks similar to event callbacks. The handling of priority values and order of triggering is the same as explained here. Heres how you can specify priority at declaration time:
class SomeController { public $components = array( Foo, //Foo gets default priority 10 Bar => array(priority => 9) //Bars callbacks are triggered before Foos ); public $helpers = array( Cache => array(priority => 12), //Caches callbacks will be triggered last Asset, Utility //Utility has priority 10 same as Asset and its callbacks are trigger //after Assets ); }

class Post { public $actsAs = array( DoFirst => array(priority => 1), Media ); }

When dynamically loading objects to a collection you can specify the priority like this:
$this->MyComponent = $this->Components->load(MyComponent, array(priority => 9));

You can also change priorities at run time using the ObjectCollection::setPriority() function:
//For a single object $this->Components->setPriority(Foo, 2); //For multiple objects $this->Behaviors->setPriority(array(Object1 => 8, Object2 => 9));

Behaviors
Behaviors add extra functionality to your models. CakePHP comes with a number of built-in behaviors such as TreeBehavior and ContainableBehavior. To learn about creating and using behaviors, read the section on Behaviors.

Behaviors

233

CakePHP Cookbook Documentation, Release 2.x

Behaviors
ACL class AclBehavior The Acl behavior provides a way to seamlessly integrate a model with your ACL system. It can create both AROs or ACOs transparently. To use the new behavior, you can add it to the $actsAs property of your model. When adding it to the actsAs array you choose to make the related Acl entry an ARO or an ACO. The default is to create ACOs:
class User extends AppModel { public $actsAs = array(Acl => array(type => requester)); }

This would attach the Acl behavior in ARO mode. To join the ACL behavior in ACO mode use:
class Post extends AppModel { public $actsAs = array(Acl => array(type => controlled)); }

For User and Group models it is common to have both ACO and ARO nodes, to achieve this use:
class User extends AppModel { public $actsAs = array(Acl => array(type => both)); }

You can also attach the behavior on the y like so:


$this->Post->Behaviors->load(Acl, array(type => controlled));

Changed in version 2.1: You can now safely attach AclBehavior to AppModel. Aco, Aro and AclNode now extend Model instead of AppModel, which would cause an innite loop. If your application depends on having those models to extend AppModel for some reason, then copy AclNode to your application and have it extend AppModel again.
Using the AclBehavior

Most of the AclBehavior works transparently on your Models afterSave(). However, using it requires that your Model has a parentNode() method dened. This is used by the AclBehavior to determine parent->child relationships. A models parentNode() method must return null or return a parent Model reference:
function parentNode() { return null; }

If you want to set an ACO or ARO node as the parent for your Model, parentNode() must return the alias of the ACO or ARO node:
function parentNode() { return root_node; }

234

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

A more complete example. Using an example User Model, where User belongsTo Group:
function parentNode() { if (!$this->id && empty($this->data)) { return null; } $data = $this->data; if (empty($this->data)) { $data = $this->read(); } if (!$data[User][group_id]) { return null; } else { return array(Group => array(id => $data[User][group_id])); } }

In the above example the return is an array that looks similar to the results of a model nd. It is important to have the id value set or the parentNode relation will fail. The AclBehavior uses this data to construct its tree structure.
node()

The AclBehavior also allows you to retrieve the Acl node associated with a model record. After setting $model->id. You can use $model->node() to retrieve the associated Acl node. You can also retrieve the Acl Node for any row, by passing in a data array:
$this->User->id = 1; $node = $this->User->node(); $user = array(User => array( id => 1 )); $node = $this->User->node($user);

Will both return the same Acl Node information. If you had setup AclBehavior to create both ACO and ARO nodes, you need to specify which node type you want:
$this->User->id = 1; $node = $this->User->node(null, Aro); $user = array(User => array( id => 1 )); $node = $this->User->node($user, Aro);

Containable class ContainableBehavior Behaviors 235

CakePHP Cookbook Documentation, Release 2.x

A new addition to the CakePHP 1.2 core is the ContainableBehavior. This model behavior allows you to lter and limit model nd operations. Using Containable will help you cut down on needless wear and tear on your database, increasing the speed and overall performance of your application. The class will also help you search and lter your data for your users in a clean and consistent way. Containable allows you to streamline and simplify operations on your model bindings. It works by temporarily or permanently altering the associations of your models. It does this by using supplied the containments to generate a series of bindModel and unbindModel calls. To use the new behavior, you can add it to the $actsAs property of your model:
class Post extends AppModel { public $actsAs = array(Containable); }

You can also attach the behavior on the y:


$this->Post->Behaviors->load(Containable);

Using Containable

To see how Containable works, lets look at a few examples. First, well start off with a nd() call on a model named Post. Lets say that Post hasMany Comment, and Post hasAndBelongsToMany Tag. The amount of data fetched in a normal nd() call is rather extensive:
debug($this->Post->find(all)); [0] => Array ( [Post] => Array ( [id] => 1 [title] => First article [content] => aaa [created] => 2008-05-18 00:00:00 ) [Comment] => Array ( [0] => Array ( [id] => 1 [post_id] => 1 [author] => Daniel [email] => [email protected] [website] => http://example.com [comment] => First comment [created] => 2008-05-18 00:00:00 ) [1] => Array ( [id] => 2 [post_id] => 1

236

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

[author] => Sam [email] => [email protected] [website] => http://example.net [comment] => Second comment [created] => 2008-05-18 00:00:00 ) ) [Tag] => Array ( [0] => Array ( [id] => 1 [name] => Awesome ) [1] => Array ( [id] => 2 [name] => Baking ) ) ) [1] => Array ( [Post] => Array (...

For some interfaces in your application, you may not need that much information from the Post model. One thing the ContainableBehavior does is help you cut down on what nd() returns. For example, to get only the post-related information, you can do the following:
$this->Post->contain(); $this->Post->find(all);

You can also invoke Containables magic from inside the nd() call:
$this->Post->find(all, array(contain => false));

Having done that, you end up with something a lot more concise:
[0] => Array ( [Post] => Array ( [id] => 1 [title] => First article [content] => aaa [created] => 2008-05-18 00:00:00 ) ) [1] => Array ( [Post] => Array (

Behaviors

237

CakePHP Cookbook Documentation, Release 2.x

[id] => 2 [title] => Second article [content] => bbb [created] => 2008-05-19 00:00:00 ) )

This sort of help isnt new: in fact, you can do that without the ContainableBehavior doing something like this:
$this->Post->recursive = -1; $this->Post->find(all);

Containable really shines when you have complex associations, and you want to pare down things that sit at the same level. The models $recursive property is helpful if you want to hack off an entire level of recursion, but not when you want to pick and choose what to keep at each level. Lets see how it works by using the contain() method. The contain methods rst argument accepts the name, or an array of names, of the models to keep in the nd operation. If we wanted to fetch all posts and their related tags (without any comment information), wed try something like this:
$this->Post->contain(Tag); $this->Post->find(all);

Again, we can use the contain key inside a nd() call:


$this->Post->find(all, array(contain => Tag));

Without Containable, youd end up needing to use the unbindModel() method of the model, multiple times if youre paring off multiple models. Containable creates a cleaner way to accomplish this same task.
Containing deeper associations

Containable also goes a step deeper: you can lter the data of the associated models. If you look at the results of the original nd() call, notice the author eld in the Comment model. If you are interested in the posts and the names of the comment authors and nothing else you could do something like the following:
$this->Post->contain(Comment.author); $this->Post->find(all); // or.. $this->Post->find(all, array(contain => Comment.author));

Here, weve told Containable to give us our post information, and just the author eld of the associated Comment model. The output of the nd call might look something like this:
[0] => Array ( [Post] => Array

238

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

( [id] => 1 [title] => First article [content] => aaa [created] => 2008-05-18 00:00:00 ) [Comment] => Array ( [0] => Array ( [author] => Daniel [post_id] => 1 ) [1] => Array ( [author] => Sam [post_id] => 1 ) ) ) [1] => Array (...

As you can see, the Comment arrays only contain the author eld (plus the post_id which is needed by CakePHP to map the results). You can also lter the associated Comment data by specifying a condition:
$this->Post->contain(Comment.author = "Daniel"); $this->Post->find(all); //or... $this->Post->find(all, array(contain => Comment.author = "Daniel"));

This gives us a result that gives us posts with comments authored by Daniel:
[0] => Array ( [Post] => Array ( [id] => 1 [title] => First article [content] => aaa [created] => 2008-05-18 00:00:00 ) [Comment] => Array ( [0] => Array ( [id] => 1 [post_id] => 1 [author] => Daniel [email] => [email protected]

Behaviors

239

CakePHP Cookbook Documentation, Release 2.x

[website] => http://example.com [comment] => First comment [created] => 2008-05-18 00:00:00 ) ) )

Additional ltering can be performed by supplying the standard nd options:


$this->Post->find(all, array(contain => array( Comment => array( conditions => array(Comment.author = => "Daniel"), order => Comment.created DESC ) )));

Heres an example of using the ContainableBehavior when youve got deep and complex model relationships. Lets consider the following model associations:
User->Profile User->Account->AccountSummary User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes User->Post->Tag

This is how we retrieve the above associations with Containable:


$this->User->find(all, array( contain => array( Profile, Account => array( AccountSummary ), Post => array( PostAttachment => array( fields => array(id, name), PostAttachmentHistory => array( HistoryNotes => array( fields => array(id, note) ) ) ), Tag => array( conditions => array(Tag.name LIKE => %happy%) ) ) ) ));

Keep in mind that contain key is only used once in the main model, you dont need to use contain again for related models Note: When using elds and contain options - be careful to include all foreign keys that your query 240 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

directly or indirectly requires. Please also note that because Containable must to be attached to all models used in containment, you may consider attaching it to your AppModel.

ContainableBehavior options

The ContainableBehavior has a number of options that can be set when the Behavior is attached to a model. The settings allow you to ne tune the behavior of Containable and work with other behaviors more easily. recursive (boolean, optional) set to true to allow containable to automatically determine the recursiveness level needed to fetch specied models, and set the model recursiveness to this level. setting it to false disables this feature. The default value is true. notices (boolean, optional) issues E_NOTICES for bindings referenced in a containable call that are not valid. The default value is true. autoFields: (boolean, optional) auto-add needed elds to fetch requested bindings. The default value is true. You can change ContainableBehavior settings at run time by reattaching the behavior as seen in Additional Methods and Properties ContainableBehavior can sometimes cause issues with other behaviors or queries that use aggregate functions and/or GROUP BY statements. If you get invalid SQL errors due to mixing of aggregate and nonaggregate elds, try disabling the autoFields setting.:
$this->Post->Behaviors->load(Containable, array(autoFields => false));

Using Containable with pagination By including the contain parameter in the $paginate property it will apply to both the nd(count) and the nd(all) done on the model See the section Using Containable for further details. Heres an example of how to contain associations when paginating:
$this->paginate[User] = array( contain => array(Profile, Account), order => User.username ); $users = $this->paginate(User);

Translate class TranslateBehavior TranslateBehavior is actually quite easy to setup and works out of the box with very little conguration. In this section, you will learn how to add and setup the behavior to use in any model.

Behaviors

241

CakePHP Cookbook Documentation, Release 2.x

If you are using TranslateBehavior in alongside containable issue, be sure to set the elds key for your queries. Otherwise you could end up with invalid SQL generated.
Initializing the i18n Database Tables

You can either use the CakePHP console or you can manually create it. It is advised to use the console for this, because it might happen that the layout changes in future versions of CakePHP. Sticking to the console will make sure that you have the correct layout.:
./cake i18n

Select [I] which will run the i18n database initialization script. You will be asked if you want to drop any existing and if you want to create it. Answer with yes if you are sure there is no i18n table already, and answer with yes again to create the table.
Attaching the Translate Behavior to your Models

Add it to your model by using the $actsAs property like in the following example.:
class Post extends AppModel { public $actsAs = array( Translate ); }

This will do nothing yet, because it expects a couple of options before it begins to work. You need to dene which elds of the current model should be tracked in the translation table weve created in the rst step.
Dening the Fields

You can set the elds by simply extending the Translate value with another array, like so:
class Post extends AppModel { public $actsAs = array( Translate => array( fieldOne, fieldTwo, and_so_on ) ); }

After you have done that (for example putting title as one of the elds) you already nished the basic setup. Great! According to our current example the model should now look something like this:
class Post extends AppModel { public $actsAs = array( Translate => array( title ) ); }

242

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

When dening elds for TranslateBehavior to translate, be sure to omit those elds from the translated models schema. If you leave the elds in, there can be issues when retrieving data with fallback locales.
Conclusion

From now on each record update/creation will cause TranslateBehavior to copy the value of title to the translation table (default: i18n) along with the current locale. A locale is the identier of the language, so to speak.
Reading translated content

By default the TranslateBehavior will automatically fetch and add in data based on the current locale. The current locale is read from Configure::read(Config.language) which is assigned by the L10n class. You can override this default on the y using $Model->locale. Retrieve translated elds in a specic locale By setting $Model->locale you can read translations for a specic locale:
// Read the spanish locale data. $this->Post->locale = es; $results = $this->Post->find(first, array( conditions => array(Post.id => $id) )); // $results will contain the spanish translation.

Retrieve all translation records for a eld If you want to have all translation records attached to the current model record you simply extend the eld array in your behavior setup as shown below. The naming is completely up to you.:
class Post extends AppModel { public $actsAs = array( Translate => array( title => titleTranslation ) ); }

With this setup the result of $this->Post->find() should look something like this:
Array ( [Post] => Array ( [id] => 1 [title] => Beispiel Eintrag [body] => lorem ipsum... [locale] => de_de )

Behaviors

243

CakePHP Cookbook Documentation, Release 2.x

[titleTranslation] => Array ( [0] => Array ( [id] => 1 [locale] => en_us [model] => Post [foreign_key] => 1 [field] => title [content] => Example entry ) [1] => Array ( [id] => 2 [locale] => de_de [model] => Post [foreign_key] => 1 [field] => title [content] => Beispiel Eintrag ) ) )

Note: The model record contains a virtual eld called locale. It indicates which locale is used in this result. Note that only elds of the model you are directly doing nd on will be translated. Models attached via associations wont be translated because triggering callbacks on associated models is currently not supported. Using the bindTranslation method You can also retrieve all translations, only when you need them, using the bindTranslation method TranslateBehavior::bindTranslation($elds, $reset) $fields is a named-key array of eld and association name, where the key is the translatable eld and the value is the fake association name.:

$this->Post->bindTranslation(array(title => titleTranslation)); $this->Post->find(all, array(recursive => 1)); // need at least recursive 1 for this to

With this setup the result of your nd() should look something like this:
Array ( [Post] => Array ( [id] => 1 [title] => Beispiel Eintrag [body] => lorem ipsum...

244

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

[locale] => de_de ) [titleTranslation] => Array ( [0] => Array ( [id] => 1 [locale] => en_us [model] => Post [foreign_key] => 1 [field] => title [content] => Example entry ) [1] => Array ( [id] => 2 [locale] => de_de [model] => Post [foreign_key] => 1 [field] => title [content] => Beispiel Eintrag ) ) )

Saving in another language

You can force the model which is using the TranslateBehavior to save in a language other than the on detected. To tell a model in what language the content is going to be you simply change the value of the $locale property on the model before you save the data to the database. You can do that either in your controller or you can dene it directly in the model. Example A: In your controller:
class PostsController extends AppController { public function add() { if (!empty($this->request->data)) { $this->Post->locale = de_de; // we are going to save the german version $this->Post->create(); if ($this->Post->save($this->request->data)) { $this->redirect(array(action => index)); } } } }

Behaviors

245

CakePHP Cookbook Documentation, Release 2.x

Example B: In your model:


class Post extends AppModel { public $actsAs = array( Translate => array( title ) ); // Option 1) just define the property directly public $locale = en_us; // Option 2) create a simple method public function setLanguage($locale) { $this->locale = $locale; } }

Multiple Translation Tables

If you expect a lot entries you probably wonder how to deal with a rapidly growing database table. There are two properties introduced by TranslateBehavior that allow to specify which Model to bind as the model containing the translations. These are $translateModel and $translateTable. Lets say we want to save our translations for all posts in the table post_i18ns instead of the default i18n table. To do so you need to setup your model like this:
class Post extends AppModel { public $actsAs = array( Translate => array( title ) ); // Use a different model (and table) public $translateModel = PostI18n; }

Note: It is important that you to pluralize the table. It is now a usual model and can be treated as such and thus comes with the conventions involved. The table schema itself must be identical with the one generated by the CakePHP console script. To make sure it ts one could just initialize a empty i18n table using the console and rename the table afterwards.

Create the TranslateModel For this to work you need to create the actual model le in your models folder. Reason is that there is no property to set the displayField directly in the model using this behavior yet. Make sure that you change the $displayField to field.: 246 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

class PostI18n extends AppModel { public $displayField = field; // important } // filename: PostI18n.php

Thats all it takes. You can also add all other model stuff here like $useTable. But for better consistency we could do that in the model which actually uses this translation model. This is where the optional $translateTable comes into play. Changing the Table If you want to change the name of the table you simply dene $translateTable in your model, like so:
class Post extends AppModel { public $actsAs = array( Translate => array( title ) ); // Use a different model public $translateModel = PostI18n; // Use a different table for translateModel public $translateTable = post_translations; }

Please note that you cant use $translateTable alone. If you dont intend to use a custom $translateModel then leave this property untouched. Reason is that it would break your setup and show you a Missing Table message for the default I18n model which is created in runtime. Tree class TreeBehavior Its fairly common to want to store hierarchical data in a database table. Examples of such data might be categories with unlimited subcategories, data related to a multilevel menu system or a literal representation of hierarchy such as is used to store access control objects with ACL logic. For small trees of data, or where the data is only a few levels deep it is simple to add a parent_id eld to your database table and use this to keep track of which item is the parent of what. Bundled with cake however, is a powerful behavior which allows you to use the benets of MPTT logic (http://www.sitepoint.com/hierarchical-data-database-2/) without worrying about any of the intricacies of the technique - unless you want to ;).
Requirements

To use the tree behavior, your database table needs 3 elds as listed below (all are ints): parent - default eldname is parent_id, to store the id of the parent object

Behaviors

247

CakePHP Cookbook Documentation, Release 2.x

left - default eldname is lft, to store the lft value of the current row. right - default eldname is rght, to store the rght value of the current row. If you are familiar with MPTT logic you may wonder why a parent eld exists - quite simply its easier to do certain tasks if a direct parent link is stored on the database - such as nding direct children. Note: The parent eld must be able to have a NULL value! It might seem to work if you just give the top elements a parent value of zero, but reordering the tree (and possible other operations) will fail.

Basic Usage

The tree behavior has a lot packed into it, but lets start with a simple example - create the following database table and put some data in it:
CREATE TABLE categories ( id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, parent_id INTEGER(10) DEFAULT NULL, lft INTEGER(10) DEFAULT NULL, rght INTEGER(10) DEFAULT NULL, name VARCHAR(255) DEFAULT , PRIMARY KEY (id) ); INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INSERT INTO INTO INTO INTO INTO INTO INTO INTO INTO INTO INTO INTO INTO INTO INTO categories categories categories categories categories categories categories categories categories categories categories categories categories categories categories (id, (id, (id, (id, (id, (id, (id, (id, (id, (id, (id, (id, (id, (id, (id, name, name, name, name, name, name, name, name, name, name, name, name, name, name, name, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, parent_id, lft, lft, lft, lft, lft, lft, lft, lft, lft, lft, lft, lft, lft, lft, lft, rght) rght) rght) rght) rght) rght) rght) rght) rght) rght) rght) rght) rght) rght) rght)

VALUES(1, My Categorie VALUES(2, Fun, 1, 2, VALUES(3, Sport, 2, 3 VALUES(4, Surfing, 3, VALUES(5, Extreme knit VALUES(6, Friends, 2, VALUES(7, Gerald, 6, VALUES(8, Gwendolyn, VALUES(9, Work, 1, 16 VALUES(10, Reports, 9 VALUES(11, Annual, 10 VALUES(12, Status, 10 VALUES(13, Trips, 9, VALUES(14, National, VALUES(15, Internation

For the purpose of checking that everything is setup correctly, we can create a test method and output the contents of our category tree to see what it looks like. With a simple controller:
class CategoriesController extends AppController { public function index() { $data = $this->Category->generateTreeList(null, null, null, &nbsp;&nbsp;&nbsp;); debug($data); die; } }

248

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

and an even simpler model denition:


// app/Model/Category.php class Category extends AppModel { public $actsAs = array(Tree); }

We can check what our category tree data looks like by visiting /categories You should see something like this: My Categories Fun * Sport Surng Extreme knitting * Friends Gerald Gwendolyn Work * Reports Annual Status * Trips National International Adding data In the previous section, we used existing data and checked that it looked hierarchal via the method generateTreeList. However, usually you would add your data in exactly the same way as you would for any model. For example:
// pseudo controller code $data[Category][parent_id] = 3; $data[Category][name] = Skating; $this->Category->save($data);

When using the tree behavior its not necessary to do any more than set the parent_id, and the tree behavior will take care of the rest. If you dont set the parent_id, the tree behavior will add to the tree making your new addition a new top level entry:
// pseudo controller code $data = array(); $data[Category][name] = Other People\s Categories; $this->Category->save($data);

Behaviors

249

CakePHP Cookbook Documentation, Release 2.x

Running the above two code snippets would alter your tree as follows: My Categories Fun * Sport Surng Extreme knitting Skating New * Friends Gerald Gwendolyn Work * Reports Annual Status * Trips National International Other Peoples Categories New Modifying data Modifying data is as transparent as adding new data. If you modify something, but do not change the parent_id eld - the structure of your data will remain unchanged. For example:
// pseudo controller code $this->Category->id = 5; // id of Extreme knitting $this->Category->save(array(name => Extreme fishing));

The above code did not affect the parent_id eld - even if the parent_id is included in the data that is passed to save if the value doesnt change, neither does the data structure. Therefore the tree of data would now look like: My Categories Fun Sport Surng Extreme shing Updated Skating Friends 250 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

Gerald Gwendolyn Work Reports Annual Status Trips National International Other Peoples Categories Moving data around in your tree is also a simple affair. Lets say that Extreme shing does not belong under Sport, but instead should be located under Other Peoples Categories. With the following code:

// pseudo controller code $this->Category->id = 5; // id of Extreme fishing $newParentId = $this->Category->field(id, array(name => Other People\s Categories)); $this->Category->save(array(parent_id => $newParentId));

As would be expected the structure would be modied to: My Categories Fun Sport * Surng * Skating Friends * Gerald * Gwendolyn Work Reports * Annual * Status Trips * National * International Other Peoples Categories

Behaviors

251

CakePHP Cookbook Documentation, Release 2.x

Extreme shing Moved Deleting data The tree behavior provides a number of ways to manage deleting data. To start with the simplest example; lets say that the reports category is no longer useful. To remove it and any children it may have just call delete as you would for any model. For example with the following code:
// pseudo controller code $this->Category->id = 10; $this->Category->delete();

The category tree would be modied as follows: My Categories Fun Sport * Surng * Skating Friends * Gerald * Gwendolyn Work Trips * National * International Other Peoples Categories Extreme shing Querying and using your data Using and manipulating hierarchical data can be a tricky business. In addition to the core nd methods, with the tree behavior there are a few more tree-orientated permutations at your disposal. Note: Most tree behavior methods return and rely on data being sorted by the lft eld. If you call find() and do not order by lft, or call a tree behavior method and pass a sort order, you may get undesirable results. class TreeBehavior children($id = null, $direct = false, $elds = null, $order = null, $limit = null, $page = 1, $recursive = null)

252

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

Parameters $id The ID of the record to look up $direct Set to true to return only the direct descendants $elds Single string eld name or array of elds to include in the return $order SQL string of ORDER BY conditions $limit SQL LIMIT statement $page for accessing paged results $recursive Number of levels deep for recursive associated Models The children method takes the primary key value (the id) of a row and returns the children, by default in the order they appear in the tree. The second optional parameter denes whether or not only direct children should be returned. Using the example data from the previous section:
$allChildren = $this->Category->children(1); // a flat array with 11 items // -- or -$this->Category->id = 1; $allChildren = $this->Category->children(); // a flat array with 11 items // Only return direct children $directChildren = $this->Category->children(1, true); // a flat array with 2 items

Note: If you want a recursive array use find(threaded) childCount($id = null, $direct = false) As with the method children, childCount takes the primary key value (the id) of a row and returns how many children it has. The second optional parameter denes whether or not only direct children are counted. Using the example data from the previous section:
$totalChildren = $this->Category->childCount(1); // will output 11 // -- or -$this->Category->id = 1; $directChildren = $this->Category->childCount(); // will output 11 // Only counts the direct descendants of this category $numChildren = $this->Category->childCount(1, true); // will output 2

generateTreeList($conditions=null, $keyPath=null, $valuePath=null, $spacer= _, $recursive=null) Parameters $conditions Uses the same conditional options as nd(). $keyPath Path to the eld to use for the key. $valuePath Path to the eld to use for the label.

Behaviors

253

CakePHP Cookbook Documentation, Release 2.x

$spacer The string to use in front of each item to indicate depth. $recursive The number of levels deep to fetch associated records This method will return data similar to nd(list), with an indented prex to show the structure of your data. Below is an example of what you can expect this method to return:
$treelist = $this->Category->generateTreeList();

Output:
array( [1] => [2] => [3] => [4] => [16] => [6] => [7] => [8] => [9] => [13] => [14] => [15] => [17] => [5] => ) "My Categories", "_Fun", "__Sport", "___Surfing", "___Skating", "__Friends", "___Gerald", "___Gwendolyn", "_Work", "__Trips", "___National", "___International", "Other Peoples Categories", "_Extreme fishing"

getParentNode() This convenience function will, as the name suggests, return the parent node for any node, or false if the node has no parent (its the root node). For example:
$parent = $this->Category->getParentNode(2); //<- id for fun // $parent contains All categories

getPath($id = null, $elds = null, $recursive = null) The path when referring to hierarchal data is how you get from where you are to the top. So for example the path from the category International is: My Categories ... Work Trips *... *International Using the id of International getPath will return each of the parents in turn (starting from the top).:

254

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

$parents = $this->Category->getPath(15); // contents array( [0] => [1] => [2] => [3] => ) of $parents array(Category array(Category array(Category array(Category => => => => array(id array(id array(id array(id => => => => 1, name => My Categories, ..)), 9, name => Work, ..)), 13, name => Trips, ..)), 15, name => International, ..)),

Advanced Usage

The tree behavior doesnt only work in the background, there are a number of specic methods dened in the behavior to cater for all your hierarchical data needs, and any unexpected problems that might arise in the process. TreeBehavior::moveDown() Used to move a single node down the tree. You need to provide the ID of the element to be moved and a positive number of how many positions the node should be moved down. All child nodes for the specied node will also be moved. Here is an example of a controller action (in a controller named Categories) that moves a specied node down the tree:
public function movedown($id = null, $delta = null) { $this->Category->id = $id; if (!$this->Category->exists()) { throw new NotFoundException(__(Invalid category)); }

if ($delta > 0) { $this->Category->moveDown($this->Category->id, abs($delta)); } else { $this->Session->setFlash(Please provide the number of positions the field sho } $this->redirect(array(action => index), null, true); }

For example, if youd like to move the Sport ( id of 3 ) category one position down, you would request: /categories/movedown/3/1. TreeBehavior::moveUp() Used to move a single node up the tree. You need to provide the ID of the element to be moved and a positive number of how many positions the node should be moved up. All child nodes will also be moved. Heres an example of a controller action (in a controller named Categories) that moves a node up the tree:

Behaviors

255

CakePHP Cookbook Documentation, Release 2.x

public function moveup($id = null, $delta = null) { $this->Category->id = $id; if (!$this->Category->exists()) { throw new NotFoundException(__(Invalid category)); }

if ($delta > 0) { $this->Category->moveUp($this->Category->id, abs($delta)); } else { $this->Session->setFlash(Please provide a number of positions the category sh } $this->redirect(array(action => index), null, true); }

For example, if you would like to move the category Gwendolyn ( id of 8 ) up one position you would request /categories/moveup/8/1. Now the order of Friends will be Gwendolyn, Gerald. TreeBehavior::removeFromTree($id = null, $delete = false) Using this method will either delete or move a node but retain its sub-tree, which will be reparented one level higher. It offers more control than delete, which for a model using the tree behavior will remove the specied node and all of its children. Taking the following tree as a starting point: My Categories Fun * Sport Surng Extreme knitting Skating Running the following code with the id for Sport:
$this->Node->removeFromTree($id);

The Sport node will be become a top level node: My Categories Fun * Surng * Extreme knitting * Skating Sport Moved This demonstrates the default behavior of removeFromTree of moving the node to have no parent, and re-parenting all children. 256 Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

If however the following code snippet was used with the id for Sport:
$this->Node->removeFromTree($id, true);

The tree would become My Categories Fun * Surng * Extreme knitting * Skating This demonstrates the alternate use for removeFromTree, the children have been reparented and Sport has been deleted. TreeBehavior::reorder(array(id => null, eld => $Model->displayField, order => ASC, verify => true)) Reorders the nodes (and child nodes) of the tree according to the eld and direction specied in the parameters. This method does not change the parent of any node.:

$model->reorder(array( id => , //id of record to use as top node for reordering, default: $Model->id field => , //which field to use in reordering, default: $Model->displayField order => , //direction to order, default: ASC verify => //whether or not to verify the tree before reorder, default: true ));

Note: If you have saved your data or made other operations on the model, you might want to set $model->id = null before calling reorder. Otherwise only the current node and its children will be reordered.

Data Integrity

Due to the nature of complex self referential data structures such as trees and linked lists, they can occasionally become broken by a careless call. Take heart, for all is not lost! The Tree Behavior contains several previously undocumented features designed to recover from such situations. TreeBehavior::recover($mode = parent, $missingParentAction = null) The mode parameter is used to specify the source of info that is valid/correct. The opposite source of data will be populated based upon that source of info. E.g. if the MPTT elds are corrupt or empty, with the $mode parent the values of the parent_id eld will be used to populate the left and right elds. The missingParentAction parameter only applies to parent mode and determines what to do if the parent eld contains an id that is not present. Available $mode options:

Behaviors

257

CakePHP Cookbook Documentation, Release 2.x

parent - use the existing parent_ids to update the lft and rght elds tree - use the existing lft and rght elds to update parent_id Available missingParentActions options when using mode=parent: null - do nothing and carry on return - do nothing and return delete - delete the node int - set the parent_id to this id Example:
// Rebuild all the left and right fields based on the parent_id $this->Category->recover(); // or $this->Category->recover(parent); // Rebuild all the parent_ids based on the lft and rght fields $this->Category->recover(tree);

TreeBehavior::reorder($options = array()) Reorders the nodes (and child nodes) of the tree according to the eld and direction specied in the parameters. This method does not change the parent of any node. Reordering affects all nodes in the tree by default, however the following options can affect the process: id - only reorder nodes below this node. field - eld to use for sorting, default is the displayField for the model. order - ASC for ascending, DESC for descending sort. verify - whether or not to verify the tree prior to resorting. $options is used to pass all extra parameters, and has the following possible keys by default, all of which are optional:
array( id => null, field => $model->displayField, order => ASC, verify => true )

TreeBehavior::verify() Returns true if the tree is valid otherwise an array of errors, with elds for type, incorrect index and message. Each record in the output array is an array of the form (type, id, message) type is either index or node

258

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

id is the id of the erroneous node. message depends on the error Example Use:
$this->Category->verify();

Example output:
Array ( [0] => Array ( [0] => [1] => [2] => ) [1] => Array ( [0] => [1] => [2] => ) [10] => Array ( [0] => [1] => [2] => ) [99] => Array ( [0] => [1] => [2] => ) )

node 3 left and right values identical

node 2 The parent node 999 doesn t exist

index 123 missing

node 163 left greater than right

Components
CakePHP has a selection of components to help take care of basic tasks in your controllers. See the section on Components for how to congure and use components.

Components
Access Control Lists class AclComponent(ComponentCollection $collection, array $settings = array())

Components

259

CakePHP Cookbook Documentation, Release 2.x

CakePHPs access control list functionality is one of the most oft-discussed, most likely because it is the most sought after, but also because it can be the most confusing. If youre looking for a good way to get started with ACLs in general, read on. Be brave and stick with it, even if the going gets rough. Once you get the hang of it, its an extremely powerful tool to have on hand when developing your application.
Understanding How ACL Works

Powerful things require access control. Access control lists are a way to manage application permissions in a ne-grained, yet easily maintainable and manageable way. Access control lists, or ACL, handle two main things: things that want stuff, and things that are wanted. In ACL lingo, things (most often users) that want to use stuff are called access request objects, or AROs. Things in the system that are wanted (most often actions or data) are called access control objects, or ACOs. The entities are called objects because sometimes the requesting object isnt a person - sometimes you might want to limit the access certain Cake controllers have to initiate logic in other parts of your application. ACOs could be anything you want to control, from a controller action, to a web service, to a line on your grandmas online diary. To review: ACO - Access Control Object - Something that is wanted ARO - Access Request Object - Something that wants something Essentially, ACL is what is used to decide when an ARO can have access to an ACO. In order to help you understand how everything works together, lets use a semi-practical example. Imagine, for a moment, a computer system used by a familiar group of fantasy novel adventurers from the Lord of the Rings. The leader of the group, Gandalf, wants to manage the partys assets while maintaining a healthy amount of privacy and security for the other members of the party. The rst thing he needs to do is create a list of the AROs involved: Gandalf Aragorn Bilbo Frodo Gollum Legolas Gimli Pippin Merry

260

Chapter 7. Core Libraries

CakePHP Cookbook Documentation, Release 2.x

Note: Realize that ACL is not the same as authentication. ACL is what happens after a user has been authenticated. Although the two are usually used in concert, its important to realize the difference between knowing who someone is (authentication) and knowing what they can do (ACL). 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 table: x Gandalf Aragorn Bilbo Frodo Gollum Legolas Gimli Pippin Merry Weapons Allow Allow Allow Allow Allow Allow Allow Allow Allow Allow The Ring Salted Pork Allow Allow Diplomacy Allow Allow Ale Allow Allow Allow Allow Allow Allow Allow

At rst 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 and weapons). It seems ne 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. Another drawback to matrices is that you cant 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 individual user basis w