The Manual
Click here for the CakePHP 1.1.x version of the manual
Welcome to the Cookbook, the CakePHP documentation. The Cookbook is a wiki-like system allowing contributions from the public. With an open system, we hope to maintain a high level of quality, validity, and accuracy for the CakePHP documentation. The Cookbook also makes it easy for anybody to contribute.
A huge thank you to AD7six, who championed the Cookbook by putting in endless hours developing, testing and improving this application.
# How it Works
- You visit the site and notice an error, something that is incomplete, something that hasn't been covered at all, or something that just isn't worded to your liking.
- Log in to Cookbook using your Bakery account.
- Submit additions/edits for review using valid, semantic HTML.
- Check back in the next day or so to see your changes approved.
- Please review the guidelines for submitting to the Cookbook to ensure consistency.
# Translations
Email John David Anderson (docs at cakephp dot org) or on IRC (#cakephp on freenode as _psychic_) to discuss any translation efforts you would like to participate in.
Translator tips:
- Browse and edit in the language you want the content to be translated to - otherwise you won't see what has already been translated.
- Feel free to dive right in if your chosen language already exists on the book.
- Use the to do list (top right) to see where attention is needed for your language.
- Use Informal Form.
- Translate both the content and the title at the same time.
- Do compare to the English content before submitting a correction (if you correct something, but don't integrate an 'upstream' change your submission won't be accepted).
- If you need to write an English term, wrap it in
<em>tags. E.g. "asdf asdf Controller asdf" or "asdf asdf Kontroller (Controller) asfd" as appropriate. - Do not edit a section with a pending change.
- Do not use html entities for accented characters, the book uses UTF-8.
- Do not significantly change the markup (HTML) or add new content - If the original content is missing some info, submit an edit for that first.
We're committed to making the documentation for CakePHP better than it has ever been. We hope you'll join us by using the Cookbook and giving back to a project that we've all benefited so much from.
1 Beginning With CakePHP
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 object-oriented 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.
1.1 What is CakePHP? Why Use it?
CakePHP is a free, open-source, rapid development framework for PHP. It’s a foundational structure for programmers to create web applications. Our primary goal is to enable you to work in a structured and rapid manner–without loss of flexibility.
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 specific 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 and community, bringing great value to the project. In addition to keeping you from wheel-reinventing, using CakePHP means your application’s core is well tested and is being constantly improved.
Here’s a quick list of features you’ll enjoy when using CakePHP:
- Active, friendly community
- Flexible licensing
- Compatible with versions 4 and 5 of PHP
- Integrated CRUD for database interaction
- Application scaffolding
- Code generation
- MVC architecture
- Request dispatcher with clean, custom URLs and routes
- Built-in validation
- Fast and flexible templating (PHP syntax, with helpers)
- View Helpers for AJAX, JavaScript, HTML Forms and more
- Email, Cookie, Security, Session, and Request Handling Components
- Flexible ACL
- Data Sanitization
- Flexible Caching
- Localization
- Works from any web site directory, with little to no Apache configuration involved
1.2 Where to Get Help
# The Official CakePHP website
The Official CakePHP website is always a great place to visit. It features links to oft-used developer tools, screencasts, donation opportunities, and downloads.
# The Cookbook
This manual should probably be the first 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 first. Answers may come slower, but will remain longer–and you'll also be lightening our support load. Both the manual and the API have an online component.
# The Bakery
The CakePHP Bakery is a clearing house for all things CakePHP. Check it out for tutorials, case studies, and code examples. Once you’re acquainted with CakePHP, log on and share your knowledge with the community and gain instant fame and fortune.
# The API
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.
# CakeForge
CakeForge is another developer resource you can use to host your CakePHP projects to share with others. If you’re looking for (or want to share) a killer component or a praiseworthy plugin, check out CakeForge.
# The Test Cases
If you ever feel the information provided in the API is not sufficient, check out the code of the test cases provided with CakePHP 1.2. They can serve as practical examples for function and data member usage for a class. To get the core test cases you need to download a nightly package or do a svn branch checkout. The test cases will be located under
cake/tests/cases
cake/tests/cases
# The IRC channel
If you’re 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. We’d love to hear from you, whether you need some help, want to find 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 finding archived answers, frequently asked questions, and getting answers to immediate problems.
1.3 Understanding Model-View-Controller
CakePHP follows the MVC software design pattern. Programming using MVC separates your application into three main parts:
- The Model represents the application data
- The View renders a presentation of model data
- The Controller handles and routes requests made by the client
Figure: 1: A Basic MVC Request
Figure: 1 shows an example of a bare-bones MVC request in CakePHP. To illustrate, assume a client named "Ricardo" just clicked on the “Buy A Custom Cake Now!” link on your application’s home page.
- Ricardo clicks the link pointing to http://www.example.com/cakes/buy, and his browser makes a request to your web server.
- The dispatcher checks the request URL (/cakes/buy), and hands the request to the correct controller.
- The controller performs application specific logic. For example, it may check to see if Ricardo has logged in.
- The controller also uses models to gain access to the application’s data. Models usually represent database tables, but they could also represent LDAP entries, RSS feeds, or files on the system. In this example, the controller uses a model to fetch Ricardo’s last purchases from the database.
- Once the controller has worked its magic on the data, it hands it to a view. The view takes this data and gets it ready for presentation to the client. Views in CakePHP are usually in HTML format, but a view could just as easily be a PDF, XML document, or JSON object depending on your needs.
- Once the view has used the data from the controller to build a fully rendered view, the content of that view is returned to Ricardo’s browser.
Almost every request to your application will follow this basic pattern. We'll add some details later on which are specific to CakePHP, so keep this in mind as we proceed.
1.3.1 Benefits
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. Separation also allows developers to make changes in one part of the application without affecting others.
If you've never built an application this way, it takes some time getting used to, but we're confident that once you've built your first application using CakePHP, you won't want to do it any other way.
2 Basic Principles of CakePHP
The CakePHP framework provides a robust base for your application. It can handle every aspect, from the user’s initial request all the way to the final 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 filenames to database table names, keeping your entire application consistent and logical. This concept is simple but powerful. Follow the conventions and you’ll always know exactly where things are and how they’re organized.
2.1 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 we’ll stay at a higher level, so look for the details on how to use these tools later on.
2.1.1 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 fit. 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 fitted with callbacks. These callbacks are available for your use, just in case you need to insert some logic between CakePHP’s core operations. Callbacks available include:
beforeFilter(), executed before any controller action logicbeforeRender(), executed after controller logic, but before the view is renderedafterFilter(), executed after all controller logic, including the view render. There may be no difference betweenafterRender()andafterFilter()unless you’ve manually made a call torender()in your controller action and have included some logic after that call.
2.1.2 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, AjaxHelper, makes Ajax requests within views much easier.
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.
2.1.3 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 files, 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 find the details in the models chapter.
2.1.4 Application Extensions
Controllers, helpers and models each have a parent class you can use to define application-wide changes. AppController (located at /app/app_controller.php), AppHelper (located at /app/app_helper.php) and AppModel (located at /app/app_model.php) are great places to put methods you want to share between all controllers, helpers or models.
Although they aren’t classes or files, routes play a role in requests made to CakePHP. Route definitions 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 specific purpose that can span multiple applications. A user management system or a simplified blog might be a good fit for CakePHP plugins.
2.2 A Typical CakePHP Request
We’ve covered the basic ingredients in CakePHP, so let’s look at how objects work together to complete a basic request. Continuing with our original request example, let’s imagine that our friend Ricardo just clicked on the “Buy A Custom Cake Now!” link on a CakePHP application’s landing page.

Figure: 2. Typical Cake Request.
Black = required element, Gray = optional element, Blue = callback
- Ricardo clicks the link pointing to http://www.example.com/cakes/buy, and his browser makes a request to your web server.
- 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.
- Using routes, a request URL is mapped to a controller action (a method in a specific controller class). In this case, it’s the buy() method of the CakesController. The controller’s beforeFilter() callback is called before any controller action logic is executed.
- The controller may use models to gain access to the application’s data. In this example, the controller uses a model to fetch Ricardo’s 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.
- After the model has retrieved the data, it is returned to the controller. Model callbacks may apply.
- The controller may use components to further refine the data or perform other operations (session manipulation, authentication, or sending emails, for example).
- Once the controller has used models and components to prepare the data sufficiently, that data is handed to the view using the controller’s 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.
- Additional controller callbacks (like afterFilter) may be applied. The complete, rendered view code is sent to Ricardo’s browser.
2.3 CakePHP Folder Structure
After you've downloaded and extracted CakePHP, these are the files and folders you should see:
- app
- cake
- vendors
- .htaccess
- index.php
- README
You'll notice three main folders:
- The app folder will be where you work your magic: it’s where your application’s files will be placed.
- The cake folder is where we’ve worked our magic. Make a personal commitment not to edit files in this folder. We can’t help you if you’ve modified the core.
- Finally, the vendors folder is where you’ll place third-party PHP libraries you need to use with your CakePHP applications.
2.3.1 The App Folder
CakePHP’s app folder is where you will do most of your application development. Let’s look a little closer at the folders inside of app.
| config | Holds the (few) configuration files CakePHP uses. Database connection details, bootstrapping, core configuration files and more should be stored here. |
|---|---|
| controllers | Contains your application’s controllers and their components. |
| locale | Stores string files for internationalization. |
| models | Contains your application’s models, behaviors, and datasources. |
| plugins | Contains plugin packages. |
| tmp | This is where CakePHP stores temporary data. The actual data it stores depends on how you have CakePHP configured, but this folder is usually used to store model descriptions, logs, and sometimes session information. |
| vendors | 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. We'll get into the differences between the two when we discuss managing multiple applications and more complex system setups. |
| views | Presentational files are placed here: elements, error pages, helpers, layouts, and view files. |
| 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 files. |
2.4 CakePHP Conventions
We are big fans of convention over configuration. While it takes a bit of time to learn CakePHP’s 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 config files. Convention also makes for a very uniform system development, allowing other developers to jump in and help more easily.
CakePHP’s 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.
2.4.1 File and Classname Conventions
In general, filenames are underscored while classnames are CamelCased. So if you have a class MyNiftyClass, then in Cake, the file should be named my_nifty_class.php. Below are examples of how to name the file 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 file named kisses_and_hugs_controller.php (notice _controller in the filename)
- The Component class MyHandyComponent would be found in a file named my_handy.php
- The Model class OptionValue would be found in a file named option_value.php
- The Behavior class EspeciallyFunkableBehavior would be found in a file named especially_funkable.php
- The View class SuperSimpleView would be found in a file named super_simple.ctp
- The Helper class BestEverHelper would be found in a file named best_ever.php
Each file would be located in or under (can be in a subfolder) the appropriate folder in your app folder.
2.4.2 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.
Foreign keys in hasMany, belongsTo or hasOne relationships are recognized by default as the (singular) name of the related model followed by _id. So if a baker hasMany cakes, the cakes table will refer to the baker in the bakers table via a baker_id foreign key.
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-field primary key, such as the rows of your posts_tags join table, CakePHP's convention is that a single-field primary key is added to the table.
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));
2.4.3 Controller Conventions
Controller classnames are plural, CamelCased, and end in Controller. PeopleController and LatestArticlesController are both examples of conventional controller names.
The first method you write for a controller might be the index() method. When a request specifies 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.
You can also change the visibility of controller methods in CakePHP by prefixing controller method names with underscores. If a controller method has been prefixed with an underscore, the method will not be accessible directly from the web but is available for internal use. For example:
<?php
class NewsController extends AppController {
function latest() {
$this->_findNewArticles();
}
function _findNewArticles() {
//Logic to find latest news articles
}
}
?>
<?phpclass NewsController extends AppController {function latest() {$this->_findNewArticles();}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/_findNewArticles/ would get an error, because the method is preceded with an underscore.
2.4.3.1 URL Considerations for Controller Names
As you've just seen, single word controllers map easily to a simple lower case URL path. For example, ApplesController (which would be defined in the file name 'apples_controller.php') is accessed from http://example.com/apples.
Multiple word controllers can be any 'inflected' 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 Configuration.
2.4.4 View Conventions
View template files 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/views/people/get_ready.ctp.
The basic pattern is /app/views/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 configuration. Here’s a final example that ties the conventions
- Database table: "people"
- Model class: "Person", found at /app/models/person.php
- Controller class: "PeopleController", found at /app/controllers/people_controller.php
- View template, found at /app/views/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 file. None of these relationships have been configured by any means other than by creating classes and files that you’d need to create anyway.
Now that you've been introduced to CakePHP's fundamentals, you might try a run through the CakePHP Blog Tutorial to see how things fit together.
3 Developing with CakePHP
Now you’re cooking.
3.1 Requirements
- HTTP Server. Apache with mod_rewrite is preferred, but by no means required.
- PHP 4.3.2 or greater. Yes, CakePHP works great on PHP 4 and 5.
Technically a database engine isn’t required, but we imagine that most applications will utilize one. CakePHP supports a variety of database storage engines:
- MySQL (4 or greater)
- PostgreSQL
- Firebird DB2
- Microsoft SQL Server
- Oracle
- SQLite
- ODBC
- ADOdb
3.2 Installation Preparation
CakePHP is fast and easy to install. The minimum requirements are a webserver and a copy of Cake, that's it! While this manual focuses primarily on setting up with Apache (because it's the most common), you can configure Cake to run on a variety of web servers such as LightHTTPD or Microsoft IIS.
Installation preparation consists of the following steps:
- Downloading a copy of CakePHP
- Configuring your web server to handle php if necessary
- Checking file permissions
3.2.1 Getting 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 SVN 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 at CakeForge, the home of CakePHP. This site also contains links to many other CakePHP projects, including plugins and applications for CakePHP. The CakePHP releases are available at http://cakeforge.org/projects/cakephp.
Alternatively nightly builds are produced which include bug-fixes and up to the minute(well, to the day) enhancements. These can be accessed from the download index here: http://cakephp.org/downloads/index/nightly. For true up to the minute updates, you can check out directly from the development branch of the svn repository here: https://svn.cakephp.org/repo/branches/1.2.x.x.
3.2.2 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 /app/tmp directory in your cake installation is writable by the web server user.
3.3 Installation
Installing CakePHP can be as simple as slapping it in your web server’s document root, or as complex and flexible 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 configure the web server’s document root, clean URLs, very secure.
- Advanced: With some configuration, allows you to place key CakePHP directories in different parts of the filesystem, possibly sharing a single CakePHP core library folder amongst many CakePHP applications.
3.3.1 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_1_2/. We assume for the purposes of this example that your document root is set to /var/www/html.
Unpack the contents of the Cake archive into /var/www/html. You now have a folder in your document root named after the release you've downloaded (e.g. cake_1.2.0.7962). Rename this folder to cake_1_2. Your development setup will look like this on the file system:
- /var/www/html
- /cake_1_2
- /app
- /cake
- /vendors
- /.htaccess
- /index.php
- /README
- /cake_1_2
If your web server is configured correctly, you should now find your Cake application accessible at http://www.example.com/cake_1_2/.
3.3.2 Production
A production installation is a more flexible 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 filesystem and make it available at http://www.example.com. Note that this installation may require the rights to change the DocumentRoot on an Apache webservers.
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 filesystem:
- /cake_install/
- /app
- /webroot (this directory is set as the
DocumentRootdirective)
- /webroot (this directory is set as the
- /cake
- /vendors
- /.htaccess
- /index.php
- /README
- /app
Developers using Apache should set the DocumentRoot directive for the domain to:
DocumentRoot /cake_install/app/webroot
If your web server is configured correctly, you should now find your Cake application accessible at http://www.example.com.
3.3.3 Advanced Installation
There may be some situations where you wish to place CakePHP's directories on different places on the filesystem. 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 filesystem.
First, realize that there are three main parts to a Cake application:
- The core CakePHP libraries, in /cake.
- Your application code, in /app.
- The application’s webroot, usually in /app/webroot.
Each of these directories can be located anywhere on your file system, with the exception of the webroot, which needs to be accessible by your web server. You can even move the webroot folder out of the app folder as long as you tell Cake where you've put it.
To configure your Cake installation, you'll need to make some changes to /app/webroot/index.php. There are three constants that you'll need to edit: ROOT, APP_DIR, and CAKE_CORE_INCLUDE_PATH.
ROOTshould be set to the path of the directory that contains your app folder.APP_DIRshould be set to the (base)name of your app folder.CAKE_CORE_INCLUDE_PATHshould be set to the path of your CakePHP libraries folder.
Let’s 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 application’s webroot directory will be /var/www/mysite/.
- My application’s app directory will be stored in /home/me/mysite.
Given this type of setup, I would need to edit my webroot/index.php file (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', 'mysite');
}
if (!defined('CAKE_CORE_INCLUDE_PATH')) {
define('CAKE_CORE_INCLUDE_PATH', DS.'usr'.DS.'lib');
}
// /app/webroot/index.php (partial, comments removed)if (!defined('ROOT')) {define('ROOT', DS.'home'.DS.'me');}if (!defined('APP_DIR')) {define ('APP_DIR', 'mysite');}if (!defined('CAKE_CORE_INCLUDE_PATH')) {define('CAKE_CORE_INCLUDE_PATH', DS.'usr'.DS.'lib');}
It is recommended to use the DS constant rather than slashes to delimit file paths. This prevents any missing file errors you might get as a result of using the wrong delimiter, and it makes your code more portable.
3.3.3.1 Additional Class Paths
It’s occasionally useful to be able to share MVC classes between applications on the same system. If you want the same controller in both applications, you can use CakePHP’s bootstrap.php to bring these additional classes into view.
In bootstrap.php, define some specially-named variables to make CakePHP aware of other places to look for MVC classes:
$viewPaths = array(); $controllerPaths = array(); $modelPaths = array(); $helperPaths = array(); $componentPaths = array(); $behaviorPaths = array(); $pluginPaths = array(); $vendorPaths = array(); $localePaths = array(); $shellPaths = array();
$viewPaths = array();$controllerPaths = array();$modelPaths = array();$helperPaths = array();$componentPaths = array();$behaviorPaths = array();$pluginPaths = array();$vendorPaths = array();$localePaths = array();$shellPaths = array();
Each of these special variables can be set to an array of absolute filesystem paths where extra classes can be found when requested. Make sure that each path specified includes a trailing slash.
3.3.4 Apache and mod_rewrite (and .htaccess)
While CakePHP is built to work with mod_rewrite out of the box–and usually does–we've 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-specific httpd.conf).
Make sure that an .htaccess override is allowed and that AllowOverride is set to All for the correct DocumentRoot. You should see a 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>## 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 FollowSymLinksAllowOverride All# Order deny,allow# Deny from all</Directory>
Make sure you are loading up mod_rewrite correctly. You should see something like:
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
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 files are actually in the right directories. This can happen during copying because some operating systems treat files that start with '.' as hidden and therefore won't see them to copy.Make sure your copy of CakePHP is from the downloads section of the site or our SVN repository, and has been unpacked correctly by checking for .htaccess files.
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/ [L] RewriteRule (.*) app/webroot/$1 [L] </IfModule>
<IfModule mod_rewrite.c>RewriteEngine onRewriteRule ^$ app/webroot/ [L]RewriteRule (.*) app/webroot/$1 [L]</IfModule>
Cake app directory (will be copied to the top directory of your application by bake):
<IfModule mod_rewrite.c> RewriteEngine on RewriteRule ^$ webroot/ [L] RewriteRule (.*) webroot/$1 [L] </IfModule><IfModule mod_rewrite.c>RewriteEngine onRewriteRule ^$ webroot/ [L]RewriteRule (.*) webroot/$1 [L]</IfModule>
Cake webroot directory (will be copied to your application's web root by bake):
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] </IfModule><IfModule mod_rewrite.c>RewriteEngine OnRewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME} !-fRewriteRule ^(.*)$ index.php?url=$1 [QSA,L]</IfModule>
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, you'll need to add RewriteBase statements to the .htaccess files 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 file would look like:
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] </IfModule><IfModule mod_rewrite.c>RewriteEngine OnRewriteBase /RewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME} !-fRewriteRule ^(.*)$ index.php?url=$1 [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 Apache's online documentation for more information.
3.3.5 Lighttpd and mod_magnet
While lighttpd features a rewrite module, it is not an equivalent of Apache's mod_rewrite. Full mod_rewrite functionalities are spread amongst Lighttpd's mod_rewrite, mod_magnet and mod_proxy.
CakePHP, however, mostly needs mod_magnet to redirect requests in order to work with pretty URLs.
To use pretty URLs with CakePHP and Lighttp, place this lua script in /etc/lighttpd/cake.
-- little helper function
function file_exists(path)
local attr = lighty.stat(path)
if (attr) then
return true
else
return false
end
end
function removePrefix(str, prefix)
return str:sub(1,#prefix+1) == prefix.."/" and str:sub(#prefix+2)
end
-- prefix without the trailing slash
local prefix = ''
-- the magic ;)
if (not file_exists(lighty.env["physical.path"])) then
-- file still missing. pass it to the fastcgi backend
request_uri = removePrefix(lighty.env["uri.path"], prefix)
if request_uri then
lighty.env["uri.path"] = prefix .. "/index.php"
local uriquery = lighty.env["uri.query"] or ""
lighty.env["uri.query"] = uriquery .. (uriquery ~= "" and "&" or "") .. "url=" .. request_uri
lighty.env["physical.rel-path"] = lighty.env["uri.path"]
lighty.env["request.orig-uri"] = lighty.env["request.uri"]
lighty.env["physical.path"] = lighty.env["physical.doc-root"] .. lighty.env["physical.rel-path"]
end
end
-- fallthrough will put it back into the lighty request loop
-- that means we get the 304 handling for free. ;)
-- little helper functionfunction file_exists(path)local attr = lighty.stat(path)if (attr) thenreturn trueelsereturn falseendendfunction removePrefix(str, prefix)return str:sub(1,#prefix+1) == prefix.."/" and str:sub(#prefix+2)end-- prefix without the trailing slashlocal prefix = ''-- the magic ;)if (not file_exists(lighty.env["physical.path"])) then-- file still missing. pass it to the fastcgi backendrequest_uri = removePrefix(lighty.env["uri.path"], prefix)if request_uri thenlighty.env["uri.path"] = prefix .. "/index.php"local uriquery = lighty.env["uri.query"] or ""lighty.env["uri.query"] = uriquery .. (uriquery ~= "" and "&" or "") .. "url=" .. request_urilighty.env["physical.rel-path"] = lighty.env["uri.path"]lighty.env["request.orig-uri"] = lighty.env["request.uri"]lighty.env["physical.path"] = lighty.env["physical.doc-root"] .. lighty.env["physical.rel-path"]endend-- fallthrough will put it back into the lighty request loop-- that means we get the 304 handling for free. ;)
If you run your CakePHP installation from a subdirectory, you must set prefix = 'subdirectory_name' in the above script.
Then tell Lighttpd about your vhost:
$HTTP["host"] =~ "example.com" {
server.error-handler-404 = "/index.php"
magnet.attract-physical-path-to = ( "/etc/lighttpd/cake.lua" )
server.document-root = "/var/www/cake-1.2/app/webroot/"
# Think about getting vim tmp files out of the way too
url.access-deny = (
"~", ".inc", ".sh", "sql", ".sql", ".tpl.php",
".xtmpl", "Entries", "Repository", "Root",
".ctp", "empty"
)
}
3.3.6 Fire It Up
Alright, let's 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, you'll be presented with CakePHP's default home, and a message that tells you the status of your current database connection.
Congratulations! You are ready to create your first CakePHP application.
3.4 Configuration
Configuring a CakePHP application is a piece of cake. After you have installed CakePHP, creating a basic web application requires only that you setup a database configuration.
There are, however, other optional configuration steps you can take in order to take advantage of CakePHP flexible architecture. You can easily add to the functionality inherited from the CakePHP core, configure additional/different URL mappings (routes), and define additional/different inflections.
3.4.1 Database Configuration
CakePHP expects database configuration details to be in a file at app/config/database.php. An example database configuration file can be found at app/config/database.php.default. A finished configuration should look something like this.
var $default = array('driver' => 'mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'cakephpuser',
'password' => 'c4k3roxx!',
'database' => 'my_cakephp_project',
'prefix' => '');
var $default = array('driver' => 'mysql','persistent' => false,'host' => 'localhost','login' => 'cakephpuser','password' => 'c4k3roxx!','database' => 'my_cakephp_project','prefix' => '');
The $default connection array is used unless another connection is specified by the $useDbConfig property in a model. For example, if my application has an additional legacy database in addition to the default one, I could use it in my models by creating a new $legacy database connection array similar to the $default array, and by setting var $useDbConfig = ‘legacy’; in the appropriate models.
Fill out the key/value pairs in the configuration array to best suit your needs.
| Key | Value |
|---|---|
| driver | The name of the database driver this configuration array is for. Examples: mysql, postgres, sqlite, pear-drivername, adodb-drivername, mssql, oracle, or odbc. |
| persistent | Whether or not to use a persistent connection to the database. |
| host | The database server’s hostname (or IP address). |
| login | The username for the account. |
| password | The password for the account. |
| database | The name of the database for this connection to use. |
| prefix (optional) | The string that prefixes every table name in the database. If your tables don’t have prefixes, set this to an empty string. |
| port (optional) | The TCP port or Unix socket used to connect to the server. |
| encoding | Indicates the character set to use when sending SQL statements to the server. This defaults to the database's default encoding for all databases other than DB2. If you wish to use UTF-8 encoding with mysql/mysqli connections you must use 'utf8' without the hyphen. |
| schema | Used in PostgreSQL database setups to specify which schema to use. |
The prefix setting is for tables, not models. For example, if you create a join table for your Apple and Flavor models, you name it prefix_apples_flavors (not prefix_apples_prefix_flavors), and set your prefix setting to 'prefix_'.
At this point, you might want to take a look at the CakePHP Conventions. The correct naming for your tables (and the addition of some columns) can score you some free functionality and help you avoid configuration. For example, if you name your database table big_boxes, your model BigBox, your controller BigBoxesController, everything just works together automatically. By convention, use underscores, lower case, and plural forms for your database table names - for example: bakers, pastry_stores, and savory_cakes.
3.4.2 Core Configuration
Application configuration in CakePHP is found in /app/config/core.php. This file is a collection of Configure class variable definitions and constant definitions that determine how your application behaves. Before we dive into those particular variables, you’ll need to be familiar with Configure, CakePHP’s configuration registry class.
3.4.3 The Configuration Class
Despite few things needing to be configured in CakePHP, it’s sometimes useful to have your own configuration rules for your application. In the past you may have defined custom configuration values by defining variable or constants in some files. Doing so forces you to include that configuration file every time you needed to use those values.
CakePHP’s new Configure class can be used to store and retrieve application or runtime specific values. Be careful, this class allows you to store anything in it, then use it in any other part of your code: a sure temptation to break the MVC pattern CakePHP was designed for. The main goal of Configure class is to keep centralized variables that can be shared between many objects. Remember to try to live by "convention over configuration" and you wont end up breaking the MVC structure we’ve set in place.
This class acts as a singleton and its methods can be called from anywhere within your application, in a static context.
<?php Configure::read('debug'); ?>
<?php Configure::read('debug'); ?>
3.4.3.1 Configure Methods
3.4.3.1.1 write
write(string $key, mixed $value)
Use write() to store data in the application’s configuration.
Configure::write('Company.name','Pizza, Inc.');
Configure::write('Company.slogan','Pizza for your body and soul');
Configure::write('Company.name','Pizza, Inc.');Configure::write('Company.slogan','Pizza for your body and soul');
The dot notation used in the $key parameter can be used to organize your configuration settings into logical groups.
The above example could also be written in a single call:
Configure::write(
'Company',array('name'=>'Pizza, Inc.','slogan'=>'Pizza for your body and soul')
);
Configure::write('Company',array('name'=>'Pizza, Inc.','slogan'=>'Pizza for your body and soul'));
You can use Configure::write('debug', $int) to switch between debug and production modes on the fly. This is especially handy for AMF or SOAP interactions where debugging information can cause parsing problems.
3.4.3.1.2 read
read(string $key = 'debug')
Used to read configuration data from the application. Defaults to CakePHP’s important debug value. If a key is supplied, the data is returned. Using our examples from write() above, we can read that data back:
Configure::read('Company.name'); //yields: 'Pizza, Inc.'
Configure::read('Company.slogan'); //yields: 'Pizza for your body and soul'
Configure::read('Company');
//yields:
array('name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul');
Configure::read('Company.name'); //yields: 'Pizza, Inc.'Configure::read('Company.slogan'); //yields: 'Pizza for your body and soul'Configure::read('Company');//yields:array('name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul');
3.4.3.1.3 delete
delete(string $key)
Used to delete information from the application’s configuration.
Configure::delete('Company.name');
Configure::delete('Company.name');
3.4.3.1.4 load
load(string $path)
Use this method to load configuration information from a specific file.
// /app/config/messages.php:
<?php
$config['Company']['name'] = 'Pizza, Inc.';
$config['Company']['slogan'] = 'Pizza for your body and soul';
$config['Company']['phone'] = '555-55-55';
?>
<?php
Configure::load('messages');
Configure::read('Company.name');
?>
// /app/config/messages.php:<?php$config['Company']['name'] = 'Pizza, Inc.';$config['Company']['slogan'] = 'Pizza for your body and soul';$config['Company']['phone'] = '555-55-55';?><?phpConfigure::load('messages');Configure::read('Company.name');?>
Every configure key-value pair is represented in the file with the $config array. Any other variables in the file will be ignored by the load() function.
3.4.3.1.5 version
version()
Returns the CakePHP version for the current application.
3.4.3.2 CakePHP Core Configuration Variables
The Configure class is used to manage a set of core CakePHP configuration variables. These variables can be found in app/config/core.php. Below is a description of each variable and how it affects your CakePHP application.
| Configure Variable | Description |
|---|---|
| debug |
Changes CakePHP debugging output. 0 = Production mode. No output. 1 = Show errors and warnings. 2 = Show errors, warnings, and SQL. 3 = Show errors, warnings, SQL, and complete controller dump. |
| App.baseUrl | Un-comment this definition if you don’t plan to use Apache’s mod_rewrite with CakePHP. Don’t forget to remove your .htaccess files too. |
| Routing.admin | Un-comment this definition if you’d like to take advantage of CakePHP admin routes. Set this variable to the name of the admin route you’d like to use. More on this later. |
| Cache.disable | When set to true, caching is disabled site-wide. |
| Cache.check | If set to true, enables view caching. Enabling is still needed in the controllers, but this variable enables the detection of those settings. |
| Session.save |
Tells CakePHP which session storage mechanism to use. php = Use the default PHP session storage. cache = Use the caching engine configured by Cache::config(). Very useful in conjunction with Memcache (in setups with multiple application servers) to store both cached data and sessions. cake = Store session data in /app/tmp database = store session data in a database table. Make sure to set up the table using the SQL file located at /app/config/sql/sessions.sql. |
| Session.table | The name of the table (not including any prefix) that stores session information. |
| Session.database | The name of the database that stores session information. |
| Session.cookie | The name of the cookie used to track sessions. |
| Session.timeout | Base session timeout in seconds. Actual value depends on Security.level. |
| Session.start | Automatically starts sessions when set to true. |
| Session.checkAgent | When set to false, CakePHP sessions will not check to ensure the user agent does not change between requests. |
| Security.level |
The level of CakePHP security. The session timeout time defined in 'Session.timeout' is multiplied according to the settings here. Valid values: 'high' = x 10 'medium' = x 100 'low' = x 300 'high' and 'medium' also enable session.referer_check CakePHP session IDs are also regenerated between requests if 'Security.level' is set to 'high'. |
| Security.salt | A random string used in security hashing. |
| Acl.classname, Acl.database | Constants used for CakePHP’s Access Control List functionality. See the Access Control Lists chapter for more information. |
Cache configuration is also found in core.php — We’ll be covering that later on, so stay tuned.
The Configure class can be used to read and write core configuration settings on the fly. This can be especially handy if you want to turn the debug setting on for a limited section of logic in your application, for instance.
3.4.3.3 Configuration Constants
While most configuration options are handled by Configure, there are a few constants that CakePHP uses during runtime.
| Constant | Description |
|---|---|
| LOG_ERROR | Error constant. Used for differentiating error logging and debugging. Currently PHP supports LOG_DEBUG. |
3.4.4 The App Class
Loading additional classes has become more streamlined in CakePHP. In previous versions there were different functions for loading a needed class based on the type of class you wanted to load. These functions have been deprecated, all class and library loading should be done through App::import() now. App::import() ensures that a class is only loaded once, that the appropriate parent class has been loaded, and resolves paths automatically in most cases.
3.4.4.1 Using App::import()
App::import($type, $name, $parent, $search, $file, $return);
At first glance App::import seems complex, however in most use cases only 2 arguments are required.
3.4.4.2 Importing Core Libs
Core libraries such as Sanitize, and Xml can be loaded by:
App::import('Core', 'Sanitize'); App::import('Core', 'Sanitize');
The above would make the Sanitize class available for use.
3.4.4.3 Importing Controllers, Models, Components, Behaviors, and Helpers
All application related class should also be loaded with App::import(). The following examples illustrate how to do so.
3.4.4.3.1 Loading Controllers
App::import('Controller', 'MyController');
Calling App::import is equivalent to require'ing the file. It is important to realize that the class subsequently needs to be initialized.
<?php
// The same as require('controllers/users_controller.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();
?>
<?php// The same as require('controllers/users_controller.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();?>
3.4.4.3.2 Loading Models
App::import('Model', 'MyModel');
3.4.4.3.3 Loading Components
App::import('Component', 'Auth');
3.4.4.3.4 Loading Behaviors
App::import('Behavior', 'Tree');
3.4.4.3.5 Loading Helpers
App::import('Helper', 'Html');
3.4.4.4 Loading 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.
App::import('Model', 'PluginName.Comment'); App::import('Model', 'PluginName.Comment');
3.4.4.5 Loading Vendor Files
The vendor() function has been deprecated. Vendor files should now be loaded through App::import() as well. The syntax and additional arguments are slightly different, as vendor file structures can differ greatly, and not all vendor files contain classes.
The following examples illustrate how to load vendor files from a number of path structures. These vendor files could be located in any of the vendor folders.
3.4.4.5.1 Vendor examples
To load vendors/geshi.php
App::import('Vendor', 'geshi'); App::import('Vendor', 'geshi');
To load vendors/flickr/flickr.php
App::import('Vendor', 'flickr/flickr'); App::import('Vendor', 'flickr/flickr');
To load vendors/some.name.php
App::import('Vendor', 'SomeName', array('file' => 'some.name.php')); App::import('Vendor', 'SomeName', array('file' => 'some.name.php'));
To load vendors/services/well.named.php
App::import('Vendor', 'WellNamed', array('file' => 'services'.DS.'well.named.php')); App::import('Vendor', 'WellNamed', array('file' => 'services'.DS.'well.named.php'));
3.4.5 Routes Configuration
Routing is a feature that maps URLs to controller actions. It was added to CakePHP to make pretty URLs more configurable and flexible. Using Apache’s mod_rewrite is not required for using routes, but it will make your address bar look much more tidy.
As will be explained later, routes in CakePHP 1.2 have been expanded and are now very powerful.
3.4.5.1 Default Routing
Before you learn about configuring your own routes, you should know that CakePHP comes configured with a default set of routes. CakePHP’s default routing will get you pretty far in any application. You can access an action directly via the URL by putting its name in the request. You can also pass parameters to your controller actions using the URL.
URL pattern default routes:
http://example.com/controller/action/param1/param2/param3
The URL /posts/view maps to the view() action of the PostsController, and /products/view_clearance maps to the viewClearance() action of the ProductsController. If no action is specified in the URL, the index() method is assumed.
The default routing setup also allows you to pass parameters to your actions using the URL. A request for /posts/view/25 would be equivalent to calling view(25) on the PostsController, for example.
3.4.5.2 Named parameters
New in CakePHP 1.2 is the ability to use named parameters. You can name parameters and send their values using the URL. A request for /posts/view/title:first+post/category:general would result in a call to the view() action of the PostsController. In that action, you’d find the values of the title and category parameters inside $this->passedArgs[‘title’] and $this->passedArgs[‘category’] respectively.
Some summarizing examples for default routes might prove helpful.
URL to controller action mapping using default routes:
URL: /monkeys/jump
Mapping: MonkeysController->jump();
URL: /products
Mapping: ProductsController->index();
URL: /tasks/view/45
Mapping: TasksController->view(45);
URL: /donations/view/recent/2001
Mapping: DonationsController->view('recent', '2001');
URL: /contents/view/chapter:models/section:associations
Mapping: ContentsController->view();
$this->passedArgs['chapter'] = 'models';
$this->passedArgs['section'] = 'associations';
3.4.5.3 Defining Routes
Defining your own routes allows you to define how your application will respond to a given URL. Define your own routes in the /app/config/routes.php file using the Router::connect() method.
The connect() method takes up to three parameters: the URL you wish to match, the default values for your route elements, and regular expression rules to help the router match elements in the URL.
The basic format for a route definition is:
Router::connect(
'URL',
array('paramName' => 'defaultValue'),
array('paramName' => 'matchingRegex')
)
Router::connect('URL',array('paramName' => 'defaultValue'),array('paramName' => 'matchingRegex'))
The first parameter is used to tell the router what sort of URL you're trying to control. The URL is a normal slash delimited string, but can also contain a wildcard (*) or route elements (variable names prefixed with a colon). Using a wildcard tells the router what sorts of URLs you want to match, and specifying route elements allows you to gather parameters for your controller actions.
Once you've specified a URL, you use the last two parameters of connect() to tell CakePHP what to do with a request once it has been matched. The second parameter is an associative array. The keys of the array should be named after the route elements in the URL, or the default elements: :controller, :action, and :plugin. The values in the array are the default values for those keys. Let's look at some basic examples before we start using the third parameter of connect().
Router::connect(
'/pages/*',
array('controller' => 'pages', 'action' => 'display')
);
Router::connect('/pages/*',array('controller' => 'pages', 'action' => 'display'));
This route is found in the routes.php file distributed with CakePHP (line 40). This route matches any URL starting with /pages/ and hands it to the display() method of the PagesController(); The request /pages/products would be mapped to PagesController->display('products'), for example.
Router::connect(
'/government',
array('controller' => 'products', 'action' => 'display', 5)
);
Router::connect('/government',array('controller' => 'products', 'action' => 'display', 5));
This second example shows how you can use the second parameter of connect() to define default parameters. If you built a site that features products for different categories of customers, you might consider creating a route. This allows you link to /government rather than /products/display/5.
Another common use for the Router is to define an "alias" for a controller. Let's say that instead of accessing our regular URL at /users/someAction/5, we'd like to be able to access it by /cooks/someAction/5. The following route easily takes care of that:
Router::connect(
'/cooks/:action/*', array('controller' => 'users', 'action' => 'index')
);
Router::connect('/cooks/:action/*', array('controller' => 'users', 'action' => 'index'));
This is telling the Router that any url beginning with /cooks/ should be sent to the users controller.
When generating urls, routes are used too. Using array('controller' => 'users', 'action' => 'someAction', 5) as a url will output /cooks/someAction/5 if the above route is the first match found
If you are planning to use custom named arguments with your route, you have to make the router aware of it using the Router::connectNamed function. So if you want the above route to match urls like /cooks/someAction/type:chef we do:
Router::connectNamed(array('type'));
Router::connect(
'/cooks/:action/*', array('controller' => 'users', 'action' => 'index')
);
Router::connectNamed(array('type'));Router::connect('/cooks/:action/*', array('controller' => 'users', 'action' => 'index'));
You can specify your own route elements, doing so gives you the power to define places in the URL where parameters for controller actions should lie. When a request is made, the values for these route elements are found in $this->params of the controller. This is different than named parameters are handled, so note the difference: named parameters (/controller/action/name:value) are found in $this->passedArgs, whereas custom route element data is found in $this->params. When you define a custom route element, you also need to specify a regular expression - this tells CakePHP how to know if the URL is correctly formed or not.
Router::connect(
'/:controller/:id',
array('action' => 'view'),
array('id' => '[0-9]+')
);
Router::connect('/:controller/:id',array('action' => 'view'),array('id' => '[0-9]+'));
This simple example illustrates how to create a quick way to view models from any controller by crafting a URL that looks like /controllername/id. The URL provided to connect() specifies two route elements: :controller and :id. The :controller element is a CakePHP default route element, so the router knows how to match and identify controller names in URLs. The :id element is a custom route element, and must be further clarified by specifying a matching regular expression in the third parameter of connect(). This tells CakePHP how to recognize the ID in the URL as opposed to something else, such as an action name.
Once this route has been defined, requesting /apples/5 is the same as requesting /apples/view/5. Both would call the view() method of the ApplesController. Inside the view() method, you would need to access the passed ID at $this->params['id'].
One more example, and you'll be a routing pro.
Router::connect(
'/:controller/:year/:month/:day',
array('action' => 'index', 'day' => null),
array(
'year' => '[12][0-9]{3}',
'month' => '0[1-9]|1[012]',
'day' => '0[1-9]|[12][0-9]|3[01]'
)
);
Router::connect('/:controller/:year/:month/:day',array('action' => 'index', 'day' => null),array('year' => '[12][0-9]{3}','month' => '0[1-9]|1[012]','day' => '0[1-9]|[12][0-9]|3[01]'));
This is rather involved, but shows how powerful routes can really become. The URL supplied has four route elements. The first is familiar to us: it's a default route element that tells CakePHP to expect a controller name.
Next, we specify some default values. Regardless of the controller, we want the index() action to be called. We set the day parameter (the fourth element in the URL) to null to flag it as being optional.
Finally, we specify some regular expressions that will match years, months and days in numerical form. Note that parenthesis (grouping) are not supported in the regular expressions. You can still specify alternates, as above, but not grouped with parenthesis.
Once defined, this route will match /articles/2007/02/01, /posts/2004/11/16, and /products/2001/05 (as defined, the day parameter is optional as it has a default), handing the requests to the index() actions of their respective controllers, with he date parameters in $this->params.
3.4.5.4 Passing parameters to action
Assuming your action was defined like this and you want to access the arguments using $articleID instead of $this->params['id'], just add an extra array in the 3rd parameter of Router::connect().
// some_controller.php
function view($articleID = null, $slug = null) {
// some code here...
}
// routes.php
Router::connect(
// E.g. /blog/3-CakePHP_Rocks
'/blog/:id-:slug',
array('controller' => 'blog', 'action' => 'view'),
array(
// order matters since this will simply map ":id" to $articleID in your action
'pass' => array('id', 'slug'),
'id' => '[0-9]+'
)
);
// some_controller.phpfunction view($articleID = null, $slug = null) {// some code here...}// routes.phpRouter::connect(// E.g. /blog/3-CakePHP_Rocks'/blog/:id-:slug',array('controller' => 'blog', 'action' => 'view'),array(// order matters since this will simply map ":id" to $articleID in your action'pass' => array('id', 'slug'),'id' => '[0-9]+'));
And now, thanks to the reverse routing capabilities, you can pass in the url array like below and Cake will know how to form the URL as defined in the routes.
// view.ctp
// this will return a link to /blog/3-CakePHP_Rocks
<?php echo $html->link('CakePHP Rocks', array(
'controller' => 'blog',
'action' => 'view',
'id' => 3,
'slug' => Inflector::slug('CakePHP Rocks')
)); ?>
// view.ctp// this will return a link to /blog/3-CakePHP_Rocks<?php echo $html->link('CakePHP Rocks', array('controller' => 'blog','action' => 'view','id' => 3,'slug' => Inflector::slug('CakePHP Rocks'))); ?>
3.4.5.5 Prefix Routing
Many applications require an administration section where privileged users can make changes. This is often done through a special URL such as /admin/users/edit/5. In CakePHP, admin routing can be enabled from within the core configuration file by setting the admin path for Routing.admin.
Configure::write('Routing.admin', 'admin'); Configure::write('Routing.admin', 'admin');
In your controller, any action with an admin_ prefix will be called. Using our users example, accessing the url /admin/users/edit/5 would call the method admin_edit of our UsersController passing 5 as the first parameter.
You can map the url /admin to your admin_index action of pages controller using following route
Router::connect('/admin', array('controller' => 'pages', 'action' => 'index', 'admin' => true)); Router::connect('/admin', array('controller' => 'pages', 'action' => 'index', 'admin' => true));
You can configure the Router to use multiple prefixes too:
Router::connect('/profiles/:controller/:action/*', array('prefix' => 'profiles', 'profiles' => true)); Router::connect('/profiles/:controller/:action/*', array('prefix' => 'profiles', 'profiles' => true));
Any calls to the profiles section would look for the profiles_ prefix on the method calls. Our users example would have a URL structure that looks like /profiles/users/edit/5 would call the profiles_edit method within the UsersController. Also important to remember, using the HTML helper to build your links will help maintain the prefix calls. Here's how to built this link using the HTML helper:
echo $html->link('Edit your profile', array('profiles' => true, 'controller' => 'users', 'action' => 'edit', 'id' => 5)); echo $html->link('Edit your profile', array('profiles' => true, 'controller' => 'users', 'action' => 'edit', 'id' => 5));
You can set up multiple prefixed routes using this approach to create a flexible URL structure for your application.
3.4.6 Inflections
Cake's naming conventions can be really nice - you can name your database table big_boxes, your model BigBox, your controller BigBoxesController, and everything just works together automatically. The way CakePHP knows how to tie things together is by inflecting the words between their singular and plural forms.
There are occasions (especially for our non-English speaking friends) where you may run into situations where CakePHP's inflector (the class that pluralizes, singularizes, camelCases, and under_scores) might not work as you'd like. If CakePHP won't recognize your Foci or Fish, editing the inflections configuration file is where you can tell CakePHP about your special cases. This file is found in /app/config/inflections.php.
In this file, you will find six variables. Each allows you to fine-tune CakePHP inflection behavior.
| inflections.php Variable | Description |
|---|---|
| $pluralRules | This array contains regular expression rules for pluralizing special cases. The keys of the array are patterns, and the values are replacements. |
| $uninflectedPlural | An array containing words that do not need to be modified in order to be plural (mass nouns, etc.). |
| $irregularPlural | An array containing words and their plurals. The keys of the array contain the singular form, the values, plural forms. This array should be used to store words that don’t follow rules defined in $pluralRules. |
| $singularRules | Same as with $pluralRules, only this array holds rules that singularize words. |
| $uninflectedSingular | Same as with $uninflectedPlural, only this array holds words that have no singular form. This is set equal to $uninflectedPlural by default. |
| $irregularSingular | Same as with $irregularPlural, only with words in singular form. |
3.4.7 Bootstrapping CakePHP
If you have any additional configuration needs, use CakePHP’s bootstrap file, found in /app/config/bootstrap.php. This file is executed just after CakePHP’s core bootstrapping.
This file is ideal for a number of common bootstrapping tasks:
- Defining convenience functions
- Registering global constants
- Defining additional model, view, and controller paths
Be careful to maintain the MVC software design pattern when you add things to the bootstrap file: it might be tempting to place formatting functions there in order to use them in your controllers.
Resist the urge. You’ll be glad you did later on down the line.
You might also consider placing things in the AppController class. This class is a parent class to all of the controllers in your application. AppController is handy place to use controller callbacks and define methods to be used by all of your controllers.
3.5 Controllers
3.5.1 Introduction
A controller is used to manage the logic for a part of your application. Most commonly, controllers are used to manage the logic for a single model. For example, if you were building a site for an online bakery, you might have a RecipesController and a IngredientsController managing your recipes and their ingredients. In CakePHP, controllers are named after the model they handle, in plural form.
The Recipe model is handled by the RecipesController, the Product model is handled by the ProductsController, and so on.
Your application's controllers are classes that extend the CakePHP AppController class, which in turn extends a core Controller class. The AppController class can be defined in /app/controllers/app_controller.php and it should contain methods that are shared between all of your application’s controllers. It extends the Controller class which is a standard CakePHP library.
Controllers can include any number of methods which are usually referred to as actions. Actions are controller methods used to display views. An action is a single method of a controller.
CakePHP’s dispatcher calls actions when an incoming request matches a URL to a controller’s action (refer to "Routes Configuration" for an explanation on how controller actions and parameters are mapped from the URL).
Returning to our online bakery example, our RecipesController might contain the view(), share(), and search() actions. The controller would be found in /app/controllers/recipes_controller.php and contain:
<?php
# /app/controllers/recipes_controller.php
class RecipesController extends AppController {
function view($id) {
//action logic goes here..
}
function share($customer_id, $recipe_id) {
//action logic goes here..
}
function search($query) {
//action logic goes here..
}
}
?>
<?php# /app/controllers/recipes_controller.phpclass RecipesController extends AppController {function view($id) {//action logic goes here..}function share($customer_id, $recipe_id) {//action logic goes here..}function search($query) {//action logic goes here..}}?>
In order for you to use a controller effectively in your own application, we’ll cover some of the core attributes and methods provided by CakePHP’s controllers.
3.5.2 The App Controller
As stated in the introduction, the AppController class is the parent class to all of your application's controllers. AppController itself extends the Controller class included in the CakePHP core library. As such, AppController is defined in /app/app_controller.php like so:
<?php
class AppController extends Controller {
}
?>
<?phpclass AppController extends Controller {}?>
Controller attributes and methods created in your AppController will be available to all of your application's controllers. It is the ideal place to create code that is common to all of your controllers. Components (which you'll learn about later) are best used for code that is used in many (but not necessarily all) controllers.
While normal object-oriented inheritance rules apply, CakePHP also does a bit of extra work when it comes to special controller attributes, like the list of components or helpers used by a controller. In these cases, AppController value arrays are merged with child controller class arrays.
CakePHP merges the following variables from the AppController to your application's controllers:
- $components
- $helpers
- $uses
Please also remember to call AppController's callbacks within child controller callbacks for best results:
function beforeFilter(){
parent::beforeFilter();
}
function beforeFilter(){parent::beforeFilter();}
3.5.3 Controller Attributes
For a complete list of controller attributes and their descriptions visit the CakePHP API. Check out http://api.cakephp.org/class/controller.
3.5.3.1 $name
PHP4 users should start out their controller definitions using the $name attribute. 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 takes care of some PHP4 classname oddities and helps CakePHP resolve naming.
<?php
# $name controller attribute usage example
class RecipesController extends AppController {
var $name = 'Recipes';
}
?>
<?php# $name controller attribute usage exampleclass RecipesController extends AppController {var $name = 'Recipes';}?>
3.5.3.2 $components, $helpers and $uses
The next most often used controller attributes tell CakePHP what helpers, components, and models you’ll be using in conjunction with the current controller. Using these attributes make these MVC classes available to the controller as a class variable ($this->ModelName, for example).
Each controller has some of these classes available by default, so you may not need to configure your controller at all.
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 controller's model must also be included. This is illustrated in the example below.
The Html, Form, and Session Helpers are always available by default, as is the SessionComponent. To learn more about these classes, be sure to check out their respective sections later in this manual.
Let’s look at how to tell a CakePHP controller that you plan to use additional MVC classes.
<?php
class RecipesController extends AppController {
var $name = 'Recipes';
var $uses = array('Recipe', 'User');
var $helpers = array('Ajax');
var $components = array('Email');
}
?>
<?phpclass RecipesController extends AppController {var $name = 'Recipes';var $uses = array('Recipe', 'User');var $helpers = array('Ajax');var $components = array('Email');}?>
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.
If you do not wish to use a Model in your controller, set var $uses = null or var $uses = array(). This will allow you to use a controller without a need for a corresponding Model file.
3.5.3.3 Page-related Attributes: $layout and $pageTitle
A few attributes exist in CakePHP controllers that give you control over how your view is set inside of a layout.
The $layout attribute can be set to the name of a layout saved in /app/views/layouts. You specify a layout by setting $layout equal to the name of the layout file minus the .ctp extension. If this attribute has not been defined, CakePHP renders the default layout, default.ctp. If you haven’t defined one at /app/views/layouts/default.ctp, CakePHP’s core default layout will be rendered.
<?php
// Using $layout to define an alternate layout
class RecipesController extends AppController {
function quickSave() {
$this->layout = 'ajax';
}
}
?>
<?php// Using $layout to define an alternate layoutclass RecipesController extends AppController {function quickSave() {$this->layout = 'ajax';}}?>
You can also change the title of the page (that is located in the bar at the top of your browser) using $pageTitle. In order for this to work properly, your layout needs to include the $title_for_layout variable, at least between the <title> and </title> tags in the head of the HTML document.
<?php
// Using $pageTitle to define the page title
class RecipesController extends AppController {
function quickSave() {
$this->pageTitle = 'My search engine optimized title';
}
}
?>
<?php// Using $pageTitle to define the page titleclass RecipesController extends AppController {function quickSave() {$this->pageTitle = 'My search engine optimized title';}}?>
You can also set the page title from the view using $this->pageTitle (You must include the $this-> part.) This is recommended, as it better separates the logic from the layout and content. For a static page you must use $this->pageTitle in the view if you want a different title.
If $this->pageTitle is not set, a title will be automatically generated based on the controller name, or the view file name in the case of a static page.
3.5.3.4 The Parameters Attribute ($params)
Controller parameters are available at $this->params in your CakePHP controller. This variable is used to provide access to information about the current request. The most common usage of $this->params is to get access to information that has been handed to the controller via POST or GET operations.
3.5.3.4.1 form
$this->params['form']
Any POST data from any form is stored here, including information also found in $_FILES.
3.5.3.4.2 admin
$this->params['admin']
Is set to 1 if the current action was invoked via admin routing.
3.5.3.4.3 bare
$this->params['bare']
Stores 1 if the current layout is empty, 0 if not.
3.5.3.4.4 isAjax
$this->params['isAjax']
Stores 1 if the current request is an ajax call, 0 if not. This variable is only set if the RequestHandler Component is being used in the controller.
3.5.3.4.5 controller
$this->params['controller']
Stores the name of the current controller handling the request. For example, if the URL /posts/view/1 was requested, $this->params['controller'] would equal "posts".
3.5.3.4.6 action
$this->params['action']
Stores the name of the current action handling the request. For example, if the URL /posts/view/1 was requested, $this->params['action'] would equal "view".
3.5.3.4.7 pass
$this->params['pass']
Returns an array (numerically indexed) of URL parameters after the Action.
// URL: /posts/view/12/print/narrow
Array
(
[0] => 12
[1] => print
[2] => narrow
)
3.5.3.4.8 url
$this->params['url']
Stores the current URL requested, along with key-value pairs of get variables. For example, if the URL /posts/view/?var1=3&var2=4 was called, $this->params['url'] would contain:
[url] => Array
(
[url] => posts/view
[var1] => 3
[var2] => 4
)
3.5.3.4.9 data
$this->data
Used to handle POST data sent from the FormHelper forms to the controller.
// The FormHelper is used to create a form element:
$form->text('User.first_name');
// The FormHelper is used to create a form element:$form->text('User.first_name');
Which when rendered, looks something like:
<input name="data[User][first_name]" value="" type="text" />
When the form is submitted to the controller via POST, the data shows up in this->data
//The submitted first name can be found here: $this->data['User']['first_name'];
//The submitted first name can be found here:$this->data['User']['first_name'];
3.5.3.4.10 prefix
$this->params['prefix']
Set to the routing prefix. For example, this attribute would contain the string "admin" during a request to /admin/posts/someaction.
3.5.3.4.11 named
$this->params['named']
Stores any named parameters in the url query string in the form /key:value/. For example, if the URL /posts/view/var1:3/var2:4 was requested, $this->params['named'] would be an array containing:
[named] => Array
(
[var1] => 3
[var2] => 4
)
3.5.3.5 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.
The $cacheAction attribute aids in caching views, and the $paginate attribute is used to set pagination defaults for the controller. For more information on how to use these attributes, check out their respective sections later on in this manual.
3.5.3.6 persistModel
Stub. Update Me!
Used to create cached instances of models a controller uses. When set to true, all models related to the controller will be cached. This can increase performance in many cases.
3.5.4 Controller Methods
For a complete list of controller methods and their descriptions visit the CakePHP API. Check out http://api.cakephp.org/class/controller.
3.5.4.1 Interacting with Views
3.5.4.1.1 set
set(string $var, mixed $value)
The set() method is the main way to send data from your controller to your view. Once you've used set(), the variable can be accessed in your view.
<?php
//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.
<?php//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 first parameter. This can often be a quick way to assign a set of information to the view.
Array keys will be inflected before they are assigned to the view ('underscored_key' becomes 'underscoredKey', etc.):
<?php
$data = array(
'color' => 'pink',
'type' => 'sugar',
'base_price' => 23.95
);
//make $color, $type, and $basePrice
//available to the view:
$this->set($data);
?>
<?php$data = array('color' => 'pink','type' => 'sugar','base_price' => 23.95);//make $color, $type, and $basePrice//available to the view:$this->set($data);?>
3.5.4.1.2 render
render(string $action, string $layout, string $file)
The render() method is automatically called at the end of each requested controller action. This method performs all the view logic (using the data you’ve given in using the set() method), places the view inside its layout and serves it back to the end user.
The default view file used by render is determined by convention. If the search() action of the RecipesController is requested, the view file in /app/views/recipes/search.ctp will be rendered.
class RecipesController extends AppController {
...
function search() {
// Render the view in /views/recipes/search.ctp
$this->render();
}
...
} class RecipesController extends AppController {...function search() {// Render the view in /views/recipes/search.ctp$this->render();}...}
Although CakePHP will automatically call it (unless you’ve set $this->autoRender to false) after every action’s logic, you can use it to specify an alternate view file by specifying an action name in the controller using $action.
If $action starts with '/' it is assumed to be a view or element file relative to the /app/views folder. This allows direct rendering of elements, very useful in ajax calls.
// Render the element in /views/elements/ajaxreturn.ctp
$this->render('/elements/ajaxreturn');
// Render the element in /views/elements/ajaxreturn.ctp$this->render('/elements/ajaxreturn');
You can also specify an alternate view or element file using the third parameter, $file. When using $file, don't forget to utilize a few of CakePHP’s global constants (such as VIEWS).
The $layout parameter allows you to specify the layout the view is rendered in.
3.5.4.2 Flow Control
3.5.4.2.1 redirect
redirect(string $url, integer $status, boolean $exit)
The flow control method you’ll use most often is redirect(). This method takes its first 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.
function placeOrder() {
//Logic for finalizing order goes here
if($success) {
$this->redirect(array('controller' => 'orders', 'action' => 'thanks'));
} else {
$this->redirect(array('controller' => 'orders', 'action' => 'confirm'));
}
}
function placeOrder() {//Logic for finalizing order goes hereif($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');
$this->redirect('/orders/thanks'));$this->redirect('http://www.example.com');
You can also pass data to the action:
$this->redirect(array('action' => 'edit', $id));
$this->redirect(array('action' => 'edit', $id));
The second parameter of redirect() allows you to define 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());
$this->redirect($this->referer());
3.5.4.2.2 flash
flash(string $message, string $url, integer $pause)
Similarly, 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 first parameter should hold the message to be displayed, and the second parameter is a CakePHP-relative URL. CakePHP will display the $message for $pause seconds before forwarding the user on.
For in-page flash messages, be sure to check out SessionComponent’s setFlash() method.
3.5.4.3 Callbacks
CakePHP controllers come fitted with callbacks you can use to insert logic just before or after controller actions are rendered.
beforeFilter()
This function is executed before every action in the controller. It's a handy place to check for an active session or inspect user permissions.
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.
afterFilter()
Called after every controller action, and after rendering is complete. This is the last controller method to run.
CakePHP also supports callbacks related to scaffolding.
_beforeScaffold($method)
$method name of method called example index, edit, etc.
_afterScaffoldSave($method)
$method name of method called either edit or update.
_afterScaffoldSaveError($method)
$method name of method called either edit or update.
_scaffoldError($method)
$method name of method called example index, edit, etc.
3.5.4.4 Other Useful Methods
3.5.4.4.1 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.
3.5.4.4.2 referer
Returns the referring URL for the current request.
3.5.4.4.3 disableCache
Used to tell the user’s browser not to cache the results of the current request. This is different than view caching, covered in a later chapter.
3.5.4.4.4 postConditions
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 find 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 CakePHP’s Form- and HtmlHelpers to create a quick form based on the Order model. Then a controller action can use the data posted from that form to craft find conditions:
function index() {
$o = $this->Order->findAll($this->postConditions($this->data));
$this->set('orders', $o);
}
function index() {$o = $this->Order->findAll($this->postConditions($this->data));$this->set('orders', $o);}
If $this->data[‘Order’][‘destination’] equals “Old Towne Bakery”, postConditions converts that condition to an array compatible for use in a Model->findAll() method. In this case, array(“Order.destination” => “Old Towne Bakery”).
If you want use a different SQL operator between terms, supply them using the second parameter.
/*
Contents of $this->data
array(
'Order' => array(
'num_items' => '4',
'referrer' => 'Ye Olde'
)
)
*/
//Let’s get orders that have at least 4 items and contain ‘Ye Olde’
$o = $this->Order->findAll($this->postConditions(
$this->data,
array(
'num_items' => '>=',
'referrer' => 'LIKE'
)
));
/*Contents of $this->dataarray('Order' => array('num_items' => '4','referrer' => 'Ye Olde'))*///Let’s get orders that have at least 4 items and contain ‘Ye Olde’$o = $this->Order->findAll($this->postConditions($this->data,array('num_items' => '>=','referrer' => 'LIKE')));
The third parameter allows you to tell CakePHP what SQL boolean operator to use between the find conditions. String like ‘AND’, ‘OR’ and ‘XOR’ are all valid values.
Finally, if the last parameter is set to true, and the $op parameter is an array, fields not included in $op will not be included in the returned conditions.
3.5.4.4.5 paginate
This method is used for paginating results fetched by your models. You can specify page sizes, model find conditions and more. See the pagination section for more details on how to use paginate.
3.5.4.4.6 requestAction
requestAction(string $url, array $options)
This function calls a controller's 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.
You can use requestAction() to retrieve a fully rendered view by passing 'return' in the options: requestAction($url, array('return'));
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. Let's 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.
// controllers/comments_controller.php
class CommentsController extends AppController {
function latest() {
return $this->Comment->find('all', array('order' => 'Comment.created DESC', 'limit' => 10));
}
}
// controllers/comments_controller.phpclass CommentsController extends AppController {function latest() {return $this->Comment->find('all', array('order' => 'Comment.created DESC', 'limit' => 10));}}
If we now create a simple element to call that function:
// views/elements/latest_comments.ctp
$comments = $this->requestAction('/comments/latest');
foreach($comments as $comment) {
echo $comment['Comment']['title'];
}
// views/elements/latest_comments.ctp$comments = $this->requestAction('/comments/latest');foreach($comments as $comment) {echo $comment['Comment']['title'];}
We can then place that element anywhere at all to get the output using:
echo $this->element('latest_comments'); echo $this->element('latest_comments');
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 it's 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')); echo $this->element('latest_comments', array('cache'=>'+1 hour'));
The requestAction call will not be made while the cached element view file exists and is valid.
In addition, requestAction now takes array based cake style urls:
echo $this->requestAction(array('controller' => 'articles', 'action' => 'featured'), array('return')); 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 only merges the named args array into the Controller::params member array and does not place the named args in the key 'named'.
echo $this->requestAction('/articles/featured/limit:3');
echo $this->requestAction('/articles/view/5');
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)));
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)));
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->data and $this->params['form']. In addition to passing all required parameters, named and pass parameters must be done in the second array as seen above.
3.6 Components
3.6.1 Introduction
Components are packages of logic that are shared between controllers. If you find 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
Each of these core components are detailed in their own chapters. For now, we’ll show you how to create your own components. Creating components keeps controller code clean and allows you to reuse code between projects.
3.6.2 Configuring Components
Many of the core components require configuration. Some examples of components requiring configuration are Auth, Cookie and Email. Configuration for these components, and components in general is usually done in your Controller's beforeFilter() method.
function beforeFilter() {
$this->Auth->authorize = 'controller';
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Cookie->name = 'CookieMonster';
}
function beforeFilter() {$this->Auth->authorize = 'controller';$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');$this->Cookie->name = 'CookieMonster';}
Would be an example of configuring component variables in your controller's beforeFilter()
It's possible, however, that a component requires certain configuration options to be set before the controller's beforeFilter is run. To this end, some components allow configuration options be set in the $components array.
var $components = array('DebugKit.toolbar' => array('panels' => array('history', 'session'));
var $components = array('DebugKit.toolbar' => array('panels' => array('history', 'session'));
Consult the relevant documentation to determine what configuration options each component provides.
3.6.3 Creating Components
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 first step is to create a new component file and class. Create the file in /app/controllers/components/math.php. The basic structure for the component would look something like this:
<?php
class MathComponent extends Object {
function doComplexOperation($amount1, $amount2) {
return $amount1 + $amount2;
}
}
?>
<?phpclass MathComponent extends Object {function doComplexOperation($amount1, $amount2) {return $amount1 + $amount2;}}?>
3.6.3.1 Including Components in your Controllers
Once our component is finished, we can use it in the application's controllers by placing the component's name (minus the "Component" part) in the controller's $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 */
var $components = array('Math', 'Session');
/* Make the new component available at $this->Math,as well as the standard $this->Session */var $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 Component's initialize() method. These parameters can then be handled by the Component.
var $components = array( 'Math' => array( 'precision' => 2, 'randomGenerator' => 'srand' ), 'Session', 'Auth' );
var $components = array('Math' => array('precision' => 2,'randomGenerator' => 'srand'),'Session', 'Auth');
The above would pass the array containing precision and randomGenerator to MathComponent's initialize() method as the second parameter.
This syntax is not implemented by any of the Core Components at this time
3.6.3.2 MVC Class Access Within Components
Components feature a number of callbacks used by the parent controller class. Judicious use of these callbacks can make creating and using components much easier..
initialize(&$controller, $settings=array())
The initialize method is called before the controller's beforeFilter method.
startup(&$controller)
The startup method is called after the controller's beforeFilter method but before the controller executes the current action handler.
beforeRender(&$controller)
The beforeRender method is called after the controller's beforeRender method but before the controller's renders views and layout.
shutdown(&$controller)
The shutdown method is called before output is sent to browser.
beforeRedirect(&$controller, $url, $status=null, $exit=true)
The beforeRedirect method is invoked when the controller's redirect method is called but before any further action. If this method returns false the controllerwill not continue on to redirect the request. The $url, $status and $exit variables have same meaning as for the controller's method.
Here is a skeleton component you can use as a template for your own custom components.
<?php
class SkeletonComponent extends Object {
//called before Controller::beforeFilter()
function initialize(&$controller, $settings = array()) {
// saving the controller reference for later use
$this->controller =& $controller;
}
//called after Controller::beforeFilter()
function startup(&$controller) {
}
//called after Controller::beforeRender()
function beforeRender(&$controller) {
}
//called after Controller::render()
function shutdown(&$controller) {
}
//called before Controller::redirect()
function beforeRedirect(&$controller, $url, $status=null, $exit=true) {
}
function redirectSomewhere($value) {
// utilizing a controller method
$this->controller->redirect($value);
}
}
?> <?phpclass SkeletonComponent extends Object {//called before Controller::beforeFilter()function initialize(&$controller, $settings = array()) {// saving the controller reference for later use$this->controller =& $controller;}//called after Controller::beforeFilter()function startup(&$controller) {}//called after Controller::beforeRender()function beforeRender(&$controller) {}//called after Controller::render()function shutdown(&$controller) {}//called before Controller::redirect()function beforeRedirect(&$controller, $url, $status=null, $exit=true) {}function redirectSomewhere($value) {// utilizing a controller method$this->controller->redirect($value);}}?>
You might also want to utilize other components inside a custom component. To do so, just create a $components class variable (just like you would in a controller) as an array that holds the names of components you wish to utilize.
<?php
class MyComponent extends Object {
// This component uses other components
var $components = array('Session', 'Math');
function doStuff() {
$result = $this->Math->doComplexOperation(1, 2);
$this->Session->write('stuff', $result);
}
}
?> <?phpclass MyComponent extends Object {// This component uses other componentsvar $components = array('Session', 'Math');function doStuff() {$result = $this->Math->doComplexOperation(1, 2);$this->Session->write('stuff', $result);}}?>
To access/use a model in a component is not generally recommended; If you end up needing one, you'll need to instantiate your model class and use it manually. Here's an example:
<?php
class MathComponent extends Object {
function doComplexOperation($amount1, $amount2) {
return $amount1 + $amount2;
}
function doReallyComplexOperation ($amount1, $amount2) {
$userInstance = ClassRegistry::init('User');
$totalUsers = $userInstance->find('count');
return ($amount1 + $amount2) / $totalUsers;
}
}
?> <?phpclass MathComponent extends Object {function doComplexOperation($amount1, $amount2) {return $amount1 + $amount2;}function doReallyComplexOperation ($amount1, $amount2) {$userInstance = ClassRegistry::init('User');$totalUsers = $userInstance->find('count');return ($amount1 + $amount2) / $totalUsers;}}?>
3.6.3.3 Using other Components in your Component
Sometimes one of your components may need to use another.
You can include other components in your component the exact same way you include them in controllers: Use the $components var.
<?php
class CustomComponent extends Object {
var $name = "Custom"; // the name of your component
var $components = array( "Existing" ); // the other component your component uses
function initialize(&$controller) {
$this->Existing->foo();
}
function bar() {
// ...
}
}
?> <?phpclass CustomComponent extends Object {var $name = "Custom"; // the name of your componentvar $components = array( "Existing" ); // the other component your component usesfunction initialize(&$controller) {$this->Existing->foo();}function bar() {// ...}}?>
<?php
class ExistingComponent extends Object {
var $name = "Existing";
function initialize(&$controller) {
$this->Parent->bar();
}
function foo() {
// ...
}
}
?> <?phpclass ExistingComponent extends Object {var $name = "Existing";function initialize(&$controller) {$this->Parent->bar();}function foo() {// ...}}?>
3.7 Models
Models represent data and are used in CakePHP applications for data access. A model usually represents a database table but can be used to access anything that stores data such as files, LDAP records, iCal events, or rows in a CSV file.
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. It'll explain the different ways to associate your data. It'll describe how to find, save, and delete data. Finally, it'll look at Datasources.
3.7.1 Understanding Models
A Model represents your data model and in object-oriented programming 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 definition in CakePHP:
<?php
class Ingredient extends AppModel {
var $name = 'Ingredient';
}
?>
<?phpclass Ingredient extends AppModel {var $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 CakePHP's Model class by the magic of inheritance. The Ingredient model extends the application model, AppModel, which extends CakePHP's internal Model class. It is this core Model class that bestows the functionality onto your Ingredient model.
This intermediate class, AppModel, is empty and if you haven't created your own is taken from within the /cake/ folder. Overriding the AppModel allows you to define functionality that should be made available to all models within your application. To do so, you need to create your own app_model.php file that resides in the root of the /app/ folder. Creating a project using Bake will automatically generate this file for you.
Create your model PHP file in the /app/models/ directory or in a subdirectory of /app/models. CakePHP will find it anywhere in the directory. By convention it should have the same name as the class; for this example ingredient.php.
CakePHP will dynamically create a model object for you if it cannot find a corresponding file in /app/models. This also means that if your model file isn't named correctly (i.e. Ingredient.php or ingredients.php) CakePHP will use a instance of AppModel rather than your awol (from CakePHP's perspective) model file. If you're trying to use a method you've defined in your model, or a behavior attached to your model and you're getting SQL errors that are the name of the method you're calling - it's a sure sign CakePHP can't find your model and you either need to check the file names, clear your tmp files, or both.
See also Behaviors for more information on how to apply similar logic to multiple models.
The $name property is necessary for PHP4 but optional for PHP5.
With your model defined, 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.
<?php
class IngredientsController extends AppController {
function index() {
//grab all ingredients and pass it to the view:
$ingredients = $this->Ingredient->find('all');
$this->set('ingredients', $ingredients);
}
}
?>
<?phpclass IngredientsController extends AppController {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.
<?php
class RecipesController extends AppController {
function index() {
$ingredients = $this->Recipe->Ingredient->find('all');
$this->set('ingredients', $ingredients);
}
}
?>
<?phpclass RecipesController extends AppController {function index() {$ingredients = $this->Recipe->Ingredient->find('all');$this->set('ingredients', $ingredients);}}?>
If models have absolutely NO association between them, you can use the ClassRegistry class to get the model.
<?php
class RecipesController extends AppController {
function index() {
$recipes = $this->Recipe->find('all');
$this->Car =& ClassRegistry::init('Car');
$cars = $this->Car->find('all');
$this->set(compact('recipes', 'cars'));
}
}
?>
<?phpclass RecipesController extends AppController {function index() {$recipes = $this->Recipe->find('all');$this->Car =& ClassRegistry::init('Car');$cars = $this->Car->find('all');$this->set(compact('recipes', 'cars'));}}?>
3.7.2 Creating Database Tables
While CakePHP can have datasources that aren't 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, they'll automatically map to the tables that you've created.
Table names are by convention lowercase and pluralized with multi-word 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 field and uses this information to automate various features such as outputting form fields in the view.
Field names are by convention lowercase and separated by underscores.
Model to table name associations can be overridden with the useTable attribute of the model explained later in this chapter.
In the rest of this section, you'll see how CakePHP maps database field types to PHP data types and how CakePHP can automate tasks based on how your fields are defined.
3.7.2.1 Data Type Associations by Database
Every RDBMS defines data types in slightly different ways. Within the datasource class for each database system, CakePHP maps those types to something it recognizes and creates a unified interface, no matter which database system you need to run on.
This breakdown describes how each one is mapped.
3.7.2.1.1 MySQL
| CakePHP Type | Field Properties |
|---|---|
| primary_key | NOT NULL auto_increment |
| string | varchar(255) |
| text | text |
| integer | int(11) |
| float | float |
| datetime | datetime |
| timestamp | datetime |
| time | time |
| date | date |
| binary | blob |
| boolean | tinyint(1) |
3.7.2.1.2 MySQLi
| CakePHP Type | Field Properties |
|---|---|
| primary_key | DEFAULT NULL auto_increment |
| string | varchar(255) |
| text | text |
| integer | int(11) |
| float | float |
| datetime | datetime |
| timestamp | datetime |
| time | time |
| date | date |
| binary | blob |
| boolean | tinyint(1) |
3.7.2.1.3 ADOdb
| CakePHP Type | Field Properties |
|---|---|
| primary_key | R(11) |
| string | C(255) |
| text | X |
| integer | I(11) |
| float | N |
| datetime | T (Y-m-d H:i:s) |
| timestamp | T (Y-m-d H:i:s) |
| time | T (H:i:s) |
| date | T (Y-m-d) |
| binary | B |
| boolean | L(1) |
3.7.2.1.4 DB2
| CakePHP Type | Field Properties |
|---|---|
| primary_key | not null generated by default as identity (start with 1, increment by 1) |
| string | varchar(255) |
| text | clob |
| integer | integer(10) |
| float | double |
| datetime | timestamp (Y-m-d-H.i.s) |
| timestamp | timestamp (Y-m-d-H.i.s) |
| time | time (H.i.s) |
| date | date (Y-m-d) |
| binary | blob |
| boolean | smallint(1) |
3.7.2.1.5 Firebird/Interbase
| CakePHP Type | Field Properties |
|---|---|
| primary_key | IDENTITY (1, 1) NOT NULL |
| string | varchar(255) |
| text | BLOB SUB_TYPE 1 SEGMENT SIZE 100 CHARACTER SET NONE |
| integer | integer |
| float | float |
| datetime | timestamp (d.m.Y H:i:s) |
| timestamp | timestamp (d.m.Y H:i:s) |
| time | time (H:i:s) |
| date | date (d.m.Y) |
| binary | blob |
| boolean | smallint |
3.7.2.1.6 MS SQL
| CakePHP Type | Field Properties |
|---|---|
| primary_key | IDENTITY (1, 1) NOT NULL |
| string | varchar(255) |
| text | text |
| integer | int |
| float | numeric |
| datetime | datetime (Y-m-d H:i:s) |
| timestamp | timestamp (Y-m-d H:i:s) |
| time | datetime (H:i:s) |
| date | datetime (Y-m-d) |
| binary | image |
| boolean | bit |
3.7.2.1.7 Oracle
| CakePHP Type | Field Properties |
|---|---|
| primary_key | number NOT NULL |
| string | varchar2(255) |
| text | varchar2 |
| integer | numeric |
| float | float |
| datetime | date (Y-m-d H:i:s) |
| timestamp | date (Y-m-d H:i:s) |
| time | date (H:i:s) |
| date | date (Y-m-d) |
| binary | bytea |
| boolean | boolean |
| number | numeric |
| inet | inet |
3.7.2.1.8 PostgreSQL
| CakePHP Type | Field Properties |
|---|---|
| primary_key | serial NOT NULL |
| string | varchar(255) |
| text | text |
| integer | integer |
| float | float |
| datetime | timestamp (Y-m-d H:i:s) |
| timestamp | timestamp (Y-m-d H:i:s) |
| time | time (H:i:s) |
| date | date (Y-m-d) |
| binary | bytea |
| boolean | boolean |
| number | numeric |
| inet | inet |
3.7.2.1.9 SQLite
| CakePHP Type | Field Properties |
|---|---|
| primary_key | integer primary key |
| string | varchar(255) |
| text | text |
| integer | integer |
| float | float |
| datetime | datetime (Y-m-d H:i:s) |
| timestamp | timestamp (Y-m-d H:i:s) |
| time | time (H:i:s) |
| date | date (Y-m-d) |
| binary | blob |
| boolean | boolean |
3.7.2.1.10 Sybase
| CakePHP Type | Field Properties |
|---|---|
| primary_key | numeric(9,0) IDENTITY PRIMARY KEY |
| string | varchar(255) |
| text | text |
| integer | int(11) |
| float | float |
| datetime | datetime (Y-m-d H:i:s) |
| timestamp | timestamp (Y-m-d H:i:s) |
| time | datetime (H:i:s) |
| date | datetime (Y-m-d) |
| binary | image |
| boolean | bit |
3.7.2.2 Titles
An object, in the physical sense, often has a name or a title that refers to it. A person has a name like John or Mac or Buddy. A blog post has a title. A category has a name.
By specifying a title or name field, CakePHP will automatically use this label in various circumstances:
- Scaffolding — page titles, fieldset labels
- Lists — normally used for
<select>drop-downs - TreeBehavior — reordering, tree views
If you have a title and name field in your table, the title will be used.
3.7.2.3 created and modified
By defining a created or modified field in your database table as datetime fields, CakePHP will recognize those fields 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 fields).
The created and modified fields will be set to the current date and time when the record is initially added. The modified field will be updated with the current date and time whenever the existing record is saved.
Note: A field named updated will exhibit the same behavior as modified. These fields need to be datetime fields with the default value set to NULL to be recognized by CakePHP.
3.7.2.4 Using UUIDs as Primary Keys
Primary keys are normally defined as INT fields. The database will automatically increment the field, starting at 1, for each new record that gets added. Alternatively, if you specify your primary key as a CHAR(36) or BINARY(36), CakePHP will automatically generate UUIDs when new records are created.
A UUID is a 32 byte string separated by four hyphens, for a total of 36 characters. For example:
550e8400-e29b-41d4-a716-446655440000
UUIDs are designed to be unique, not only within a single table, but also across tables and databases. If you require a field to remain unique across systems then UUIDs are a great approach.
3.7.3 Retrieving Your Data
3.7.3.1 find
find($type, $params)
Find is the multifunctional workhorse of all model data-retrieval functions. $type can be either 'all', 'first', 'count', 'list', 'neighbors' or 'threaded'. The default find type is 'first'.
$params is used to pass all parameters to the various finds, 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 order
'group' => array('Model.field'), //fields to GROUP BY
'limit' => n, //int
'page' => n, //int
'callbacks' => true //other possible values are false, 'before', 'after'
) 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 order'group' => array('Model.field'), //fields to GROUP BY'limit' => n, //int'page' => n, //int'callbacks' => true //other possible values are false, 'before', 'after')
It's also possible to add and use other parameters, as is made use of by some find types, behaviors and of course possible with your own model methods
More information about model callbacks is available here
3.7.3.1.1 find('first')
find('first', $params)
'first' is the default find type, and will return one result, you'd use this for any use where you expect only one result. Below are a couple of simple (controller code) examples:
function some_function() {
...
$this->Article->order = null; // resetting if it's set
$semiRandomArticle = $this->Article->find();
$this->Article->order = 'Article.created DESC'; // simulating the model having a default order
$lastCreated = $this->Article->find();
$alsoLastCreated = $this->Article->find('first', array('order' => array('Article.created DESC')));
$specificallyThisOne = $this->Article->find('first', array('conditions' => array('Article.id' => 1)));
...
}
function some_function() {...$this->Article->order = null; // resetting if it's set$semiRandomArticle = $this->Article->find();$this->Article->order = 'Article.created DESC'; // simulating the model having a default order$lastCreated = $this->Article->find();$alsoLastCreated = $this->Article->find('first', array('order' => array('Article.created DESC')));$specificallyThisOne = $this->Article->find('first', array('conditions' => array('Article.id' => 1)));...}
In the first example, no parameters at all are passed to find - 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
[field2] => value2
[field3] => value3
)
[AssociatedModelName] => Array
(
[id] => 1
[field1] => value1
[field2] => value2
[field3] => value3
)
)
There are no additional parameters used by find('first').
3.7.3.1.2 find('count')
find('count', $params)
find('count', $params) returns an integer value. Below are a couple of simple (controller code) examples:
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')
));
...
}
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')));...}
Don't pass fields as an array to find('count'). You would only need to specify fields for a DISTINCT count (since otherwise, the count is always the same - dictated by the conditions).
There are no additional parameters used by find('count').
3.7.3.1.3 find('all')
find('all', $params)
find('all') 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:
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');
$allPublishedAuthors = $this->Article->User->find('all', array('conditions' => array('Article.status !=' => 'pending')));
...
}
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');$allPublishedAuthors = $this->Article->User->find('all', array('conditions' => array('Article.status !=' => 'pending')));...}
In the above example $allAuthors will contain every user in the users table, there will be no condition applied to the find 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
)
)
)
There are no additional parameters used by find('all').
3.7.3.1.4 find('list')
find('list', $params)
find('list', $params) returns an indexed array, useful for any use where you would want a list such as for populating input select boxes. Below are a couple of simple (controller code) examples:
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->User->find('list', array('conditions' => array('Article.status !=' => 'pending')));
...
}
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->User->find('list', array('conditions' => array('Article.status !=' => 'pending')));...}
In the above example $allAuthors will contain every user in the users table, there will be no condition applied to the find as none were passed.
The results of a call to find('list') will be in the following form:
Array
(
//[id] => 'displayValue',
[1] => 'displayValue1',
[2] => 'displayValue2',
[4] => 'displayValue4',
[5] => 'displayValue5',
[6] => 'displayValue6',
[3] => '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 field is used for the value. Some further examples to clarify:.
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'));
...
}
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
(
//[username] => 'firstname',
['AD7six'] => 'Andy',
['_psychic_'] => 'John',
['PHPNut'] => 'Larry',
['gwoo'] => 'Gwoo',
['jperras'] => 'Joël',
)
$usernameGroups = Array
(
['Uber'] => Array
(
['PHPNut'] => 'Larry',
['gwoo'] => 'Gwoo',
)
['Admin'] => Array
(
['_psychic_'] => 'John',
['AD7six'] => 'Andy',
['jperras'] => 'Joël',
)
)
3.7.3.1.5 find('threaded')
find('threaded', $params)
find('threaded', $params) returns a nested array, and is appropriate if you want to use the parent_id field of your model data to build nested results. Below are a couple of simple (controller code) examples:
function some_function() {
...
$allCategories = $this->Category->find('threaded');
$aCategory = $this->Category->find('first', array('conditions' => array('parent_id' => 42)); // not the root
$someCategories = $this->Category->find('threaded', array(
'conditions' => array(
'Article.lft >=' => $aCategory['Category']['lft'],
'Article.rght <=' => $aCategory['Category']['rght']
)
));
...
}
function some_function() {...$allCategories = $this->Category->find('threaded');$aCategory = $this->Category->find('first', array('conditions' => array('parent_id' => 42)); // not the root$someCategories = $this->Category->find('threaded', array('conditions' => array('Article.lft >=' => $aCategory['Category']['lft'],'Article.rght <=' => $aCategory['Category']['rght'])));...}
It is not necessary to use the Tree behavior to use this method - but all desired results must be possible to be found in a single query.
In the above code example, $allCategories will contain a nested array representing the whole category structure. The second example makes use of the data structure used by the Tree behavior the return a partial, nested, result for $aCategory and everything below it. The results of a call to find('threaded') will be of the following form:
Array
(
[0] => Array
(
[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 influence 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 first.
There are no additional parameters used by find('threaded').
3.7.3.1.6 find('neighbors')
find('neighbors', $params)
'neighbors' will perform a find similar to 'first', but will return the row before and after the one you request. Below is a simple (controller code) example:
function some_function() {
$neighbors = $this->Article->find('neighbors', array('field' => 'id', 'value' => 3));
}
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: field and value. Other elements are still allowed as with any other find (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
(
[id] => 122
[field1] => value1
[field2] => value2
...
)
)
)
Note how the result always contains only two root elements: prev and next.
3.7.3.2 findAll
findAll(string $conditions, array $fields, string $order, int $limit, int $page, int $recursive)
findAll has been deprecated, use find('all') instead.
Returns the specified fields up to $limit records matching $conditions (if any), start listing from page $page (default is page 1). If there are no matching fields, an empty array is returned.
The $conditions should be formed just as they would in an SQL statement: $conditions = "Pastry.type LIKE '%cake%' AND Pastry.created_on > '2007-01-01'", for example. Prefixing conditions with the model's name ('Pastry.type' rather than just 'type') is always a good practice, especially when associated data is being fetched in a query.
Setting the $recursive parameter to an integer forces findAll() to fetch data according to the behavior described in the Model Attributes $recursive section outlined earlier. Do not forget to manually add the required foreign key columns to the $fields array as described there.
Data from findAll() is returned in an array, following this basic format:
Array
(
[0] => Array
(
[ModelName] => Array
(
[id] => 83
[field1] => value1
[field2] => value2
[field3] => value3
)
[AssociatedModelName] => Array
(
[id] => 1
[field1] => value1
[field2] => value2
[field3] => value3
)
)
[1] => Array
(
[ModelName] => Array
(
[id] => 85
[field1] => value1
[field2] => value2
[field3] => value3
)
[AssociatedModelName] => Array
(
[id] => 2
[field1] => value1
[field2] => value2
[field3] => value3
)
)
)
3.7.3.3 findAllBy
findAllBy<fieldName>(string $value)
These magic functions can be used as a shortcut to search your tables by a certain field. Just add the name of the field (in CamelCase format) to the end of these functions, and supply the criteria for that field as the first parameter.
| PHP5 findAllBy<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->findById(7); | Cake.id = 7 |
| $this->User->findByUserName(‘psychic’); | User.user_name = ‘psychic’ |
PHP4 users have to use this function a little differently due to some case-insensitivity in PHP4:
| PHP4 findAllBy<x> Example | Corresponding SQL Fragment |
|---|---|
| $this->Product->findAllByOrder_status(‘3’); | Product.order_status = 3 |
| $this->Recipe->findAllByType(‘Cookie’); | Recipe.type = ‘Cookie’ |
| $this->User->findAllByLast_name(‘Anderson’); | User.last_name = ‘Anderson’ |
| $this->Cake->findById(7); | Cake.id = 7 |
| $this->User->findByUser_name(‘psychic’); | User.user_name = ‘psychic’ |
findBy() functions like find('first',...), while findAllBy() functions like find('all',...).
In either case, the returned result is an array formatted just as it would be from find() or findAll(), respectively.
3.7.3.4 findBy
findBy<fieldName>(string $value)
These magic functions can be used as a shortcut to search your tables by a certain field. Just add the name of the field (in CamelCase format) to the end of these functions, and supply the criteria for that field as the first parameter.
| PHP5 findAllBy<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->findById(7); | Cake.id = 7 |
| $this->User->findByUserName(‘psychic’); | User.user_name = ‘psychic’ |
PHP4 users have to use this function a little differently due to some case-insensitivity in PHP4:
| PHP4 findAllBy<x> Example | Corresponding SQL Fragment |
|---|---|
| $this->Product->findAllByOrder_status(‘3’); | Product.order_status = 3 |
| $this->Recipe->findAllByType(‘Cookie’); | Recipe.type = ‘Cookie’ |
| $this->User->findAllByLast_name(‘Anderson’); | User.last_name = ‘Anderson’ |
| $this->Cake->findById(7); | Cake.id = 7 |
| $this->User->findByUser_name(‘psychic’); | User.user_name = ‘psychic’ |
findBy() functions like find('first',...), while findAllBy() functions like find('all',...).
In either case, the returned result is an array formatted just as it would be from find() or findAll(), respectively.
3.7.3.5 findNeighbours
findNeighbours(string $conditions, mixed $field, string $value)
findNeighbours has been deprecated, use find('neighbors') instead.
This shortcut method creates an array containing values helpful in generating 'Previous' and 'Next' links in a view.
The method determines which data rows to return based on the values submitted in the $field and $value parameters. Further refinement can be done with the $conditions parameter.
For example, if you call the function like this:
$conditions = array('Article.status' => 'published');
$field = array('date', 'id');
$value = '2008-03-24';
$this->Article->findNeighbours( $conditions, $field, $value ) );
$conditions = array('Article.status' => 'published');$field = array('date', 'id');$value = '2008-03-24';$this->Article->findNeighbours( $conditions, $field, $value ) );
The resulting array will contain values for the 'date' and 'id' fields from the articles who have a status of "published", and whose dates are just before and after the date '2008-03-24'.
Array
(
[prev] => Array ([Article] =>
Array ([date] => 2008-03-20, [id] => 99 )
),
[next] => Array ( [Article] =>
Array( [date] => 2008-03-27, [id] => 15 )
)
);
Note that the comparison was made on date field, and that the id values were not used to determine neighboring data.
This method can also be called with the $field value being a single string. When an array is used, the first field listed will be the field used in the comparison query.
class ImagesController extends AppController {
function view($id) {
// Say we want to be able to show the image...
$this->set('image', $this->Image->findById($id);
// But we also want links to the previous and next images...
$this->set(
'neighbors',
$this->Image->findNeighbours(null, 'id', $id);
)
}
}
class ImagesController extends AppController {function view($id) {// Say we want to be able to show the image...$this->set('image', $this->Image->findById($id);// But we also want links to the previous and next images...$this->set('neighbors',$this->Image->findNeighbours(null, 'id', $id);)}}
This gives us the full $image['Image'] array, along with $neighbors['prev']['Image']['id'] and $neighbors['next']['Image']['id'] for use in the view.
3.7.3.6 query
query(string $query)
SQL calls that you can't or don't want to make via other model methods (careful - there are very few circumstances this is true) can be made using the model's query() method.
If you’re ever using this method in your application, be sure to check out CakePHP’s Sanitize library, which aids in cleaning up user-provided data from injection and cross-site scripting attacks.
query() does not honour $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;");
$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
)
)
)
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;");
$this->Picture->query("SELECT * FROM pictures AS Picture LIMIT 2;");
which returns
Array
(
[0] => Array
(
[Picture] => Array
(
[id] => 1304
[user_id] => 759
)
)
[1] => Array
(
[Picture] => Array
(
[id] => 1305
[user_id] => 759
)
)
)
Array([0] => Array([Picture] => Array([id] => 1304[user_id] => 759))[1] => Array([Picture] => Array([id] => 1305[user_id] => 759)))
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.
3.7.3.7 generateList
generateList(string $conditions, string $order, int $limit, string $keyPath, string $valuePath)
generateList is deprecated and replaced by usage of find('list'), or find('all') combined with a call to Set::combine().
This function is a shortcut to getting a list of key/value pairs - especially handy for creating an HTML select tag from a list of your models. Use the $conditions, $order, and $limit parameters just as you would for a findAll() request.
If $primaryKey and $displayField have been set in the model, you don’t need to supply the last two parameters, as they act as $keyPath and $keyValue, respectively. Additionally, if neither $keyPath nor $displayField have been supplied, CakePHP will try to load the information using ‘title’ or ‘name’.
The $keyPath and $valuePath specify where to find the keys and values for your generated list. For example, if you wanted to generate a list of roles based on your Role model, keyed by their integer ids, the full call might look something like:
$this->Role->generateList(
null,
'role_name ASC',
null,
'{n}.Role.id',
'{n}.Role.role_name'
);
//This would return something like:
array(
'1' => 'Head Honcho',
'2' => 'Marketing',
'3' => 'Department Head',
'4' => 'Grunt'
);
$this->Role->generateList(null,'role_name ASC',null,'{n}.Role.id','{n}.Role.role_name');//This would return something like:array('1' => 'Head Honcho','2' => 'Marketing','3' => 'Department Head','4' => 'Grunt');
Many people are a little bewildered by the ‘{n}’ syntax used by generateList(). Fret not, for it serves as a place holder for switching between model DataSources, covered later on in this chapter.
3.7.3.8 findCount
findCount(string $conditions, int $recursive)
This method has been deprecated, use find('count').
Returns the number of records that match the given conditions. Use the $recursive parameter to have CakePHP fetch more (or fewer) levels of associated models.
3.7.3.9 field
field(string $name, array $conditions = null, string $order = null)
Returns the value of a single field, specified as $name, from the first record matched by $conditions as ordered by $order. If no conditions are passed and the model id is set, will return the field value for the current model result. If no matching record is found returns false.
$model->id = 22;
echo $model->field('name'); // echo the name for row id 22
echo $model->field('name', array('created <' => date('Y-m-d H:i:s')), 'created DESC'); // echo the name of the last created instance
$model->id = 22;echo $model->field('name'); // echo the name for row id 22echo $model->field('name', array('created <' => date('Y-m-d H:i:s')), 'created DESC'); // echo the name of the last created instance
3.7.3.10 Complex Find Conditions
Most of the model's find calls involve passing sets of conditions in one way or another. The simplest approach to this is to use a WHERE clause snippet of SQL. If you find yourself needing more control, you can use arrays.
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 (fields, values, operators, etc.) into discrete, manipulatable parts. This allows CakePHP to generate the most efficient query possible, ensure proper SQL syntax, and properly escape each individual part of the query.
At it's most basic, an array-based query looks like this:
$conditions = array("Post.title" => "This is a post");
//Example usage with a model:
$this->Post->find($conditions);
$conditions = array("Post.title" => "This is a post");//Example usage with a model:$this->Post->find($conditions);
The structure here is fairly self-explanatory: it will find any post where the title equals "This is a post". Note that we could have used just "title" as the field 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. Let's say we wanted to find all the posts where the title is not "This is a post":
array("Post.title <>" => "This is a post")
array("Post.title <>" => "This is a post")
Notice the '<>' that follows the field 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 field name and the operator. The one exception here is IN (...)-style matches. Let's say you wanted to find posts where the title was in a given set of values:
array(
"Post.title" => array("First post", "Second post", "Third post")
)
array("Post.title" => array("First post", "Second post", "Third post"))
To do a NOT IN(...) match to find posts where the title is not in the given set of values:
array(
"NOT" => array( "Post.title" => array("First post", "Second post", "Third post") )
)
array("NOT" => array( "Post.title" => array("First post", "Second post", "Third post") ))
Adding additional filters to the conditions is as simple as adding additional key/value pairs to the array:
array (
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created >" => date('Y-m-d', strtotime("-2 weeks"))
)
array ("Post.title" => array("First post", "Second post", "Third post"),"Post.created >" => date('Y-m-d', strtotime("-2 weeks")))
You can also create finds that compare two fields in the database
array("Post.created = Post.modified")
array("Post.created = Post.modified")
This above example will return posts where the created date is equal to the modified date (ie it will return posts that have never been modified).
Remember that if you find 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
)
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 find 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"))
)
)
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 infinitely nest-able. Let's say you had a belongsTo relationship between Posts and Authors. Let's say you wanted to find 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"))
)
)
array ("Author.name" => "Bob","or" => array ("Post.title LIKE" => "%magic%","Post.created >" => date('Y-m-d', strtotime("-2 weeks"))))
Cake can also check for null fields. In this example, the query will return records where the post title is not null:
array ("not" => array (
"Post.title" => null
)
)
array ("not" => array ("Post.title" => null))
To handle BETWEEN queries, you can use the following:
array('Post.id BETWEEN ? AND ?' => array(1,10)) array('Post.id BETWEEN ? AND ?' => array(1,10))
Note: CakePHP will quote the numeric values depending on the field type in your DB.
How about GROUP BY?
array('fields'=>array('Product.type','MIN(Product.price) as price'), 'group' => 'Product.type');
array('fields'=>array('Product.type','MIN(Product.price) as price'), 'group' => 'Product.type');
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'));
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.name' => 'Steel Mega Works')
),
'AND' => array(
array(
'OR'=>array(
array('Company.status' => 'active'),
'NOT'=>array(
array('Company.status'=> array('inactive', 'suspended'))
)
)
)
)
);
array('OR' => array(array('Company.name' => 'Future Holdings'),array('Company.name' => 'Steel Mega Works')),'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'))))
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'))))
3.7.4 Saving Your Data
CakePHP makes saving model data a snap. Data ready to be saved should be passed to the model’s save() method using the following basic format:
Array
(
[ModelName] => Array
(
[fieldname1] => 'value'
[fieldname2] => 'value'
)
)
Most of the time you won’t even need to worry about this format: CakePHP's HtmlHelper, FormHelper, and find methods all package data in this format. If you're using either of the helpers, the data is also conveniently available in $this->data for quick usage.
Here's a quick example of a controller action that uses a CakePHP model to save data to a database table:
function edit($id) {
//Has any form data been POSTed?
if(!empty($this->data)) {
//If the form data can be validated and saved...
if($this->Recipe->save($this->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));
}
function edit($id) {//Has any form data been POSTed?if(!empty($this->data)) {//If the form data can be validated and saved...if($this->Recipe->save($this->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));}
One additional note: when save is called, the data passed to it in the first parameter is validated using CakePHP validation mechanism (see the Data Validation chapter for more information). If for some reason your data isn't saving, be sure to check to see if some validation rules aren't being broken.
There are a few other save-related methods in the model that you'll find useful:
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 fields to be saved. For added security, you can limit the saved fields to those listed in $fieldList.
If $fieldList is not supplied, a malicious user can add additional fields to the form data, and by this change fields 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:
array( 'validate' => true, 'fieldList' => array(), 'callbacks' => true //other possible values are false, 'before', 'after' )
array('validate' => true,'fieldList' => array(),'callbacks' => true //other possible values are false, 'before', 'after')
More information about model callbacks is available here
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;
$this->Ingredient->save($newData);$newIngredientId = $this->Ingredient->id;
Creating or updating is controlled by the model's id field. If $Model->id is set, the record with this primary key is updated. Otherwise a new record is created.
//Create: id isn't set or is null $this->Recipe->create(); $this->Recipe->save($this->data); //Update: id is set to a numerical value $this->Recipe->id = 2; $this->Recipe->save($this->data);
//Create: id isn't set or is null$this->Recipe->create();$this->Recipe->save($this->data);//Update: id is set to a numerical value$this->Recipe->id = 2;$this->Recipe->save($this->data);
When calling save in a loop, don't forget to call create().
create(array $data = array())
This method resets the model state for saving new information.
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 fields from the model schema that are not already set, it will only reset fields that have already been set, and leave the rest unset. Use this to avoid updating fields in the database that were already set and are intended to be updated.
saveField(string $fieldName, string $fieldValue, $validate = false)
Used to save a single field 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 field, not the name of the model and field.
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');
$this->Post->saveField('title', 'A New Title for a New Day');
updateAll(array $fields, array $conditions)
Updates many records in a single call. Records to be updated are identified by the $conditions array, and fields to be updated, along with their values, are identified 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")
);
$this_year = date('Y-m-d h:i:s', strtotime('-1 year'));$this->Baker->updateAll(array('Baker.approved' => true),array('Baker.created <=' => "$this_year"));
The $fields array accepts SQL expressions. Literal values should be quoted manually.
For example, to close all tickets that belong to a certain customer:
$this->Ticket->updateAll(
array('Ticket.status' => "'closed'"),
array('Ticket.customer_id' => 453)
);
$this->Ticket->updateAll(array('Ticket.status' => "'closed'"),array('Ticket.customer_id' => 453));
saveAll(array $data = null, array $options = array())
Used to save (a) multiple individual records for a single model or (b) this record, as well as all associated records
The following options may be used:
validate: Set to false to disable validation, true to validate each record before saving, 'first' to validate *all* records before any are saved, or 'only' to only validate the records, but not save them.
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. If false, we return an array similar to the $data array passed, but values are set to true/false depending on whether each record saved successfully.
fieldList: Equivalent to the $fieldList parameter in Model::save()
For saving multiple records of single model, $data needs to be a numerically indexed array of records like this:
Array
(
[Article] => Array(
[0] => Array
(
[title] => title 1
)
[1] => Array
(
[title] => title 2
)
)
)
The command for saving the above $data array would look like this:
$this->Article->saveAll($data['Article']);
$this->Article->saveAll($data['Article']);
For saving a record along with its related record having a hasOne or belongsTo association, the data array should be like this:
Array
(
[User] => Array
(
[username] => billy
)
[Profile] => Array
(
[sex] => Male
[occupation] => Programmer
)
)
The command for saving the above $data array would look like this:
$this->Article->saveAll($data);
$this->Article->saveAll($data);
For saving a record along with its related records having hasMany association, the data array should be like this:
Array
(
[Article] => Array
(
[title] => My first article
)
[Comment] => Array
(
[0] => Array
(
[comment] => Comment 1
[user_id] => 1
)
[1] => Array
(
[comment] => Comment 2
[user_id] => 2
)
)
)
The command for saving the above $data array would look like this:
$this->Article->saveAll($data);
$this->Article->saveAll($data);
Saving related data with saveAll() will only work for directly associated models.
3.7.4.1 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.
If neither of the associated model records exists in the system yet (for example, you want to save a new User and their related Profile records at the same time), you'll need to first save the primary, or parent model.
To get an idea of how this works, let's imagine that we have an action in our UsersController that handles the saving of a new User and a related Profile. The example action shown below will assume that you've POSTed enough data (using the FormHelper) to create a single User and a single Profile.
<?php
function add() {
if (!empty($this->data)) {
// We can save the User data:
// it should be in $this->data['User']
$user = $this->User->save($this->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->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->data);
}
}
}
?>
<?phpfunction add() {if (!empty($this->data)) {// We can save the User data:// it should be in $this->data['User']$user = $this->User->save($this->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->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->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 field 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 that’s just been POSTed to a controller action.
To supplement the basic approach used above, CakePHP also offers a very handy method saveAll(), which allows you to validate and save multiple models in one shot. In addtion, saveAll() 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).
For transactions to work correctly in MySQL your tables must use InnoDB engine. Remember that MyISAM tables do not support transactions.
Let's see how we can use saveAll() to save Company and Account models at the same time.
First, you need to build your form for both Company and Account models (we'll assume that Company hasMany Account).
echo $form->create('Company', array('action'=>'add'));
echo $form->input('Company.name', array('label'=>'Company name'));
echo $form->input('Company.description');
echo $form->input('Company.location');
echo $form->input('Account.0.name', array('label'=>'Account name'));
echo $form->input('Account.0.username');
echo $form->input('Account.0.email');
echo $form->end('Add');
echo $form->create('Company', array('action'=>'add'));echo $form->input('Company.name', array('label'=>'Company name'));echo $form->input('Company.description');echo $form->input('Company.location');echo $form->input('Account.0.name', array('label'=>'Account name'));echo $form->input('Account.0.username');echo $form->input('Account.0.email');echo $form->end('Add');
Take a look at the way we named the form fields for the Account model. If Company is our main model saveAll() will expect the related model's (Account) data to arrive in a specific format. And having Account.0.fieldName is exactly what we need.
The above field naming is required for a hasMany association. If the association between the models is hasOne, you have to use ModelName.fieldName notation for the associated model.
Now, in our companies_controller we can create an add() action:
function add() {
if(!empty($this->data)) {
$this->Company->saveAll($this->data, array('validate'=>'first'));
}
}
function add() {if(!empty($this->data)) {$this->Company->saveAll($this->data, array('validate'=>'first'));}}
That's all there is to it. Now our Company and Account models will be validated and saved all at the same time.
A quick thing to point out here is the use of array('validate'=>'first'); this option will ensure that both of our models are validated.
3.7.4.1.1 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 field within the parent model table.
The name of the field consists of the singular model name followed by a underscore and the word "count".
my_model_count
my_model_count
Let's say you have a model called ImageComment and a model called Image, you would add an new INT-field to the image_comments table and name it image_count.
Here are some more examples:
| Model | Associated Model | Example |
|---|---|---|
| User | Image | users.image_count |
| Image | ImageComment | image.image_comment_count |
| BlogEntry | BlogEntryComment | blog_entries.blog_entry_comment_count |
Once you have added the counter field you are good to go. Activate counter-cache in your association by adding a counterCache key and set the value to true.
class Image extends AppModel {
var $belongsTo = array(
'ImageAlbum' => array('counterCache' => true)
);
}
class Image extends AppModel {var $belongsTo = array('ImageAlbum' => array('counterCache' => true));}
From now on, every time you add or remove a Image associated to ImageAlbum, the number within image_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 Image extends AppModel {
var $belongsTo = array(
'ImageAlbum' => array(
'counterCache' => true,
'counterScope' => array('active' => 1) // only count if "Image" is active = 1
));
}
class Image extends AppModel {var $belongsTo = array('ImageAlbum' => array('counterCache' => true,'counterScope' => array('active' => 1) // only count if "Image" is active = 1));}
3.7.4.2 Saving Related Model Data (HABTM)
Saving models that are associated by hasOne, belongsTo, and hasMany is pretty simple: you just populate the foreign key field with the ID of the associated model. Once that's done, you just call the save() method on the model, and everything gets linked up correctly.
With HABTM, you need to set the ID of the associated model in your data array. We'll build a form that creates a new tag and associates it on the fly with some recipe.
The simplest form might look something like this (we'll assume that $recipe_id is already set to something):
<?php echo $form->create('Tag');?>
<?php echo $form->input(
'Recipe.id',
array('type'=>'hidden', 'value' => $recipe_id)); ?>
<?php echo $form->input('Tag.name'); ?>
<?php echo $form->end('Add Tag'); ?>
<?php echo $form->create('Tag');?><?php echo $form->input('Recipe.id',array('type'=>'hidden', 'value' => $recipe_id)); ?><?php echo $form->input('Tag.name'); ?><?php echo $form->end('Add Tag'); ?>
In this example, you can see the Recipe.id hidden field 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, it'll automatically save the HABTM data to the database.
function add() {
//Save the association
if ($this->Tag->save($this->data)) {
//do something on success
}
}
function add() {//Save the associationif ($this->Tag->save($this->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->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>.
// in the controller:
$this->set('tags', $this->Recipe->Tag->find('list'));
// in the view:
$form->input('tags');
// in the controller:$this->set('tags', $this->Recipe->Tag->find('list'));// in the view:$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 defined using the ModelName convention.
// in the controller:
$this->set('tags', $this->Recipe->Tag->find('list'));
// in the view:
$form->input('Tag');
// in the controller:$this->set('tags', $this->Recipe->Tag->find('list'));// in the view:$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 fields 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
Child hasAndBelongsToMany Club
Another way to look at this is adding a Membership model:
Child hasMany Membership Membership belongsTo Child, Club Club hasMany Membership.
Child hasMany MembershipMembership belongsTo Child, ClubClub hasMany Membership.
These two examples are almost the exact same. They use the same amount and named fields in the database and the same amount of models. The important differences are that the "join" model is named differently and it's behavior is more predictable.
3.7.5 Deleting Data
These methods can be used to remove data.
3.7.5.1 del
del(int $id = null, boolean $cascade = true);
Deletes the record identified by $id. By default, also deletes records dependent on the record specified to be deleted.
For example, when deleting a User record that is tied to many Recipe records:
- 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.
3.7.5.2 remove
remove(int $id = null, boolean $cascade = true);
A synonym for del().
3.7.5.3 deleteAll
deleteAll(mixed $conditions, $cascade = true, $callbacks = false)
Same as with del() and remove(), except that deleteAll() deletes all records that match the supplied conditions. The $conditions array should be supplied as an SQL fragment or array.
3.7.6 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.
Defining 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. Defining 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, define, 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.
3.7.6.1 Relationship Types
The four association types in CakePHP are: hasOne, hasMany, belongsTo, and hasAndBelongsToMany (HABTM).
| Relationship | Association Type | Example |
|---|---|---|
| one to one | hasOne | A user has one profile. |
| one to many | hasMany | A user can have multiple recipes. |
| many to one | belongsTo | Many recipes belong to a user. |
| many to many | hasAndBelongsToMany | Recipes have, and belong to many tags. |
Associations are defined by creating a class variable named after the association you are defining. The class variable can sometimes be as simple as a string, but can be as complete as a multidimensional array used to define association specifics.
<?php
class User extends AppModel {
var $name = 'User';
var $hasOne = 'Profile';
var $hasMany = array(
'Recipe' => array(
'className' => 'Recipe',
'conditions' => array('Recipe.approved' => '1'),
'order' => 'Recipe.created DESC'
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasOne = 'Profile';var $hasMany = array('Recipe' => array('className' => 'Recipe','conditions' => array('Recipe.approved' => '1'),'order' => 'Recipe.created DESC'));}?>
In the above example, the first instance of the word 'Recipe' is what is termed an 'Alias'. This is an identifier for the relationship and can be anything you choose. Usually, you will choose the same name as the class that it references. However, aliases must be unique both within a single model and on both sides of a belongsTo/hasMany or a belongsTo/hasOne relationship. Choosing non-unique names for model aliases 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();
$this->Recipe->someFunction();
Similarly in your controller you can access a associated models simply by following your model associations and without adding it to the $uses array:
$this->User->Recipe->someFunction();
$this->User->Recipe->someFunction();
Remember that associations are defined 'one way'. If you define User hasMany Recipe that has no effect on the Recipe Model. You need to define Recipe belongsTo User to be able to access the User model from your Recipe model
3.7.6.2 hasOne
Let’s set up a User model with a hasOne relationship to a Profile 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 profiles table will contain a field called user_id. The basic pattern is:
| Relation | Schema |
|---|---|
| Apple hasOne Banana | bananas.apple_id |
| User hasOne Profile | profiles.user_id |
| Doctor hasOne Mentor | mentors.doctor_id |
The User model file will be saved in /app/models/user.php. To define the ‘User hasOne Profile’ association, add the $hasOne property to the model class. Remember to have a Profile model in /app/models/profile.php, or the association won’t work.
<?php
class User extends AppModel {
var $name = 'User';
var $hasOne = 'Profile';
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasOne = 'Profile';}?>
There are two ways to describe this relationship in your model files. The simplest method is to set the $hasOne attribute to a string containing the classname of the associated model, as we’ve done above.
If you need more control, you can define your associations using array syntax. For example, you might want to limit the association to include only certain records.
<?php
class User extends AppModel {
var $name = 'User';
var $hasOne = array(
'Profile' => array(
'className' => 'Profile',
'conditions' => array('Profile.published' => '1'),
'dependent' => true
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $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 you’re defining a ‘User hasOne Profile’ relationship, the className key should equal ‘Profile.’
- foreignKey: the name of the foreign key found in the other model. This is especially handy if you need to define multiple hasOne relationships. The default value for this key is the underscored, singular name of the current model, suffixed with ‘_id’. In the example above it would default to 'user_id'.
- conditions: An SQL fragment used to filter related model records. It’s good practice to use model names in SQL fragments: “Profile.approved = 1” is always better than just “approved = 1.”
- fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by default.
- order: An SQL fragment that defines the sorting order for the returned associated rows.
- dependent: When the dependent key is set to true, and the model’s 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 Profile.
Once this association has been defined, find operations on the User model will also fetch a related Profile 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
)
)
3.7.6.3 belongsTo
Now that we have Profile data access from the User model, let’s define a belongsTo association in the Profile 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:
| Relation | Schema |
|---|---|
| Banana belongsTo Apple | bananas.apple_id |
| Profile belongsTo User | profiles.user_id |
| Mentor belongsTo Doctor | mentors.doctor_id |
If a model(table) contains a foreign key, it belongsTo the other model(table).
We can define the belongsTo association in our Profile model at /app/models/profile.php using the string syntax as follows:
<?php
class Profile extends AppModel {
var $name = 'Profile';
var $belongsTo = 'User';
}
?>
<?phpclass Profile extends AppModel {var $name = 'Profile';var $belongsTo = 'User';}?>
We can also define a more specific relationship using array syntax:
<?php
class Profile extends AppModel {
var $name = 'Profile';
var $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id'
)
);
}
?>
<?phpclass Profile extends AppModel {var $name = 'Profile';var $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 you’re defining a ‘Profile 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 define multiple belongsTo relationships. The default value for this key is the underscored, singular name of the other model, suffixed with ‘_id’.
- conditions: An SQL fragment used to filter related model records. It’s good practice to use model names in SQL fragments: “User.active = 1” is always better than just “active = 1.”
- fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by default.
- order: An SQL fragment that defines the sorting order for the returned associated rows.
- counterCache: If set to true the associated Model will automatically increase or decrease the “[singular_model_name]_count” field in the foreign table whenever you do a save() or delete(). If its a string then its the field name to use. The value in the counter field represents the number of related rows.
- counterScope: Optional conditions array to use for updating counter cache field.
Once this association has been defined, find operations on the Profile 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
)
)
3.7.6.4 hasMany
Next step: defining a “User hasMany Comment” association. A hasMany association will allow us to fetch a user’s comments when we fetch a User record.
When keying your database tables for a hasMany relationship, follow this convention:
| Relation | Schema |
|---|---|
| User hasMany Comment | Comment.user_id |
| Cake hasMany Virtue | Virtue.cake_id |
| Product hasMany Option | Option.product_id |
We can define the hasMany association in our User model at /app/models/user.php using the string syntax as follows:
<?php
class User extends AppModel {
var $name = 'User';
var $hasMany = 'Comment';
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasMany = 'Comment';}?>
We can also define a more specific relationship using array syntax:
<?php
class User extends AppModel {
var $name = 'User';
var $hasMany = array(
'Comment' => array(
'className' => 'Comment',
'foreignKey' => 'user_id',
'conditions' => array('Comment.status' => '1'),
'order' => 'Comment.created DESC',
'limit' => '5',
'dependent'=> true
)
);
}
?>
<?phpclass User extends AppModel {var $name = 'User';var $hasMany = array('Comment' => array('className' => 'Comment','foreignKey' => 'user_id','conditions' => array('Comment.status' => '1'),'order' => 'Comment.created DESC','limit' => '5','dependent'=> true));}?>
Possible keys for hasMany association arrays include:
- className: the classname of the model being associated to the current model. If you’re defining 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 define multiple hasMany relationships. The default value for this key is the underscored, singular name of the actual model, suffixed with ‘_id’.
- conditions: An SQL fragment used to filter related model records. It’s good practice to use model names in SQL fragments: “Comment.status = 1” is always better than just “status = 1.”
- fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by default.
- order: An SQL fragment that defines the sorting order for the returned associated rows.
- 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.
The second parameter of the
Model->delete()method must be set to true in order for recursive deletion to occur. - 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.
-
finderQuery: 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 you're 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__$};SELECT Orange.* from oranges as Orange WHERE Orange.apple_id = {$__cakeID__$};
Once this association has been defined, find 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 you’ll need a complimentary Comment belongsTo User association in order to get the data from both directions. What we’ve 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 flow of information from either model’s perspective.
3.7.6.5 hasAndBelongsToMany (HABTM)
Alright. At this point, you can already call yourself a CakePHP model associations professional. You're already well versed in the three associations that take up the bulk of object relations.
Let's tackle the final 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, we're about to join up our Recipe model with a Tag model using HABTM. Attaching the "Italian" tag to my grandma's Gnocci recipe doesn't "use up" the tag. I can also tag my Honey Glazed BBQ Spaghettio's with "Italian" if I want to.
Links between hasMany associated objects are exclusive. If my User hasMany Comments, a comment is only linked to a specific user. It's no longer up for grabs.
Moving on. We'll need to set up an extra table in the database to handle HABTM associations. This new join table's name needs to include the names of both models involved, in alphabetical order, and separated with an underscore ( _ ). The contents of the table should be two fields, each foreign keys (which should be integers) pointing to both of the primary keys of the involved models. To avoid any issues - don't define a combined primary key for these two fields, if your application requires it you can define a unique index. If you plan to add any extra information to this table, it's a good idea to add an additional primary key field (by convention 'id') to make acting on the table as easy as any other model.
HABTM requires a separate join table that includes both model names.
| Relation | Schema (HABTM table in bold) |
|---|---|
| Recipe HABTM Tag | recipes_tags.id, recipes_tags.recipe_id, recipes_tags.tag_id |
| Cake HABTM Fan | cakes_fans.id, cakes_fans.cake_id, cakes_fans.fan_id |
| Foo HABTM Bar | bars_foos.id, bars_foos.foo_id, bars_foos.bar_id |
Table names are by convention in alphabetical order.
Once this new table has been created, we can define the HABTM association in the model files. We're gonna skip straight to the array syntax this time:
<?php
class Recipe extends AppModel {
var $name = 'Recipe';
var $hasAndBelongsToMany = array(
'Tag' =>
array(
'className' => 'Tag',
'joinTable' => 'recipes_tags',
'foreignKey' => 'recipe_id',
'associationForeignKey' => 'tag_id',
'unique' => true,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
)
);
}
?>
<?phpclass Recipe extends AppModel {var $name = 'Recipe';var $hasAndBelongsToMany = array('Tag' =>array('className' => 'Tag','joinTable' => 'recipes_tags','foreignKey' => 'recipe_id','associationForeignKey' => 'tag_id','unique' => true,'conditions' => '','fields' => '','order' => '','limit' => '','offset' => '','finderQuery' => '','deleteQuery' => '','insertQuery' => ''));}?>
Possible keys for HABTM association arrays include:
- className: the classname of the model being associated to the current model. If you're defining a ‘Recipe HABTM Tag' relationship, the className key should equal ‘Tag.'
- joinTable: The name of the join table used in this association (if the current table doesn't adhere to the naming convention for HABTM join tables).
- with: Defines the name of the model for the join table. By default CakePHP will auto-create a model for you. Using the example above it would be called RecipesTag. 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.
- foreignKey: the name of the foreign key found in the current model. This is especially handy if you need to define multiple HABTM relationships. The default value for this key is the underscored, singular name of the current model, suffixed with ‘_id'.
- associationForeignKey: the name of the foreign key found in the other model. This is especially handy if you need to define multiple HABTM relationships. The default value for this key is the underscored, singular name of the other model, suffixed with ‘_id'.
- unique: If true (default value) cake will first delete existing relationship records in the foreign keys table before inserting new ones, when updating a record. So existing associations need to be passed again when updating.
- conditions: An SQL fragment used to filter related model records. It's good practice to use model names in SQL fragments: "Comment.status = 1" is always better than just "status = 1."
- fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by default.
- order: An SQL fragment that defines the sorting order for the returned associated rows.
- 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.
- finderQuery, 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 defined, find 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
[user_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 123
[name] => Breakfast
)
[1] => Array
(
[id] => 124
[name] => Dessert
)
[2] => Array
(
[id] => 125
[name] => Heart Disease
)
)
)
Remember to define a HABTM association in the Tag model if you'd like to fetch Recipe data when using the Tag model.
It is also possible to execute custom find queries based on HABTM relationships. Consider the following examples:
Assuming the same structure in the above example (Recipe HABTM Tag), let's say we want to fetch all Recipes with the tag 'Dessert', one potential (wrong) way to achieve this would be to apply a condition to the association itself:
$this->Recipe->bindModel(array(
'hasAndBelongsToMany' => array(
'Tag' => array('conditions'=>array('Tag.name'=>'Dessert'))
)));
$this->Recipe->find('all');
$this->Recipe->bindModel(array('hasAndBelongsToMany' => array('Tag' => array('conditions'=>array('Tag.name'=>'Dessert')))));$this->Recipe->find('all');
//Data Returned
Array
(
0 => Array
{
[Recipe] => Array
(
[id] => 2745
[name] => Chocolate Frosted Sugar Bombs
[created] => 2007-05-01 10:31:01
[user_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 124
[name] => Dessert
)
)
)
1 => Array
{
[Recipe] => Array
(
[id] => 2745
[name] => Crab Cakes
[created] => 2008-05-01 10:31:01
[user_id] => 2349
)
[Tag] => Array
(
}
}
}
Notice that this example returns ALL recipes but only the "Dessert" tags. To properly achieve our goal, there are a number of ways to do it. One option is to search the Tag model (instead of Recipe), which will also give us all of the associated Recipes.
$this->Recipe->Tag->find('all', array('conditions'=>array('Tag.name'=>'Dessert')));
$this->Recipe->Tag->find('all', array('conditions'=>array('Tag.name'=>'Dessert')));
We could also use the join table model (which CakePHP provides for us), to search for a given ID.
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag')));
$this->Recipe->find('all', array(
'fields' => array('Recipe.*'),
'conditions'=>array('RecipesTag.tag_id'=>124) // id of Dessert
));
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag')));$this->Recipe->find('all', array('fields' => array('Recipe.*'),'conditions'=>array('RecipesTag.tag_id'=>124) // id of Dessert));
It's also possible to create an exotic association for the purpose of creating as many joins as necessary to allow filtering, for example:
$this->Recipe->bindModel(array(
'hasOne' => array(
'RecipesTag',
'FilterTag' => array(
'className' => 'Tag',
'foreignKey' => false,
'conditions' => array('FilterTag.id = RecipesTag.tag_id')
))));
$this->Recipe->find('all', array(
'fields' => array('Recipe.*'),
'conditions'=>array('FilterTag.name'=>'Dessert')
));
$this->Recipe->bindModel(array('hasOne' => array('RecipesTag','FilterTag' => array('className' => 'Tag','foreignKey' => false,'conditions' => array('FilterTag.id = RecipesTag.tag_id')))));$this->Recipe->find('all', array('fields' => array('Recipe.*'),'conditions'=>array('FilterTag.name'=>'Dessert')));
Both of which will return the following data:
//Data Returned
Array
(
0 => Array
{
[Recipe] => Array
(
[id] => 2745
[name] => Chocolate Frosted Sugar Bombs
[created] => 2007-05-01 10:31:01
[user_id] => 2346
)
[Tag] => Array
(
[0] => Array
(
[id] => 123
[name] => Breakfast
)
[1] => Array
(
[id] => 124
[name] => Dessert
)
[2] => Array
(
[id] => 125
[name] => Heart Disease
)
)
}
The same binding trick can be used to easily paginate your HABTM models. Just one word of caution: since paginate requires two queries (one to count the records and one to get the actual data), be sure to supply the false parameter to your bindModel(); which essentially tells CakePHP to keep the binding persistent over multiple queries, rather than just one as in the default behavior. Please refer to the API for more details.
For more information on binding model associations on the fly see Creating and destroying associations on the fly
Mix and match techniques to achieve your specific objective.
3.7.6.6 Creating and Destroying Associations on the Fly
Sometimes it becomes necessary to create and destroy model associations on the fly. 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 first level of recursion.
- You want to change the way an association is defined in order to sort or filter 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 Built-in behaviors for more information). Let's set up a few models so we can see how bindModel() and unbindModel() work. We'll start with two models:
<?php
class Leader extends AppModel {
var $name = 'Leader';
var $hasMany = array(
'Follower' => array(
'className' => 'Follower',
'order' => 'Follower.rank'
)
);
}
?>
<?php
class Follower extends AppModel {
var $name = 'Follower';
}
?>
<?phpclass Leader extends AppModel {var $name = 'Leader';var $hasMany = array('Follower' => array('className' => 'Follower','order' => 'Follower.rank'));}?><?phpclass Follower extends AppModel {var $name = 'Follower';}?>
Now, in the LeadersController, we can use the find() 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 defines a "Leader hasMany Followers" relationship. For demonstration purposes, let's use unbindModel() to remove that association in a controller action.
function someAction() {
// This fetches Leaders, and their associated Followers
$this->Leader->find('all');
// Let's 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.
// We've already used find('all') after unbindModel(),
// so this will fetch Leaders with associated
// Followers once again...
$this->Leader->find('all');
}
function someAction() {// This fetches Leaders, and their associated Followers$this->Leader->find('all');// Let's remove the hasMany...$this->Leader->unbindModel(array('hasMany' => array('Follower')));// Now using a find function will return// Leaders, with no Followers