Cake PHP Manualv 1
Cake PHP Manualv 1
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.
1. Click the edit link for the section you want to change.
2. Log in to Cookbook if prompted using your Bakeryaccount. Anyone can get a bakery account!
3. Please review the guidelines for submitting to the Cookbookto ensure consistency.
4. Submit additions/edits for review using valid, semantic HTML.
5. Follow the progress of your submissions using the rss feeds or check back in the next day or so to
see your changes approved.
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.
• 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 submit partial translations.
• 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.
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:
• 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
http://www.cakephp.org
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
http://book.cakephp.org
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
http://bakery.cakephp.org
The CakePHP Bakery is a clearing house for all things CakePHP. Check it out for tutorials, case studies, and
code examples. Once you’re acquainted with CakePHP, log on and share your knowledge with the
community and gain instant fame and fortune.
#The API
http://api.cakephp.org/
Straight to the point and straight from the core developers, the CakePHP API (Application Programming
Interface) is the most comprehensive documentation around for all the nitty gritty details of the internal
workings of the framework. Its a straight forward code reference, so bring your propeller hat.
#CakeForge
http://www.cakeforge.org
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.
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.
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:
• 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.3 Understanding Model-View-Controller
•
CakePHP follows the MVC software design pattern. Programming using MVC separates your application
into three main parts:
1. The Model represents the application data
2. The View renders a presentation of model data
3. The Controller handles and routes requests made by the client
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, RSSfeeds, 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.
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.
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:
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.
• 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.
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.
• app
• cake
• vendors
• .htaccess
• index.php
• README
• 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.
Holds the (few) configuration files CakePHP uses. Database connection details,
config
bootstrapping, core configuration files and more should be stored here.
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.
tmp
Make sure that this folder exists and that it is writable, otherwise the performance of your
application will be severely impacted. In debug mode, CakePHP will warn you if it is not the
case.
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
vendors 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.
In a production setup, this folder should serve as the document root for your application.
webroot
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.
Table names corresponding to CakePHP models are plural and underscored. The underlying tables for the
above mentioned models would be people, big_people, and really_big_people, respectively.
You can use the utility library "Inflector" to check the singular/plural of words. See the Inflector
documentation for more information.
Field names with two or more words are underscored like, first_name.
Foreign keys in hasMany, belongsTo or hasOne relationships are recognized by default as the (singular)
name of the related table followed by _id. So if a Baker hasMany Cake, the cakes table will refer to the
bakers table via a baker_id foreign key. For a multiple worded table like category_types, the foreign key
would be category_type_id.
Join tables, used in hasAndBelongsToMany (HABTM) relationships between models should be named after
the model tables they will join in alphabetical order (apples_zebras rather than zebras_apples).
All tables with which CakePHP models interact (with the exception of join tables), require a singular
primary key to uniquely identify each row. If you wish to model a table which does not have a single-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));
Rather than using an auto-increment key as the primary key, you may also use char(36). Cake will then
use a unique 36 character uuid (String::uuid) whenever you save a new record using the Model::save
method.
<?php
class 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.
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
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. For example: Apache. 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
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://github.com/cakephp/cakephp/downloads.
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 git repository here:http://github.com/cakephp/cakephp.
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 athttp://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
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 theDocumentRoot 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 DocumentRoot directive)
• /cake
• /vendors
• /.htaccess
• /index.php
• /README
Developers using Apache should set the DocumentRoot directive for the domain to:
DocumentRoot /cake_install/app/webroot
If your web server is configured correctly, you should now find your Cake application accessible at
http://www.example.com.
First, realize that there are three main parts to a Cake application:
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.
• ROOT should be set to the path of the directory that contains your app folder.
• APP_DIR should be set to the (base)name of your app folder.
• CAKE_CORE_INCLUDE_PATH should be set to the path of your CakePHP libraries folder.
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:
if (!defined('ROOT')) {
define('ROOT', DS.'home'.DS.'me');
}
if (!defined('APP_DIR')) {
define ('APP_DIR', 'myapp');
}
if (!defined('CAKE_CORE_INCLUDE_PATH')) {
define('CAKE_CORE_INCLUDE_PATH', DS.'usr'.DS.'lib');
}
$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.
Here are a few things you might try to get it running correctly.
1. 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 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>
Make sure you are loading up mod_rewrite correctly. You should see something like:
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.
1. Make sure your copy of CakePHP is from the downloads section of the site or our GIT repository,
and has been unpacked correctly by checking .htaccess files exist in the the cake root directory
(/var/www/html/cake_1_2 if you followed the insructions from the previous steps), the app
directory (/var/www/html/cake_1_2/app) and the cake webroot directory
(/var/www/html/cake_1_2/app/webroot).
The .htaccess file in the Cake root directory should look like this (this redirects everything to your
Cake app):
Code View
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^$ app/webroot/ [L]
RewriteRule (.*) app/webroot/$1 [L]
</IfModule>
The .htaccess file in the Cake app directory (will be copied to the top directory of your application by
bake) should look like this:
Code View
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule ^$ webroot/ [L]
RewriteRule (.*) webroot/$1 [L]
</IfModule>
The .htaccess file in the Cake webroot directory (will be copied to your application's web root by bake)
should look like this:
Code View
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ 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:
Code View
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ 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.
Using mod_rewrite
The easiest way to get pretty urls is by adding this script to your lighty config. Just edit the url, and you
should be okay. Please note that this doesn't work on Cake installations in subdirectories.
Code View
$HTTP["host"] =~ "^(www\.)?example.com$" {
url.rewrite-once = (
# if the request is for css|files etc, do not pass on to Cake
"/(css|files|img|js)/(.*)" => "/$1/$2",
"^([^\?]*)(\?(.+))?$" => "/index.php?url=$1&$3",
)
evhost.path-pattern = "/home/%2-%1/www/www/%4/app/webroot/"
}
Using mod_magnet
To use pretty URLs with CakePHP and Lighttpd, place this lua script in /etc/lighttpd/cake.
Code View
-- 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
-- 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. ;)
If you run your CakePHP installation from a subdirectory, you must set prefix = 'subdirectory_name' in the
above script.
$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"
)
}
server {
listen 80;
server_name example.com;
access_log /var/www/example.com/log/access.log;
error_log /var/www/example.com/log/error.log;
location / {
root /var/www/example.com/public/app/webroot/;
index index.php index.html index.htm;
if (-f $request_filename) {
break;
}
if (-d $request_filename) {
break;
}
rewrite ^(.+)$ /index.php?q=$1 last;
}
location ~ .*\.php[345]?$ {
include /etc/nginx/fcgi.conf;
fastcgi_pass 127.0.0.1:10005;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME
/var/www/example.com/public/app/webroot$fastcgi_script_name;
}
}
3.3.7 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.
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.
Fill out the key/value pairs in the configuration array to best suit your needs.
Key Value
The name of the database driver this configuration array is for. Examples: mysql, postgres, sqlite, pear-
driver drivername, adodb-drivername, mssql, oracle, or odbc. Note that for non-database sources (e.g. LDAP,
Twitter), leave this blank and use "datasource".
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.
The string that prefixes every table name in the database. If your tables don ’t have prefixes, set this to an
prefix (optional)
empty string.
port (optional) The TCP port or Unix socket used to connect to the server.
Indicates the character set to use when sending SQL statements to the server. This defaults to the
encoding 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.
datasource non-DBO datasource to use, e.g. 'ldap', 'twitter'
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 (notprefix_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.
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 won't 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.
Code View
<?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.
Code View
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:
Code View
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:
Code View
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.
Code View
Configure::delete('Company.name');
3.4.3.1.4 load
load(string $path)
Use this method to load configuration information from a specific file.
Code View
// /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');
?>
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.5version
version()
Returns the CakePHP version for the current application.
3.4.3.2CakePHP 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.
Valid values:
Security.level 'high' = x 10
'medium' = x 100
'low' = x 300
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.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.
Code View
App::import('Model', 'PluginName.Comment');
To load APP/plugins/plugin_name/vendors/flickr/flickr.php
Code View
App::import('Vendor', 'PluginName.flickr/flickr');
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.
To load app/vendors/vendorName/libFile.php
Code View
App::import('Vendor', 'aUniqueIdentifier', array('file' =>'vendorName'.DS.'libFile.php'));
As will be explained later, routes in CakePHP 1.2 have been expanded and are now very powerful.
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.
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';
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().
Code View
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.
Code View
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:
Code View
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/5if 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:
Code View
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.
Code View
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.
Code View
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 the date parameters in $this->params.
// 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]+'
)
);
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.
Code View
// 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')
)); ?>
If you want to create a URL such as /page/title-of-page.html you would create your route as illustrated
below:
Code View
Router::connect(
'/page/:title',
array('controller' => 'pages', 'action' => 'view'),
array(
'pass' => array('title')
)
);
Then to create links which map back to the routes simply use:
Code View
$html->link('Link title', array('controller' => 'pages', 'action' => 'view', 'title' => Inflector::slug('text to
slug', '-'), 'ext' => 'html'))
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
This array contains regular expression rules for pluralizing special cases. The keys of the array
$pluralRules
are patterns, and the values are replacements.
An array containing words that do not need to be modified in order to be plural (mass nouns,
$uninflectedPlural
etc.).
An array containing words and their plurals. The keys of the array contain the singular form,
$irregularPlural 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.
Same as with $uninflectedPlural, only this array holds words that have no singular form. This is
$uninflectedSingular
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.
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, which are part of the CakePHP library. The AppController class can be
defined in /app/app_controller.php and it should contain methods that are shared between all of your
application’s controllers.
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:
Code View
<?php
# /app/controllers/recipes_controller.php
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:
Code View
<?php
class 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
Remember to add the default Html and Form helpers, if you define var $helpers in your AppController
Please also remember to call AppController's callbacks within child controller callbacks for best results:
Code View
function beforeFilter(){
parent::beforeFilter();
}
3.5.4.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.
Code View
<?php
?>
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. But if
you choose to define your own $helpers array in AppController, make sure to include Html and Form if you
want them still available by default in your own Controllers. 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.
Code View
<?php
class RecipesController extends AppController {
var $name = 'Recipes';
If you do not wish to use a Model in your controller, set var $uses = array(). This will allow you to use a
controller without a need for a corresponding Model file.
It's bad practice to just add all the models your controller uses to the $uses array. Check here and here to
see how to properly access associated and unassociated models respectively.
3.5.4.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.
Code View
<?php
?>
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.
Code View
<?php
?>
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.4.4.2 admin
$this->params['admin']
Is set to 1 if the current action was invoked via admin routing.
3.5.4.4.3 bare
$this->params['bare']
Stores 1 if the current layout is empty, 0 if not.
3.5.4.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.4.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.4.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.4.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.4.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.4.4.9 data
$this->data
Used to handle POST data sent from the FormHelper forms to the controller.
Code View
// The FormHelper is used to create a form element:
$form->text('User.first_name');
Which when rendered, looks something like:
3.5.4.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
)
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.4.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.5 Controller Methods
For a complete list of controller methods and their descriptions visit the CakePHP API. Check out
http://api.cakephp.org/class/controller.
$this->set('color', 'pink');
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.):
Code View
<?php
$data = array(
'color' => 'pink',
'type' => 'sugar',
'base_price' => 23.95
);
$this->set($data);
?>
3.5.5.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.
Code View
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 the $action parameter. If $action starts with '/', it is assumed to be a view or element file
relative to the /app/viewsfolder. This allows direct rendering of elements, very useful in ajax calls.
Code View
// 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.
if($success) {
$this->redirect(array('controller' => 'orders', 'action' => 'thanks'));
} else {
$this->redirect(array('controller' => 'orders', 'action' => 'confirm'));
}
}
You can also use a relative or absolute URL as the $url argument:
Code View
$this->redirect('/orders/thanks'));
$this->redirect('http://www.example.com');
You can also pass data to the action:
Code View
$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:
Code View
$this->redirect($this->referer());
If using prefix routing and you would like to redirect to a page without the prefix you need to specify with
a third parameter set to null, false will not work correctly. The example below shows how to redirect to a
page with the admin prefix removed.
Code View
$this->redirect(array('controller' => 'orders', 'action' => 'add', 'admin' => null));
3.5.5.2.2 flash
flash(string $message, string $url, integer $pause)
Like redirect(), the flash() method is used to direct a user to a new page after an operation. The flash()
method is different in that it shows a message before passing the user on to another URL.
The first parameter should hold the message to be displayed, and the second parameter is a CakePHP-
relative URL. CakePHP will display the $message for $pauseseconds before forwarding the user on.
For in-page flash messages, be sure to check out SessionComponent’s setFlash() method.
3.5.5.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.
_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.5.4.2 referer
string referer(mixed $default = null, boolean $local = false)
Returns the referring URL for the current request. Parameter $default can be used to supply a default URL
to use if HTTP_REFERER cannot be read from headers. So, instead of doing this:
Code View
<?php
class UserController extends AppController {
function delete($id) {
// delete code goes here, and then...
if ($this->referer() != '/') {
$this->redirect($this->referer());
} else {
$this->redirect(array('action' => 'index'));
}
}
}
?>
you can do this:
Code View
<?php
class UserController extends AppController {
function delete($id) {
// delete code goes here, and then...
$this->redirect($this->referer(array('action' => 'index')));
}
}
?>
If $default is not set, the function defaults to the root of your domain - '/'.
Parameter $local if set to true, restricts referring URLs to local server.
3.5.5.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.
The headers sent to this effect are:
If you want use a different SQL operator between terms, supply them using the second parameter.
Code View
/*
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’
$condtions=$this->postConditions(
$this->data,
array(
'num_items' => '>=',
'referrer' => 'LIKE'
)
);
$orders = $this->Order->find("all",compact('conditions'));
The third parameter allows you to tell CakePHP what SQL boolean operator to use between the 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.5.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.5.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.
Code View
// controllers/comments_controller.php
class 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:
Code View
// 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:
Code View
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:
Code View
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:
Code View
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'.
Code View
echo $this->requestAction('/articles/featured/limit:3');
echo $this->requestAction('/articles/view/5');
As an array in the requestAction would then be:
Code View
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.5.5.4.7 loadModel
loadModel(string $modelClass, mixed $id)
The loadModel function comes handy when you need to use a model which is not the controller's default
model or its associated model.
Code View
$this->loadModel('Article');
$recentArticles = $this->Article->find('all', array('limit' => 5, 'order' => 'Article.created DESC'));
Code View
$this->loadModel('User', 2);
$user = $this->User->read();
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.
Code View
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.
Code View
var $components = array('DebugKit.toolbar' => array('panels' => array('history', 'session'));
Consult the relevant documentation to determine what configuration options each component provides.
The components can have the beforeRender and beforeRedirect callbacks which are triggered before your
page is rendered and before a redirect respectively.
You can disable the callbacks triggering by setting the enabled property of a component to false.
• initialize() is fired before the controller's beforeFilter, but after models have been constructed.
• startup() is fired after the controllers' beforeFilter, but before the controller action.
• beforeRender() is fired before a view is rendered.
• beforeRedirect() is fired before a redirect is done from a controller. You can use the return of the
callback to replace the url to be used for the redirect.
• shutdown() is fired after the view is rendered and before the response is returned.
You can add additional methods to your components, and call those methods at any time by using
Component::triggerCallback(). If you had added a onAccesscallback to your components. You could fire
that callback from within the controller by calling $this->Component->triggerCallback('onAccess', $this);
You can disable the callbacks triggering by setting the enabled property of a component to false.
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:
Code View
<?php
?>
Take notice that our MathComponent extends Object and not Component. Extending Component can
create infinite redirect issues, when combined with other Components.
This syntax is not implemented by any of the Core Components at this time
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 executes the requested action's logic but before
the controller's renders views and layout.
shutdown(&$controller)
The shutdown method is called before output is sent to browser.
Here is a skeleton component you can use as a template for your own custom components.
Code View
<?php
class SkeletonComponent extends Object {
//called before Controller::beforeFilter()
function initialize(&$controller, $settings = array()) {
// saving the controller reference for later use
$this->controller =& $controller;
}
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.
Code View
<?php
class MyComponent extends Object {
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:
Code View
<?php
class MathComponent extends Object {
function doComplexOperation($amount1, $amount2) {
return $amount1 + $amount2;
}
You can include other components in your component the exact same way you include them in
controllers: Use the $components var.
Code View
<?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() {
// ...
}
}
?>
Code View
<?php
class 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.
?>
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 missing (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.
Code View
<?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);
}
}
?>
Associated models are available through the main model. In the following example, Recipe has an
association with the Ingredient model.
Code View
<?php
class 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 Controller::loadModel() to get the
model.
Code View
<?php
class RecipesController extends AppController {
function index() {
$recipes = $this->Recipe->find('all');
$this->loadModel('Car');
$cars = $this->Car->find('all');
$this->set(compact('recipes', 'cars'));
}
}
?>
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.
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.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.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
If you have a title and name field in your table, the title will be used.
If you want to use something other than the convention set var $displayField = 'some_field';. Only one
field can be set here.
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'. Keep in mind that $type is case
sensitive. Using a upper case character (for example 'All') will not produce the expected results.
$params is used to pass all parameters to the various finds, and has the following possible keys by default
—all of which are optional:
Code View
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
'joins' => array(), // array of arrays defining join operations
'group' => array('Model.field'), //fields to GROUP BY
'limit' => n, //int
'page' => n, //int
'offset'=> n, //int
'callbacks' => true //other possible values are false, 'before', 'after'
)
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. For details about join options, see Joining
tables.
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:
Code View
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
)
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 (which can be configured using the model attribute displayField) is used for the
value. Some further examples to clarify:.
Code View
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',
)
)
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:
Code View
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
)
3.7.3.2 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.
PHP4 users have to use this function a little differently due to some case-insensitivity in PHP4:
In either case, the returned result is an array formatted just as it would be from find() or findAll(),
respectively.
3.7.3.3 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.
PHP4 users have to use this function a little differently due to some case-insensitivity in PHP4:
In either case, the returned result is an array formatted just as it would be from find() or findAll(),
respectively.
3.7.3.4 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,
Code View
$this->Picture->query("SELECT * FROM pictures LIMIT 2;");
might return
Code View
Array
(
[0] => Array
(
[pictures] => Array
(
[id] => 1304
[user_id] => 759
)
)
3.7.3.5 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.
Code View
$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
3.7.3.6 read()
read($fields, $id)
read() is a method used to set the current model data (Model::$data)--such as during edits--but it can also
be used in other circumstances to retrieve a single record from the database.
$fields is used to pass a single field name, as a string, or an array of field names; if left empty, all fields
will be fetched.
$id specifies the ID of the record to be read. By default, the currently selected record, as specified by
Model::$id, is used. Passing a different value to $id will cause that record to be selected.
read() always returns an array (even if only a single field name is requested). Use field to retrieve the
value of a single field.
Code View
function beforeDelete($cascade) {
...
$rating = $this->read('rating'); // gets the rating of the record being deleted.
$name = $this->read('name', $id2); // gets the name of a second record.
$rating = $this->read('rating'); // gets the rating of the second record.
$this->id = $id3; //
$this->read(); // reads a third record
$record = $this->data // stores the third record in $record
...
}
Notice that the third call to read() fetches the rating of the same record read before. That is because
read() changes Model::$id to any value passed as $id. Lines 6-8 demonstrate how read() changes the
current model data.
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.
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":
Code View
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:
Code View
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:
Code View
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:
Code View
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
Code View
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:
Code View
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:
Code View
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:
Code View
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:
Code View
array ("NOT" => array (
"Post.title" => null
)
)
To handle BETWEEN queries, you can use the following:
Code View
array('Post.id BETWEEN ? AND ?' => array(1,10))
Note: CakePHP will quote the numeric values depending on the field type in your DB.
FROM
`companies` AS `Company`
WHERE
((`Company`.`name` = 'Future Holdings')
OR
(`Company`.`name` = 'Steel Mega Works'))
AND
((`Company`.`status` = 'active')
OR (NOT (`Company`.`status` IN ('inactive', 'suspended'))))
Sub-queries
For the example, imagine we have a "users" table with "id", "name" and "status". The status can be "A",
"B" or "C". And we want to get all the users that have status different than "B" using sub-query.
In order to achieve that we are going to get the model data source and ask it to build the query as if we
were calling a find method, but it will just return the SQL statement. After that we make an expression and
add it to the conditions array.
Code View
$conditionsSubQuery['"User2"."status"'] = 'B';
$dbo = $this->User->getDataSource();
$subQuery = $dbo->buildStatement(
array(
'fields' => array('"User2"."id"'),
'table' => $dbo->fullTableName($this->User),
'alias' => 'User2',
'limit' => null,
'offset' => null,
'joins' => array(),
'conditions' => $conditionsSubQuery,
'order' => null,
'group' => null
),
$this->User
);
$subQuery = ' "User"."id" NOT IN (' . $subQuery . ') ';
$subQueryExpression = $dbo->expression($subQuery);
$conditions[] = $subQueryExpression;
$this->User->find('all', compact('conditions'));
This should generate the following SQL:
Code View
SELECT
"User"."id" AS "User__id",
"User"."name" AS "User__name",
"User"."status" AS "User__status"
FROM
"users" AS "User"
WHERE
"User"."id" NOT IN (
SELECT
"User2"."id"
FROM
"users" AS "User2"
WHERE
"User2"."status" = 'B'
)
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:
Code View
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');
}
}
There are a few other save-related methods in the model that you'll find useful:
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 (if you are not using
Security component), and by this change fields that were not originally intended to be changed.
The save method also has an alternate syntax:
$params array can have any of the following available options as keys:
Code View
array(
'validate' => true,
'fieldList' => array(),
'callbacks' => true //other possible values are false, 'before', 'after'
)
More information about model callbacks is available here
If you dont want the updated field to be updated when saving some data add 'updated' => false to your
$data array
Once a save has been completed, the ID for the object can be found in the $id attribute of the model
object - something especially handy when creating new objects.
Code View
$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.
Code View
//Create: id isn't set or is null
$this->Recipe->create();
$this->Recipe->save($this->data);
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.
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, $fieldNameshould 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:
Code View
$this->Post->saveField('title', 'A New Title for a New Day');
You cant stop the updated field being updated with this method, you need to use the save() method.
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:
Code View
$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.
Used to save (a) multiple individual records for a single model or (b) this record, as well as all associated
records
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.
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:
Code View
$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:
Code View
$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:
Code View
$this->Article->saveAll($data);
Saving related data with saveAll() will only work for directly associated models.
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.
Code View
<?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;
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).
Code View
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->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:
Code View
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 ofarray('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".
Code View
my_model_count
Let's say you have a model called ImageComment and a model called Image, you would add a new INT-
field to the image table and name it image_comment_count.
Here are some more examples:
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.
Code View
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.
If you need to specify a custom counter field, set counterCache to the name of that field:
Code View
class Image extends AppModel {
var $belongsTo = array(
'ImageAlbum' => array('counterCache' => 'number_of_images')
);
}
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:
Code View
class Image extends AppModel {
var $belongsTo = array(
'ImageAlbum' => array(
'counterCache' => true,
'counterScope' => array('Image.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):
Code View
<?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.
Code View
function add() {
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>.
Code View
// 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.
Code View
// 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.
HasAndBelongsToMany between two models is in reality shorthand for three models associated through
both a hasMany and a belongsTo association.
When your join table contains extra fields besides two foreign keys, in most cases its easier to make a
model for the join table and setup hasMany, belongsTo associations as shown in example above instead of
using HABTM association.
3.7.5 Deleting Data
These methods can be used to remove data.
3.7.5.1 delete
delete(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 delete().
3.7.5.3 deleteAll
deleteAll(mixed $conditions, $cascade = true, $callbacks = false)
Same as with delete() 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.
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.
Code View
<?php
?>
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
Code View
$this->Recipe->someFunction();
Similarly in your controller you can access an associated model simply by following your model
associations and without adding it to the $uses array:
Code View
$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:
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.
Code View
<?php
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.
Code View
<?php
• 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:
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:
We can define the belongsTo association in our Profile model at /app/models/profile.php using the string
syntax as follows:
Code View
<?php
• 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:
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:
We can define the hasMany association in our User model at /app/models/user.php using the string syntax
as follows:
Code View
<?php
• 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.
• 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:
Code View
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:
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
Make sure primary keys in tables cakes and recipes have "id" fields as assumed by convention. If they're
different than assumed, it has to be changed in model
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:
Code View
<?php
• 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:
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:
Code View
$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.
Code View
$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.
Code View
$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:
Code View
$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 saving HABTM objects see Saving Related Model Data (HABTM)
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.
• 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:
Code View
<?php
?>
<?php
?>
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.
Code View
function someAction() {
// This fetches Leaders, and their associated Followers
$this->Leader->find('all');
In CakePHP some associations (belongsTo and hasOne) performs automatic joins to retrieve data, so you
can issue queries to retrieve models based on data in the related one.
But this is not the case with hasMany and hasAndBelongsToMany associations. Here is where forcing joins
comes to the rescue. You only have to define the necessary joins to combine tables and get the desired
results for your query.
To force a join between tables you need to use the "modern" syntax for Model::find(), adding a 'joins' key
to the $options array. For example:
Code View
$options['joins'] = array(
array(
'table' => 'channels',
'alias' => 'Channel',
'type' => 'LEFT',
'conditions' => array(
'Channel.id = Item.channel_id',
)
)
);
$Item->find('all', $options);
Note that the 'join' arrays are not keyed.
In the above example, a model called Item is left joined to the channels table. You can alias the table with
the model name, so the retrieved data complies with the CakePHP data structure.
$options['conditions'] = array(
'Channel.private' => 1
);
Suppose a Book hasAndBelongsToMany Tag association. This relation uses a books_tags table as join
table, so you need to join the books table to the books_tags table, and this with the tags table:
Code View
$options['joins'] = array(
array('table' => 'books_tags',
'alias' => 'BooksTag',
'type' => 'inner',
'conditions' => array(
'Books.id = BooksTag.books_id'
)
),
array('table' => 'tags',
'alias' => 'Tag',
'type' => 'inner',
'conditions' => array(
'BooksTag.tag_id = Tag.id'
)
)
);
$options['conditions'] = array(
'Tag.tag' => 'Novel'
);
Please note that these callbacks are not called when dealing with associated models; callbacks are only
executed for the main model of a query.
3.7.7.1 beforeFind
beforeFind(mixed $queryData)
Called before any find-related operation. The $queryData passed to this callback contains information
about the current query: conditions, fields, etc.
If you do not wish the find operation to begin (possibly based on a decision relating to the $queryData
options), return false. Otherwise, return the possibly modified$queryData, or anything you want to get
passed to find and its counterparts.
You might use this callback to restrict find operations based on a user’s role, or make caching decisions
based on the current load.
3.7.7.2 afterFind
afterFind(array $results, bool $primary)
Use this callback to modify results that have been returned from a find operation, or to perform any other
post-find logic. The $results parameter passed to this callback contains the returned results from the
model's find operation, i.e. something like:
Code View
$results = array(
0 => array(
'ModelName' => array(
'field1' => 'value1',
'field2' => 'value2',
),
),
);
The return value for this callback should be the (possibly modified) results for the find operation that
triggered this callback.
The $primary parameter indicates whether or not the current model was the model that the query
originated on or whether or not this model was queried as an association. If a model is queried as an
assocation the format of $results can differ; instead of the result you would normally get from a find
operation, you may get this:
Code View
$results = array(
'field_1' => 'value1',
'field_2' => 'value2'
);
Code expecting $primary to be true will probably get a "Cannot use string offset as an array" fatal error
from PHP if a recursive find is used.
Below is an example of how afterfind can be used for date formating.
Code View
function afterFind($results) {
foreach ($results as $key => $val) {
if (isset($val['Event']['begindate'])) {
$results[$key]['Event']['begindate'] = $this-
>dateFormatAfterFind($val['Event']['begindate']);
}
}
return $results;
}
function dateFormatAfterFind($dateString) {
return date('d-m-Y', strtotime($dateString));
}
3.7.7.3 beforeValidate
beforeValidate()
Use this callback to modify model data before it is validated, or to modify validation rules if required. This
function must also return true, otherwise the current save() execution will abort.
3.7.7.4 beforeSave
beforeSave()
Place any pre-save logic in this function. This function executes immediately after model data has been
successfully validated, but just before the data is saved. This function should also return true if you want
the save operation to continue.
This callback is especially handy for any data-massaging logic that needs to happen before your data is
stored. If your storage engine needs dates in a specific format, access it at $this->data and modify it.
Below is an example of how beforeSave can be used for date conversion. The code in the example is used
for an application with a begindate formatted like YYYY-MM-DD in the database and is displayed like DD-
MM-YYYY in the application. Of course this can be changed very easily. Use the code below in the
appropriate model.
Code View
function beforeSave() {
if(!empty($this->data['Event']['begindate']) && !empty($this->data['Event']['enddate'])) {
$this->data['Event']['begindate'] = $this->dateFormatBeforeSave($this-
>data['Event']['begindate']);
$this->data['Event']['enddate'] = $this->dateFormatBeforeSave($this->data['Event']
['enddate']);
}
return true;
}
function dateFormatBeforeSave($dateString) {
return date('Y-m-d', strtotime($dateString)); // Direction is from
}
Be sure that beforeSave() returns true, or your save is going to fail.
3.7.7.5 afterSave
afterSave(boolean $created)
If you have logic you need to be executed just after every save operation, place it in this callback method.
The value of $created will be true if a new record was created (rather than an update).
3.7.7.6 beforeDelete
beforeDelete(boolean $cascade)
Place any pre-deletion logic in this function. This function should return true if you want the deletion to
continue, and false if you want to abort.
The value of $cascade will be true if records that depend on this record will also be deleted.
3.7.7.7 afterDelete
afterDelete()
Place any logic that you want to be executed after every deletion in this callback method.
3.7.7.8 onError
onError()
Called if any problems occur.
For a complete list of model attributes and their descriptions visit the CakePHP API. Check out
http://api.cakephp.org/class/model.
3.7.8.1 useDbConfig
The useDbConfig property is a string that specifies the name of the database connection to use to bind
your model class to the related database table. You can set it to any of the database connections defined
within your database configuration file. The database configuration file is stored in
/app/config/database.php.
The useDbConfig property is defaulted to the 'default' database connection.
Example usage:
Code View
class Example extends AppModel {
var $useDbConfig = 'alternate';
}
3.7.8.2 useTable
The useTable property specifies the database table name. By default, the model uses the lowercase,
plural form of the model's class name. Set this attribute to the name of an alternate table, or set it to false
if you wish the model to use no database table.
Example usage:
Code View
class Example extends AppModel {
var $useTable = false; // This model does not use a database table
}
Alternatively:
Code View
class Example extends AppModel {
var $useTable = 'exmp'; // This model uses a database table 'exmp'
}
3.7.8.3 tablePrefix
The name of the table prefix used for the model. The table prefix is initially set in the database connection
file at /app/config/database.php. The default is no prefix. You can override the default by setting the
tablePrefix attribute in the model.
Example usage:
Code View
class Example extends AppModel {
var $tablePrefix = 'alternate_'; // will look for 'alternate_examples'
}
3.7.8.4 primaryKey
Each table normally has a primary key, id. You may change which field name the model uses as its
primary key. This is common when setting CakePHP to use an existing database table.
Example usage:
Code View
class Example extends AppModel {
var $primaryKey = 'example_id'; // example_id is the field name in the database
}
3.7.8.5 displayField
The displayField attribute specifies which database field should be used as a label for the record. The label
is used in scaffolding and in find('list') calls. The model will use name or title, by default.
For example, to use the username field:
Code View
class User extends AppModel {
var $displayField = 'username';
}
Multiple field names cannot be combined into a single display field. For example, you cannot specify,
array('first_name', 'last_name') as the display field.
3.7.8.6 recursive
The recursive property defines how deep CakePHP should go to fetch associated model data via find(),
findAll() and read() methods.
Imagine your application features Groups which belong to a domain and have many Users which in turn
have many Articles. You can set $recursive to different values based on the amount of data you want back
from a $this->Group->find() call:
Depth Description
-1 Cake fetches Group data only, no joins.
0 Cake fetches Group data and its domain
1 Cake fetches a Group, its domain and its associated Users
2 Cake fetches a Group, its domain, its associated Users, and the Users' associated Articles
Set it no higher than you need. Having CakePHP fetch data you aren’t going to use slows your app
unnecessarily. Also note that the default recursive level is 1.
If you want to combine $recursive with the fields functionality, you will have to add the columns
containing the required foreign keys to the fields array manually. In the example above, this could mean
adding domain_id.
3.7.8.7 order
The default ordering of data for any find operation. Possible values include:
Code View
$order = "field"
$order = "Model.field";
$order = "Model.field asc";
$order = "Model.field ASC";
$order = "Model.field DESC";
$order = array("Model.field" => "asc", "Model.field2" => "DESC");
3.7.8.8 data
The container for the model’s fetched data. While data returned from a model class is normally used as
returned from a find() call, you may need to access information stored in $data inside of model callbacks.
3.7.8.9 _schema
Contains metadata describing the model’s database table fields. Each field is described by:
• name
• type (integer, string, datetime, etc.)
• null
• default value
• length
Example Usage:Code View
var $_schema = array(
'first_name' => array(
'type' => 'string',
'length' => 30
),
'last_name' => array(
'type' => 'string',
'length' => 30
),
'email' => array(
'type' => 'string',
'length' => 30
),
'message' => array('type' => 'text')
);
3.7.8.10 validate
This attribute holds rules that allow the model to make data validation decisions before saving. Keys
named after fields hold regex values allowing the model to try to make matches.
It is not necessary to call validate() before save() as save() will automatically validate your data before
actually saving.
For more information on validation, see the Data Validation chapter later on in this manual.
3.7.8.11 name
As you saw earlier in this chapter, the name attribute is a compatibility feature for PHP4 users and is set
to the same value as the model name.
Example usage:
Code View
class Example extends AppModel {
var $name = 'Example';
}
3.7.8.12 cacheQueries
If set to true, data fetched by the model during a single request is cached. This caching is in-memory
only, and only lasts for the duration of the request. Any duplicate requests for the same data is handled
by the cache.
3.7.9 Additional Methods and Properties
While CakePHP’s model functions should get you where you need to go, don’t forget that model classes
are just that: classes that allow you to write your own methods or define your own properties.
Any operation that handles the saving and fetching of data is best housed in your model classes. This
concept is often referred to as the fat model.
Code View
class Example extends AppModel {
function getRecent() {
$conditions = array(
'created BETWEEN (curdate() - interval 7 day) and (curdate() - interval 0 day))'
);
return $this->find('all', compact('conditions'));
}
}
This getRecent() method can now be used within the controller.
Code View
$recent = $this->Example->getRecent();
3.8 Behaviors
Model behaviors are a way to organize some of the functionality defined in CakePHP models. They allow
us to separate logic that may not be directly related to a model, but needs to be there. By providing a
simple yet powerful way to extend models, behaviors allow us to attach functionality to models by
defining a simple class variable. That's how behaviors allow models to get rid of all the extra weight that
might not be part of the business contract they are modeling, or that is also needed in different models
and can then be extrapolated.
As an example, consider a model that gives us access to a database table which stores structural
information about a tree. Removing, adding, and migrating nodes in the tree is not as simple as deleting,
inserting, and editing rows in the table. Many records may need to be updated as things move around.
Rather than creating those tree-manipulation methods on a per model basis (for every model that needs
that functionality), we could simply tell our model to use the TreeBehavior, or in more formal terms, we
tell our model to behave as a Tree. This is known as attaching a behavior to a model. With just one line of
code, our CakePHP model takes on a whole new set of methods that allow it to interact with the
underlying structure.
CakePHP already includes behaviors for tree structures, translated content, access control list interaction,
not to mention the community-contributed behaviors already available in the CakePHP Bakery
(http://bakery.cakephp.org). In this section, we'll cover the basic usage pattern for adding behaviors to
models, how to use CakePHP's built-in behaviors, and how to create our own.
?>
This example shows how a Category model could be managed in a tree structure using the TreeBehavior.
Once a behavior has been specified, use the methods added by the behavior as if they always existed as
part of the original model:
Code View
// Set ID
$this->Category->id = 42;
?>
We can also attach several behaviors to a model. There's no reason why, for example, our Category
model should only behave as a tree, it may also need internationalization support:
Code View
<?php
?>
So far we have been adding behaviors to models using a model class variable. That means that our
behaviors will be attached to our models throughout the model's lifetime. However, we may need to
"detach" behaviors from our models at runtime. Let's say that on our previous Category model, which is
acting as a Tree and a Translate model, we need for some reason to force it to stop acting as a Translate
model:
Code View
// Detach a behavior from our model:
$this->Category->Behaviors->detach('Translate');
That will make our Category model stop behaving as a Translate model from thereon. We may need,
instead, to just disable the Translate behavior from acting upon our normal model operations: our finds,
our saves, etc. In fact, we are looking to disable the behavior from acting upon our CakePHP model
callbacks. Instead of detaching the behavior, we then tell our model to stop informing of these callbacks
to the Translate behavior:
Code View
// Stop letting the behavior handle our model callbacks
$this->Category->Behaviors->disable('Translate');
We may also need to find out if our behavior is handling those model callbacks, and if not we then restore
its ability to react to them:
Code View
// If our behavior is not handling model callbacks
if (!$this->Category->Behaviors->enabled('Translate')) {
// Tell it to start doing so
$this->Category->Behaviors->enable('Translate');
}
Just as we could completely detach a behavior from a model at runtime, we can also attach new
behaviors. Say that our familiar Category model needs to start behaving as a Christmas model, but only
on Christmas day:
Code View
// If today is Dec 25
if (date('m/d') == '12/25') {
// Our model needs to behave as a Christmas model
$this->Category->Behaviors->attach('Christmas');
}
We can also use the attach method to override behavior settings:
Code View
// We will change one setting from our already attached behavior
$this->Category->Behaviors->attach('Tree', array('left' => 'new_left_node'));
There's also a method to obtain the list of behaviors a model has attached. If we pass the name of a
behavior to the method, it will tell us if that behavior is attached to the model, otherwise it will give us the
list of attached behaviors:
Code View
// If the Translate behavior is not attached
if (!$this->Category->Behaviors->attached('Translate')) {
// Get the list of all behaviors the model has attached
$behaviors = $this->Category->Behaviors->attached();
}
Every callback takes a reference to the model it is being called from as the first parameter.
Besides implementing the callbacks, you can add settings per behavior and/or model behavior
attachment. Information about specifying settings can be found in the chapters about core behaviors and
their configuration.
A quick example that illustrates how behavior settings can be passed from the model to the behavior:
Code View
class Post extends AppModel {
var $name = 'Post'
var $actsAs = array(
'YourBehavior' => array(
'option1_key' => 'option1_value'));
}
As of 1.2.8004, CakePHP adds those settings once per model/alias only. To keep your behavior upgradable
you should respect aliases (or models).
3.9 DataSources
DataSources are the link between models and the source of data that models represent. In many cases,
the data is retrieved from a relational database such as MySQL, PostgreSQL or MSSQL. CakePHP is
distributed with several database-specific datasources (see the dbo_* class files in
cake/libs/model/datasources/dbo/), a summary of which is listed here for your convenience:
• dbo_adodb.php
• dbo_db2.php
• dbo_firebird.php
• dbo_mssql.php
• dbo_mysql.php
• dbo_mysqli.php
• dbo_odbc.php
• dbo_oracle.php
• dbo_postgres.php
• dbo_sqlite.php
• dbo_sybase.php
• describe($model)
• listSources()
• At least one of:
3.9.2 An Example
Here is a simple example of how to use Datasources and HttpSocket to implement a very basic Twitter
source that allows querying the Twitter API as well as posting new status updates to a configured account.
This example will only work in PHP 5.2 and above, due to the use of json_decode for the parsing of JSON
formatted data.
You would place the Twitter datasource in app/models/datasources/twitter_source.php:
Code View
<?php
/**
* Twitter DataSource
*
* Used for reading and writing to Twitter, through models.
*
* PHP Version 5.x
*
* CakePHP(tm) : Rapid Development Framework (http://www.cakephp.org)
* Copyright 2005-2009, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @filesource
* @copyright Copyright 2009, Cake Software Foundation, Inc. (http://www.cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
App::import('Core', 'HttpSocket');
class TwitterSource extends DataSource {
protected $_schema = array(
'tweets' => array(
'id' => array(
'type' => 'integer',
'null' => true,
'key' => 'primary',
'length' => 11,
),
'text' => array(
'type' => 'string',
'null' => true,
'key' => 'primary',
'length' => 140
),
'status' => array(
'type' => 'string',
'null' => true,
'key' => 'primary',
'length' => 140
),
)
);
public function __construct($config) {
$auth = "{$config['login']}:{$config['password']}";
$this->connection = new HttpSocket(
"http://{$auth}@twitter.com/"
);
parent::__construct($config);
}
public function listSources() {
return array('tweets');
}
public function read($model, $queryData = array()) {
if (!isset($queryData['conditions']['username'])) {
$queryData['conditions']['username'] = $this->config['login'];
}
$url = "/statuses/user_timeline/";
$url .= "{$queryData['conditions']['username']}.json";
And the configuration settings in your app/config/database.php would resemble something like this:
Code View
<?php
var $twitter = array(
'datasource' => 'twitter',
'login' => 'username',
'password' => 'password',
);
?>
Using the familiar model methods from a controller:
Code View
<?php
// Will use the username defined in the $twitter as shown above:
$tweets = $this->Tweet->find('all');
CakePHP view files are written in plain PHP and have a default extension of .ctp (CakePHP Template).
These files contain all the presentational logic needed to get the data it received from the controller in a
format that is ready for the audience you’re serving to.
View files are stored in /app/views/, in a folder named after the controller that uses the files, and named
after the action it corresponds to. For example, the view file for the Products controller's "view()" action,
would normally be found in /app/views/products/view.ctp.
The view layer in CakePHP can be made up of a number of different parts. Each part has different uses,
and will be covered in this chapter:
• layouts: view files that contain presentational code that is found wrapping many interfaces in your
application. Most views are rendered inside of a layout.
• elements: smaller, reusable bits of view code. Elements are usually rendered inside of views.
• helpers: these classes encapsulate view logic that is needed in many places in the view layer.
Among other things, helpers in CakePHP can help you build forms, build AJAX functionality,
paginate model data, or serve RSS feeds.
3.10.2 Layouts
A layout contains presentation code that wraps around a view. Anything you want to see in all of your
views should be placed in a layout.
Layout files should be placed in /app/views/layouts. CakePHP's default layout can be overridden by
creating a new default layout at /app/views/layouts/default.ctp. Once a new default layout has been
created, controller-rendered view code is placed inside of the default layout when the page is rendered.
When you create a layout, you need to tell CakePHP where to place the code for your views. To do so,
make sure your layout includes a place for $content_for_layout (and optionally, $title_for_layout). Here's
an example of what a default layout might look like:
Code View
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><?php echo $title_for_layout?></title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<!-- Include external files and scripts here (See HTML helper for more info.) -->
<?php echo $scripts_for_layout ?>
</head>
<body>
</body>
</html>
$scripts_for_layout contains any external files and scripts included with the built-in HTML helper. Useful for
including javascript and CSS files from views.
When using $html->css() or $javascript->link() in view files, specify 'false' for the 'in-line' argument to
place the html source in $scripts_for_layout. (See API for more details on usage).
$content_for_layout contains the view. This is where the view code will be placed.
$title_for_layout contains the page title.
To set the title for the layout, it's easiest to do so in the controller, using the $pageTitle controller variable.
Code View
<?php
For example, if a section of my site included a smaller ad banner space, I might create a new layout with
the smaller advertising space and specify it as the layout for all controller's actions using something like:
function viewImage() {
$this->layout = 'image';
//output user image
}
}
?>
CakePHP features two core layouts (besides CakePHP’s default layout) you can use in your own
application: ‘ajax’ and ‘flash’. The Ajax layout is handy for crafting Ajax responses - it’s an empty layout
(most ajax calls only require a bit of markup in return, rather than a fully-rendered interface). The flash
layout is used for messages shown by the controllers flash() method.
Three other layouts xml, js, and rss exist in the core for a quick and easy way to serve up content that
isn’t text/html.
3.10.3 Elements
Many applications have small blocks of presentation code that need to be repeated from page to page,
sometimes in different places in the layout. CakePHP can help you repeat parts of your website that need
to be reused. These reusable parts are called Elements. Ads, help boxes, navigational controls, extra
menus, login forms, and callouts are often implemented in CakePHP as elements. An element is basically
a mini-view that can be included in other views, in layouts, and even within other elements. Elements can
be used to make a view more readable, placing the rendering of repeating elements in its own file. They
can also help you re-use content fragments in your application.
Elements live in the /app/views/elements/ folder, and have the .ctp filename extension. They are output
using the element method of the view.
Code View
<?php echo $this->element('helpbox'); ?>
echo $this->element('helpbox', array('cache' => array('key' => 'second_use', 'time' => '+1 day'), 'var'
=> $differentVar));
?>
The above will ensure that both element results are cached separately.
3.10.4.1 set()
set(string $var, mixed $value)
Views have a set() method that is analogous to the set() found in Controller objects. It allows you to add
variables to the viewVars. Using set() from your view file will add the variables to the layout and elements
that will be rendered later. See Controller::set() for more information on using set().
In your view file you can do
Code View
$this->set('activeMenuButton', 'posts');
Then in your layout the $activeMenuButton variable will be available and contain the value 'posts'.
3.10.4.2 getVar()
getVar(string $var)
Gets the value of the viewVar with the name $var
3.10.4.3 getVars()
getVars()
Gets a list of all the available view variables in the current rendering scope. Returns an array of variable
names.
3.10.4.4 error()
error(int $code, string $name, string $message)
Displays an error page to the user. Uses layouts/error.ctp to render the page.
Code View
$this->error(404, 'Not found', 'This page was not found, sorry');
This will render an error page with the title and messages specified. Its important to note that script
execution is not stopped by View::error() So you will have to stop code execution yourself if you want to
halt the script.
3.10.4.5 element()
element(string $elementPath, array $data, bool $loadHelpers)
Renders an element or view partial. See the section on View Elements for more information and
examples.
3.10.4.6 uuid()
uuid(string $object, mixed $url)
Generates a unique non-random DOM ID for an object, based on the object type and url. This method is
often used by helpers that need to generate unique DOM ID's for elements such as the AjaxHelper.
Code View
$uuid = $this->uuid('form', array('controller' => 'posts', 'action' => 'index'));
//$uuid contains 'form0425fe3bad'
3.10.4.7 addScript()
addScript(string $name, string $content)
Adds content to the internal scripts buffer. This buffer is made available in the layout as
$scripts_for_layout. This method is helpful when creating helpers that need to add javascript or css
directly to the layout. Keep in mind that scripts added from the layout, or elements in the layout will not
be added to $scripts_for_layout. This method is most often used from inside helpers, like the Javascript
and Html Helpers.
3.10.5 Themes
You can take advantage of themes, making it easy to switch the look and feel of your page quickly and
easily.
To use themes, you need to tell your controller to use the ThemeView class instead of the default View
class.
Code View
class ExampleController extends AppController {
var $view = 'Theme';
}
To declare which theme to use by default, specify the theme name in your controller.
Code View
class ExampleController extends AppController {
var $view = 'Theme';
var $theme = 'example';
}
You can also set or change the theme name within an action or within the beforeFilter or beforeRender
callback functions.
Code View
$this->theme = 'another_example';
Theme view files need to be within the /app/views/themed/ folder. Within the themed folder, create a
folder using the same name as your theme name. Beyond that, the folder structure within the
/app/views/themed/example/ folder is exactly the same as /app/views/.
For example, the view file for an edit action of a Posts controller would reside at
/app/views/themed/example/posts/edit.ctp. Layout files would reside in
/app/views/themed/example/layouts/.
If a view file can't be found in the theme, CakePHP will try to locate the view file in the /app/views/ folder.
This way, you can create master view files and simply override them on a case-by-case basis within your
theme folder.
If you have CSS or JavaScript files that are specific to your theme, you can store them in a themed folder
within webroot. For example, your stylesheets would be stored in /app/webroot/themed/example/css/ and
your JavaScript files would be stored in /app/webroot/themed/example/js/.
All of CakePHP's built-in helpers are aware of themes and will create the correct paths automatically. Like
view files, if a file isn't in the theme folder, it'll default to the main webroot folder.
3.10.6 Media Views
Media views allow you to send binary files to the user. For example, you may wish to have a directory of
files outside of the webroot to prevent users from direct linking them. You can use the Media view to pull
the file from a special folder within /app/, allowing you to perform authentication before delivering the file
to the user. To use the Media view, you need to tell your controller to use the MediaView class instead of
the default View class. After that, just pass in additional parameters to specify where your file is located.
Code View
class ExampleController extends AppController {
function download () {
$this->view = 'Media';
$params = array(
'id' => 'example.zip',
'name' => 'example',
'download' => true,
'extension' => 'zip',
'path' => APP . 'files' . DS
);
$this->set($params);
}
}
Here's an example of rendering a file who's mime type is not included in the MediaView's $mimeType
array.
Code View
function download () {
$this->view = 'Media';
$params = array(
'id' => 'example.docx',
'name' => 'example',
'extension' => 'docx',
'mimeType' => array('docx' => 'application/vnd.openxmlformats-
officedocument.wordprocessingml.document'),
'path' => APP . 'files' . DS
);
$this->set($params);
}
Parameters Description
id The ID is the file name as it resides on the file server including the file extension.
The name allows you to specify an alternate file name to be sent to the user. Specify the name without the
name
file extension.
A boolean value indicating whether headers should be set to force download. Note that your controller's
download
autoRender option should be set to false for this to work correctly.
The file extension. This is matched against an internal list of acceptable mime types. If the mime type
extension
specified is not in the list (or sent in the mimeType parameter array), the file will not be downloaded.
The folder name, including the final directory separator. The path should be absolute, but can be relative to
path
the APP/webroot folder.
mimeType An array with additional mime types to be merged with MediaView internal list of acceptable mime types.
A boolean or integer value - If set to true it will allow browsers to cache the file (defaults to false if not
cache
set); otherwise set it to the number of seconds in the future for when the cache should expire.
Last modified date/time of the file in a format compatible with strtotime(). Defaults to the current time if not
modified
set. Example: 'modified' => '@' . filemtime($filepath)
3.11 Helpers
Helpers are the component-like classes for the presentation layer of your application. They contain
presentational logic that is shared between many views, elements, or layouts. This chapter will show you
how to create your own helpers, and outline the basic tasks CakePHP’s core helpers can help you
accomplish. For more information on core helpers, check out Built-in Helpers.
Let's say we wanted to create a helper that could be used to output a specifically crafted CSS-styled link
you needed many different places in your application. In order to fit your logic in to CakePHP's existing
helper structure, you'll need to create a new class in /app/views/helpers. Let's call our helper LinkHelper.
The actual PHP class file would look something like this:
Code View
<?php
/* /app/views/helpers/link.php */
?>
There are a few methods included in CakePHP's Helper class you might want to take advantage of:
output(string $string)
Use this function to hand any data back to your view.
Code View
<?php
function makeEdit($title, $url) {
// Use the helper's output function to hand formatted
// data back to the view:
return $this->output(
"<div class=\"editOuter\">
<a href=\"$url\" class=\"edit\">$title</a>
</div>"
);
}
?>
Once your controller has been made aware of this new class, you can use it in your views by accessing a
variable named after the helper:
Code View
<!-- make a link using the new helper -->
<?php echo $link->makeEdit('Change this Recipe', '/recipes/edit/5') ?>
The Html, Form and Session (If sessions are enabled) helpers are always available.
All that’s needed to create a scaffold is a model and its controller. Once you set the $scaffold variable in
the controller, you’re up and running.
CakePHP’s scaffolding is pretty cool. It allows you to get a basic CRUD application up and going in
minutes. So cool that you'll want to use it in production apps. Now, we think its cool too, but please realize
that scaffolding is... well... just scaffolding. It's a loose structure you throw up real quick during the
beginning of a project in order to get started. It isn't meant to be completely flexible, it’s meant as a
temporary way to get up and going. If you find yourself really wanting to customize your logic and your
views, its time to pull your scaffolding down in order to write some code. CakePHP’s Bake console,
covered in the next section, is a great next step: it generates all the code that would produce the same
result as the most current scaffold.
Scaffolding is a great way of getting the early parts of developing a web application started. Early
database schemas are subject to change, which is perfectly normal in the early part of the design
process. This has a downside: a web developer hates creating forms that never will see real use. To
reduce the strain on the developer, scaffolding has been included in CakePHP. Scaffolding analyzes your
database tables and creates standard lists with add, delete and edit buttons, standard forms for editing
and standard views for inspecting a single item in the database.
To add scaffolding to your application, in the controller, add the $scaffold variable:
Code View
<?php
?>
Assuming you’ve created even the most basic Category model class file (in /app/models/category.php),
you’re ready to go. Visit http://example.com/categories to see your new scaffold.
Creating methods in controllers that are scaffolded can cause unwanted results. For example, if you
create an index() method in a scaffolded controller, your index method will be rendered rather than the
scaffolding functionality.
Scaffolding is knowledgeable about model associations, so if your Category model belongsTo a User, you’ll
see related User IDs in the Category listings. If you’d rather see something besides an ID (like the user’s
first name), you can set the $displayField variable in the model.
Let’s set the $displayField variable in our User class so that users related to categories will be shown by
first name rather than just an ID in scaffolding. This feature makes scaffolding more readable in many
instances.
Code View
<?php
?>
3.12.1 Creating a simple admin interface
with scaffolding
If you have enabled admin routing in your app/config/core.php, with Configure::write('Routing.admin',
'admin'); you can use scaffolding to generate an admin interface.
Once you have enabled admin routing assign your admin prefix to the scaffolding variable.
Code View
var $scaffold = 'admin';
You will now be able to access admin scaffolded actions:Code View
http://example.com/admin/controller/index
http://example.com/admin/controller/view
http://example.com/admin/controller/edit
http://example.com/admin/controller/add
http://example.com/admin/controller/delete
This is an easy way to create a simple backend interface quickly. Keep in mind that you cannot have both
admin and non-admin methods scaffolded at the same time. As with normal scaffolding you can override
individual methods and replace them with your own.
Code View
function admin_view($id = null) {
//custom code here
}
Once you have replaced a scaffolded action you will need to create a view file for the action as well.
/app/views/posts/scaffold.index.ctp
/app/views/posts/scaffold.show.ctp
/app/views/posts/scaffold.edit.ctp
/app/views/posts/scaffold.new.ctp
Custom scaffolding views for all controllers should be placed like so:
/app/views/scaffolds/index.ctp
/app/views/scaffolds/show.ctp
/app/views/scaffolds/edit.ctp
/app/views/scaffolds/new.ctp
/app/views/scaffolds/add.ctp
3.13 The CakePHP Console
This section provides an introduction into CakePHP at the command-line. If you’ve ever needed access to
your CakePHP MVC classes in a cron job or other command-line script, this section is for you.
PHP provides a powerful client that makes interfacing with your file system and applications much
smoother. The CakePHP console provides a framework for creating shell scripts. The Console uses a
dispatcher-type setup to load a shell or task, and hand it its parameters.
A command-line (CLI) build of PHP must be available on the system if you plan to use the Console.
Before we get into specifics, let’s make sure we can run the CakePHP Console. First, you’ll need to bring
up a system shell. The examples shown in this section will be in bash, but the CakePHP Console is
Windows-compatible as well. Let’s execute the Console program from bash. This example assumes that
the user is currently logged into a bash prompt and is currently at the root of a CakePHP installation.
You can technically run the console using something like this:
$ cd /my/cake/app_folder
$ ../cake/console/cake
But the preferred usage is adding the console directory to your path so you can use the cake command
anywhere:
$ cake
Running the Console with no arguments produces this help message:
Hello user,
Changing Paths:
your working path should be the same as your application path
to change your path use the '-app' param.
Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp
Available Shells:
app/vendors/shells/:
- none
vendors/shells/:
- none
cake/console/libs/:
acl
api
bake
console
extract
function main() {
}
}
?>
Once we've added our model to the $uses array, we can use it in the main() method. In this example, our
Order model should now be accessible as $this->Order in the main() method of our new shell.
$total += $order['Order']['amount'];
}
$ cake report
where report is the name of the shell file in /vendor/shells/ without the .php extension. This should yield
something like:
Hello user,
Welcome to CakePHP v1.2 Console
---------------------------------------------------------------
App : app
Path: /path/to/cake/app
---------------------------------------------------------------
Order date: 2007-07-30 10:31:12
Amount: $42.78
----------------------------------------
Order date: 2007-07-30 21:16:03
Amount: $83.63
----------------------------------------
Order date: 2007-07-29 15:52:42
Amount: $423.26
----------------------------------------
Order date: 2007-07-29 01:42:22
Amount: $134.52
----------------------------------------
Order date: 2007-07-29 01:40:52
Amount: $183.56
----------------------------------------
Total: $867.75
3.13.1.2 Tasks
Tasks are small extensions to shells. They allow logic to be shared between shells, and are added to shells
by using the special $tasks class variable. For example in the core bake shell, there are a number of tasks
defined:
Code View
<?php
class BakeShell extends Shell {
var $tasks = array('Project', 'DbConfig', 'Model', 'View', 'Controller');
}
?>
Tasks are stored in /vendors/shells/tasks/ in files named after their classes. So if we were to create a new
‘cool’ task. Class CoolTask (which extends Shell) would be placed in /vendors/shells/tasks/cool.php. Class
VeryCoolTask (which extends Shell) would be placed in /vendors/shells/tasks/very_cool.php.
Each task must at least implement an execute() method - shells will call this method to start the task
logic.
Code View
<?php
class SoundTask extends Shell {
var $uses = array('Model'); // same as controller var $uses
function execute() {}
}
?>
You can access tasks inside your shell classes and execute them there:
Code View
<?php
class SeaShell extends Shell { // found in /vendors/shells/sea.php
var $tasks = array('Sound'); //found in /vendors/shells/tasks/sound.php
function main() {
$this->Sound->execute();
}
}
?>
You can also access tasks directly from the command line:
3.14 Plugins
CakePHP allows you to set up a combination of controllers, models, and views and release them as a
packaged application plugin that others can use in their CakePHP applications. Have a sweet user
management module, simple blog, or web services module in one of your applications? Package it as a
CakePHP plugin so you can pop it into other applications.
The main tie between a plugin and the application it has been installed into, is the application's
configuration (database connection, etc.). Otherwise, it operates in its own little space, behaving much
like it would if it were an application on its own.
Please note that the process of creating plugins can be greatly simplified by using the Cake shell.
While it isn't required, it is recommended that you name your plugin controllers something relatively
unique in order to avoid namespace conflicts with parent applications. Its not a stretch to think that a
parent application might have a UsersController, OrdersController, or ProductsController: so you might
want to be creative with controller names, or prepend the name of the plugin to the classname
(PizzaOrdersController, in this case).
So, we place our new PizzaOrdersController in /app/plugins/pizza/controllers and it looks like so:
Code View
// /app/plugins/pizza/controllers/pizza_orders_controller.php
class PizzaOrdersController extends PizzaAppController {
var $name = 'PizzaOrders';
var $uses = array('Pizza.PizzaOrder');
function index() {
//...
}
}
This controller extends the plugin's AppController (called PizzaAppController) rather than the parent
application's AppController.
Also note how the name of the model is prefixed with the name of the plugin. This line of code is added
for clarity but is not necessary for this example.
If you want to access what we’ve got going thus far, visit /pizza/pizza_orders. You should get a “Missing
Model” error because we don’t have a PizzaOrder model defined yet.
If you need to reference a model within your plugin, you need to include the plugin name with the model
name, separated with a dot.
For example:
Code View
// /app/plugins/pizza/models/example_model.php:
class ExampleModel extends PizzaAppModel {
var $name = 'ExampleModel';
var $hasMany = array('Pizza.PizzaOrder');
}
?>
If you would prefer that the array keys for the association not have the plugin prefix on them, use the
alternative syntax:
Code View
// /app/plugins/pizza/models/example_model.php:
class ExampleModel extends PizzaAppModel {
var $name = 'ExampleModel';
var $hasMany = array(
'PizzaOrder' => array(
'className' => 'Pizza.PizzaOrder'
)
);
}
?>
« Plugin Controllers | Plugin Views »
Building these components is exactly the same as building it within a regular application, with no special
naming convention. Referring to your components from within the plugin also does not require any special
reference.
Code View
// Component
class ExampleComponent extends Object {
The above are examples of how to link to images, javascript and CSS files for your plugin.
It is important to note the /your_plugin/ prefix before the img, js or css path. That makes the magic
happen!
The method above is valid when mod_rewrite is used.
Once a plugin has been installed in /app/plugins, you can access it at the URL
/pluginname/controllername/action. In our pizza ordering plugin example, we'd access our
PizzaOrdersController at /pizza/pizzaOrders.
• When you don't have a [Plugin]AppController and [Plugin]AppModel, you'll get missing Controller
errors when trying to access a plugin controller.
• You can have a default controller with the name of your plugin. If you do that, you can access it
via /[plugin]/action. For example, a plugin named 'users' with a controller named UsersController
can be accessed at /users/add if there is no controller called AddController in your
[plugin]/controllers folder.
• You can define your own layouts for plugins, inside app/plugins/[plugin]/views/layouts. Otherwise,
plugins will use the layouts from the /app/views/layouts folder by default.
• You can do inter-plugin communication by using $this->requestAction('/plugin/controller/action');
in your controllers.
• If you use requestAction, make sure controller and model names are as unique as possible.
Otherwise you might get PHP "redefined class ..." errors.
We’ll also cover some of the constants available in CakePHP applications. Using these constants will help
make upgrades more smooth, but are also convenient ways to point to certain files or directories in your
CakePHP application.
3.15.1.1 __
__(string $string_id, boolean $return = false)
This function handles localization in CakePHP applications. The $string_id identifies the ID for a
translation, and the second parameter allows you to have the function automatically echo the string (the
default behavior), or return it for further processing (pass a boolean true to enable this behavior).
Check out the Localization & Internationalization section for more information.
3.15.1.2 a
a(mixed $one, $two, $three...)
Returns an array of the parameters used to call the wrapping function.
Code View
print_r(a('foo', 'bar'));
// output:
array(
[0] => 'foo',
[1] => 'bar'
)
3.15.1.3 aa
aa(string $one, $two, $three...)
Used to create associative arrays formed from the parameters used to call the wrapping function.
Code View
print_r(aa('a','b'));
// output:
array(
'a' => 'b'
)
3.15.1.4 am
am(array $one, $two, $three...)
Merges all the arrays passed as parameters and returns the merged array.
3.15.1.5 config
Can be used to load files from your application config-folder via include_once. Function checks for
existance before include and returns boolean. Takes an optional number of arguments.
Example: config('some_file', 'myconfig');
3.15.1.6 convertSlash
convertSlash(string $string)
Converts forward slashes to underscores and removes the first and last underscores in a string. Returns
the converted string.
3.15.1.7 countdim
countdim(array $array)
Returns the number of dimensions in the supplied array.
3.15.1.8 debug
debug(mixed $var, boolean $showHtml = false)
If the application's DEBUG level is non-zero, $var is printed out. If $showHTML is true, the data is rendered
to be browser-friendly.
3.15.1.9 e
e(mixed $data)
Convenience wrapper for echo().
3.15.1.10 env
env(string $key)
Gets an environment variable from available sources. Used as a backup if $_SERVER or $_ENV are
disabled.
This function also emulates PHP_SELF and DOCUMENT_ROOT on unsupporting servers. In fact, it's a good
idea to always use env() instead of $_SERVER or getenv()(especially if you plan to distribute the code),
since it's a full emulation wrapper.
3.15.1.11 fileExistsInPath
fileExistsInPath(string $file)
Checks to make sure that the supplied file is within the current PHP include_path. Returns a boolean
result.
3.15.1.12 h
h(string $text, string $charset = null)
Convenience wrapper for htmlspecialchars().
3.15.1.13 ife
ife($condition, $ifNotEmpty, $ifEmpty)
Used for ternary-like operations. If the $condition is non-empty, $ifNotEmpty is returned, else $ifEmpty is
returned.
3.15.1.14 low
low(string $string)
Convenience wrapper for strtolower().
3.15.1.15 paths
paths()
Get CakePHP basic paths as an indexed array. Resulting array will contain array of paths indexed by:
Models, Behaviors, Controllers, Components, and Helpers.
This has been Deprecated and is no longer available in RC2. Use Configure::corePaths(); instead.
3.15.1.16 pr
pr(mixed $var)
Convenience wrapper for print_r(), with the addition of wrapping <pre> tags around the output.
3.15.1.17 r
r(string $search, string $replace, string $subject)
Convenience wrapper for str_replace().
3.15.1.18 stripslashes_deep
stripslashes_deep(array $value)
Recursively strips slashes from the supplied $value. Returns the modified array.
3.15.1.19 up
up(string $string)
Convenience wrapper for strtoupper().
3.15.1.20 uses
uses(string $lib1, $lib2, $lib3...)
Used to load CakePHP's core libraries (found in cake/libs/). Supply the name of the library's file name
without the '.php' extension.
There are many different aspects to the validation process. What we’ll cover in this section is the model
side of things. Essentially: what happens when you call the save() method of your model. For more
information about how to handle the displaying of validation errors, check out the section covering
FormHelper.
The first step to data validation is creating the validation rules in the Model. To do that, use the
Model::validate array in the Model definition, for example:
Code View
<?php
class User extends AppModel {
var $name = 'User';
var $validate = array();
}
?>
In the example above, the $validate array is added to the User Model, but the array contains no validation
rules. Assuming that the users table has login, password, email and born fields, the example below shows
some simple validation rules that apply to those fields:
Code View
<?php
class User extends AppModel {
var $name = 'User';
var $validate = array(
'login' => 'alphaNumeric',
'email' => 'email',
'born' => 'date'
);
}
?>
This last example shows how validation rules can be added to model fields. For the login field, only letters
and numbers will be accepted, the email should be valid, and born should be a valid date. Defining
validation rules enables CakePHP’s automagic showing of error messages in forms if the data submitted
does not follow the defined rules.
CakePHP has many validation rules and using them can be quite easy. Some of the built-in rules allow you
to verify the formatting of emails, URLs, and credit card numbers – but we’ll cover these in detail later on.
Here is a more complex validation example that takes advantage of some of these built-in validation
rules:
Code View
<?php
class User extends AppModel {
var $name = 'User';
var $validate = array(
'login' => array(
'alphaNumeric' => array(
'rule' => 'alphaNumeric',
'required' => true,
'message' => 'Alphabets and numbers only'
),
'between' => array(
'rule' => array('between', 5, 15),
'message' => 'Between 5 to 15 characters'
)
),
'password' => array(
'rule' => array('minLength', '8'),
'message' => 'Mimimum 8 characters long'
),
'email' => 'email',
'born' => array(
'rule' => 'date',
'message' => 'Enter a valid date',
'allowEmpty' => true
)
);
}
?>
Two validation rules are defined for login: it should contain letters and numbers only, and its length should
be between 5 and 15. The password field should be a minimum of 8 characters long. The email should be
a valid email address, and born should be a valid date. Also, notice how you can define specific error
messages that CakePHP will use when these validation rules fail.
As the example above shows, a single field can have multiple validation rules. And if the built-in rules do
not match your criteria, you can always add your own validation rules as required.
Now that you’ve seen the big picture on how validation works, let’s look at how these rules are defined in
the model. There are three different ways that you can define validation rules: simple arrays, single rule
per field, and multiple rules per field.
For example, to ensure that the user is giving a well formatted email address, you could use this rule:
Code View
var $validate = array('user_email' => 'email');
As you can see here, each field (only one field shown above) is associated with an array that contains five
keys: ‘rule’, ‘required’, ‘allowEmpty’, ‘on’ and ‘message’. Let’s have a closer look at these keys.
4.1.2.1 rule
The 'rule' key defines the validation method and takes either a single value or an array. The specified
'rule' may be the name of a method in your model, a method of the core Validation class, or a regular
expression. For more information on the rules available by default, see Core Validation Rules.
If the rule does not require any parameters, 'rule' can be a single value e.g.:
Code View
var $validate = array(
'login' => array(
'rule' => 'alphaNumeric'
)
);
If the rule requires some parameters (like the max, min or range), 'rule' should be an array:
Code View
var $validate = array(
'password' => array(
'rule' => array('minLength', 8)
)
);
Remember, the 'rule' key is required for array-based rule definitions.
4.1.2.2 required
This key should be assigned to a boolean value. If ‘required’ is true, the field must be present in the data
array. For example, if the validation rule has been defined as follows:
Code View
var $validate = array(
'login' => array(
'rule' => 'alphaNumeric',
'required' => true
)
);
The data sent to the model’s save() method must contain data for the login field. If it doesn’t, validation
will fail. The default value for this key is boolean false.
required => true does not mean the same as the validation rule notEmpty(). required => true indicates
that the array key must be present - it does not mean it must have a value. Therefore validation will fail if
the field is not present in the dataset, but may (depending on the rule) succeed if the value submitted is
empty ('').
4.1.2.3 allowEmpty
If set to false, the field value must be "nonempty", where "nonempty" is defined as !empty($value) ||
is_numeric($value). The numeric check is so that CakePHP does the right thing when $value is zero.
The difference between required and allowEmpty can be confusing. 'required' => true means that you
cannot save the model without the key for this field being present in $this->data (the check is performed
with isset); whereas, 'allowEmpty' => false makes sure that the current field value is "nonempty", as
described above.
4.1.2.4 on
The ‘on’ key can be set to either one of the following values: ‘update’ or ‘create’. This provides a
mechanism that allows a certain rule to be applied either during the creation of a new record, or during
update of a record.
If a rule has defined ‘on’ => ‘create’, the rule will only be enforced during the creation of a new record.
Likewise, if it is defined as ‘on’ => ‘update’, it will only be enforced during the updating of a record.
The default value for ‘on’ is null. When ‘on’ is null, the rule will be enforced during both creation and
update.
4.1.2.5 message
The ‘message’ key allows you to define a custom validation error message for the rule:
Code View
var $validate = array(
'password' => array(
'rule' => array('minLength', 8),
'message' => 'Password must be at least 8 characters long'
)
);
4.1.2.6 last
Setting the 'last' key to true will cause the validator to stop on the rule if it fails instead of continuing with
the next rule. This is handy if you want validation to stop if the field is notEmpty in a multi-rule field.
Code View
var $validate = array(
'username' => array(
'usernameRule-1' => array(
'rule' => 'notEmpty',
'message' => 'Please enter a username.',
'last' => true
),
'usernameRule-2' => array(
'rule' => array('minLength', 8),
'message' => 'Minimum length of 8 characters.'
)
)
);
The default value for 'last' is false.
If you would like to assign multiple validation rules to a single field, this is basically how it should look:
Code View
By default CakePHP tries to validate a field using all the validation rules declared for it and returns the
error message for the last failing rule. But if the key last is set totrue for a rule and it fails, then the error
message for that rule is returned and further rules are not validated. So if you prefer to show the error
message for the first failing rule then set 'last' => true for each rule.
If you plan on using internationalized error messages, you may want to specify error messages in your
view instead:
Code View
echo $form->input('login', array(
'label' => __('Login', true),
'error' => array(
'loginRule-1' => __('Only alphabets and numbers allowed', true),
'loginRule-2' => __('Minimum length of 8 characters', true)
)
)
);
The field is now fully internationalized, and you are able to remove the messages from the model. For
more information on the __() function, see Localization & Internationalization
4.1.4.1 alphaNumeric
The data for the field must only contain letters and numbers.
Code View
var $validate = array(
'login' => array(
'rule' => 'alphaNumeric',
'message' => 'Usernames must only contain letters and numbers.'
)
);
4.1.4.2 between
The length of the data for the field must fall within the specified numeric range. Both minimum and
maximum values must be supplied. Uses <= not < .
Code View
var $validate = array(
'password' => array(
'rule' => array('between', 5, 15),
'message' => 'Passwords must be between 5 and 15 characters long.'
)
);
The length of data is "the number of bytes in the string representation of the data". Be careful that it may
be larger than the number of characters when handling non-ASCII characters.
4.1.4.3 blank
This rule is used to make sure that the field is left blank or only white space characters are present in its
value. White space characters include space, tab, carriage return, and newline.
Code View
var $validate = array(
'id' => array(
'rule' => 'blank',
'on' => 'create'
)
);
4.1.4.4 boolean
The data for the field must be a boolean value. Valid values are true or false, integers 0 or 1 or strings '0'
or '1'.
Code View
var $validate = array(
'myCheckbox' => array(
'rule' => array('boolean'),
'message' => 'Incorrect value for myCheckbox'
)
);
4.1.4.5 cc
This rule is used to check whether the data is a valid credit card number. It takes three parameters: ‘type’,
‘deep’ and ‘regex’.
The ‘type’ key can be assigned to the values of ‘fast’, ‘all’ or any of the following:
• amex
• bankcard
• diners
• disc
• electron
• enroute
• jcb
• maestro
• mc
• solo
• switch
• visa
• voyager
If ‘type’ is set to ‘fast’, it validates the data against the major credit cards’ numbering formats. Setting
‘type’ to ‘all’ will check with all the credit card types. You can also set ‘type’ to an array of the types you
wish to match.
The ‘deep’ key should be set to a boolean value. If it is set to true, the validation will check the Luhn
algorithm of the credit card (http://en.wikipedia.org/wiki/Luhn_algorithm). It defaults to false.
The ‘regex’ key allows you to supply your own regular expression that will be used to validate the credit
card number.
Code View
var $validate = array(
'ccnumber' => array(
'rule' => array('cc', array('visa', 'maestro'), false, null),
'message' => 'The credit card number you supplied was invalid.'
)
);
4.1.4.6 comparison
Comparison is used to compare numeric values. It supports “is greater”, “is less”, “greater or equal”,
“less or equal”, “is less”, “equal to”, and “not equal”. Some examples are shown below:
Code View
var $validate = array(
'age' => array(
'rule' => array('comparison', '>=', 18),
'message' => 'Must be at least 18 years old to qualify.'
)
);
• ‘dmy’ e.g. 27-12-2006 or 27-12-06 (separators can be a space, period, dash, forward slash)
• ‘mdy’ e.g. 12-27-2006 or 12-27-06 (separators can be a space, period, dash, forward slash)
• ‘ymd’ e.g. 2006-12-27 or 06-12-27 (separators can be a space, period, dash, forward slash)
• ‘dMy’ e.g. 27 December 2006 or 27 Dec 2006
• ‘Mdy’ e.g. December 27, 2006 or Dec 27, 2006 (comma is optional)
• ‘My’ e.g. (December 2006 or Dec 2006)
• ‘my’ e.g. 12/2006 or 12/06 (separators can be a space, period, dash, forward slash)
If no keys are supplied, the default key that will be used is ‘ymd’.
Code View
var $validate = array(
'born' => array(
'rule' => 'date',
'message' => 'Enter a valid date in YY-MM-DD format.',
'allowEmpty' => true
)
);
While many data stores require a certain date format, you might consider doing the heavy lifting by
accepting a wide-array of date formats and trying to convert them, rather than forcing users to supply a
given format. The more work you can do for your users, the better.
4.1.4.8 decimal
This rule ensures that the data is a valid decimal number. A parameter can be passed to specify the
number of digits required after the decimal point. If no parameter is passed, the data will be validated as
a scientific float, which will cause validation to fail if no digits are found after the decimal point.
Code View
var $validate = array(
'price' => array(
'rule' => array('decimal', 2)
)
);
4.1.4.9 email
This checks whether the data is a valid email address. Passing a boolean true as the second parameter for
this rule will also attempt to verify that the host for the address is valid.
Code View
var $validate = array('email' => array('rule' => 'email'));
4.1.4.11 extension
• Edit
• View just this section
• Comments (0)
• History
This rule checks for valid file extensions like .jpg or .png. Allow multiple extensions by passing them in
array form.
Code View
var $validate = array(
'image' => array(
'rule' => array('extension', array('gif', 'jpeg', 'png', 'jpg')),
'message' => 'Please supply a valid image.'
)
);
4.1.4.12 file
• Edit
• View just this section
• Comments (2)
• History
This rule ensures that the value is a valid file name. This validation rule is currently non-functional.
4.1.4.13 ip
• Edit
• View just this section
• Comments (0)
• History
This rule will ensure that a valid IPv4 address has been submitted.
Code View
var $validate = array(
'clientip' => array(
'rule' => 'ip',
'message' => 'Please supply a valid IP address.'
)
);
4.1.4.14 isUnique
• Edit
• View just this section
• Comments (4)
• History
The data for the field must be unique, it cannot be used by any other rows.
Code View
var $validate = array(
'login' => array(
'rule' => 'isUnique',
'message' => 'This username has already been taken.'
)
);
4.1.4.15 minLength
• Edit
• View just this section
• Comments (3)
• History
This rule ensures that the data meets a minimum length requirement.
Code View
var $validate = array(
'login' => array(
'rule' => array('minLength', 8),
'message' => 'Usernames must be at least 8 characters long.'
)
);
The length here is "the number of bytes in the string representation of the data". Be careful that it may be
larger than the number of characters when handling non-ASCII characters.
4.1.4.16 maxLength
• Edit
• View just this section
• Comments (0)
• History
This rule ensures that the data stays within a maximum length requirement.
Code View
var $validate = array(
'login' => array(
'rule' => array('maxLength', 15),
'message' => 'Usernames must be no larger than 15 characters long.'
)
);
The length here is "the number of bytes in the string representation of the data". Be careful that it may be
larger than the number of characters when handling non-ASCII characters.
4.1.4.17 money
• Edit
• View just this section
• Comments (2)
• History
This rule will ensure that the value is in a valid monetary amount.
4.1.4.18 multiple
• Edit
• View just this section
• Comments (5)
• History
Use this for validating a multiple select input. It supports parameters "in", "max" and "min".
Code View
var $validate = array(
'multiple' => array(
'rule' => array('multiple', array('in' => array('do', 'ray', 'me', 'fa', 'so', 'la', 'ti'), 'min' => 1,
'max' => 3)),
'message' => 'Please select one, two or three options'
)
);
4.1.4.19 inList
• Edit
• View just this section
• Comments (1)
• History
This rule will ensure that the value is in a given set. It needs an array of values. The field is valid if the
field's value matches one of the values in the given array.
Example:Code View
var $validate = array(
'function' => array(
'allowedChoice' => array(
'rule' => array('inList', array('Foo', 'Bar')),
'message' => 'Enter either Foo or Bar.'
)
)
);
4.1.4.20 numeric
• Edit
• View just this section
• Comments (2)
• History
Checks if the data passed is a valid number.
Code View
var $validate = array(
'cars' => array(
'rule' => 'numeric',
'message' => 'Please supply the number of cars.'
)
);
4.1.4.21 notEmpty
• Edit
• View just this section
• Comments (0)
• History
The basic rule to ensure that a field is not empty.
Code View
var $validate = array(
'title' => array(
'rule' => 'notEmpty',
'message' => 'This field cannot be left blank'
)
);
Do not use this for a multiple select input as it will cause an error. Instead, use "multiple".
4.1.4.22 phone
• Edit
• View just this section
• Comments (0)
• History
Phone validates US phone numbers. If you want to validate non-US phone numbers, you can provide a
regular expression as the second parameter to cover additional number formats.
Code View
var $validate = array(
'phone' => array(
'rule' => array('phone', null, 'us')
)
);
4.1.4.23 postal
• Edit
• View just this section
• Comments (0)
• History
Postal is used to validate ZIP codes from the U.S. (us), Canada (ca), U.K (uk), Italy (it), Germany (de) and
Belgium (be). For other ZIP code formats, you may provide a regular expression as the second parameter.
Code View
var $validate = array(
'zipcode' => array(
'rule' => array('postal', null, 'us')
)
);
4.1.4.24 range
• Edit
• View just this section
• Comments (2)
• History
This rule ensures that the value is in a given range. If no range is supplied, the rule will check to ensure
the value is a legal finite on the current platform.
Code View
var $validate = array(
'number' => array(
'rule' => array('range', -1, 11),
'message' => 'Please enter a number between 0 and 10'
)
);
The above example will accept any value which is larger than 0 (e.g., 0.01) and less than 10 (e.g., 9.99).
Note: The range lower/upper are not inclusive!!!
4.1.4.25 ssn
• Edit
• View just this section
• Comments (0)
• History
Ssn validates social security numbers from the U.S. (us), Denmark (dk), and the Netherlands (nl). For
other social security number formats, you may provide a regular expression.
Code View
var $validate = array(
'ssn' => array(
'rule' => array('ssn', null, 'us')
)
);
4.1.4.26 url
• Edit
• View just this section
• Comments (2)
• History
This rule checks for valid URL formats. Supports http(s), ftp(s), file, news, and gopher protocols.
Code View
var $validate = array(
'website' => array(
'rule' => 'url'
)
);
To ensure that a protocol is in the url, strict mode can be enabled like so.
Code View
var $validate = array(
'website' => array(
'rule' => array('url', true)
)
);
« Multiple Rules per Field | Custom Validation Rules »
If you want to pass extra parameters to your validation function, add elements onto the ‘rule’ array, and
handle them as extra params (after the main $check param) in your function.
Your validation function can be in the model (as in the example above), or in a behavior that the model
implements. This includes mapped methods.
Model/behavior methods are checked first, before looking for a method on the Validation class. This
means that you can override existing validation methods (such as alphaNumeric()) at an application level
(by adding the method to AppModel), or at model level.
When writing a validation rule which can be used by multiple fields, take care to extract the field value
from the $check array. The $check array is passed with the form field name as its key and the field value
as its value. The full record being validated is stored in $this->data member variable.
Code View
<?php
class Post extends AppModel {
var $name = 'Post';
function alphaNumericDashUnderscore($check) {
// $data array is passed using the form field name as the key
// have to extract the value to make the function generic
$value = array_values($check);
$value = $value[0];
CakePHP already protects you against SQL Injection if you use CakePHP's ORM methods (such as find()
and save()) and proper array notation (ie. array('field' => $value)) instead of raw SQL. For sanitization
against XSS its generally better to save raw HTML in database without modification and sanitize at the
time of output/display.
All you need to do is include the Sanitize core library (e.g. before the controller class definition):
Code View
App::import('Sanitize');
4.2.1 paranoid
• Edit
• Comments (0)
• History
paranoid(string $string, array $allowedChars);
This function strips anything out of the target $string that is not a plain-jane alphanumeric character. The
function will overlook certain characters by passing them in $allowedChars array.
Code View
$badString = ";:<script><html>< // >@@#";
echo Sanitize::paranoid($badString);
// output: scripthtml
echo Sanitize::paranoid($badString, array(' ', '@'));
// output: scripthtml @@
4.2.2 html
• Edit
• Comments (0)
• History
html(string $string, boolean $remove = false)
This method prepares user-submitted data for display inside HTML. This is especially useful if you don't
want users to be able to break your layouts or insert images or scripts inside of your HTML pages. If the
$remove option is set to true, HTML content detected is removed rather than rendered as HTML entities.
Code View
$badString = '<font size="99" color="#FF0000">HEY</font><script>...</script>';
echo Sanitize::html($badString);
// output: <font size="99"
color="#FF0000">HEY</font><script>...</script>
echo Sanitize::html($badString, true);
// output: HEY...
4.2.3 escape
• Edit
• Comments (0)
• History
escape(string $string, string $connection)
Used to escape SQL statements by adding slashes, depending on the system's current magic_quotes_gpc
setting. $connection is the name of the database to quote the string for, as named in your
app/config/database.php file.
4.2.4 clean
• Edit
• Comments (2)
• History
Sanitize::clean(mixed $data, mixed $options)
This function is an industrial-strength, multi-purpose cleaner, meant to be used on entire arrays (like
$this->data, for example). The function takes an array (or string) and returns the clean version. The
following cleaning operations are performed on each element in the array (recursively):
The $options argument can either be a string or an array. When a string is provided it's the database
connection name. If an array is provided it will be merged with the following options:
• connection
• odd_spaces
• encode
• dollar
• carriage
• unicode
• escape
• backslash
parameters must be an array of strings. If the array contains objects (including Exception objects), they
will be cast into strings.
CakePHP pre-defines a set of error-types, but at the time of writing, most are only really useful by the
framework itself. One that is more useful to the application developer is the good old 404 error. This can
be called with no parameters as follows:
Code View
$this->cakeError('error404');
Or alternatively, you can cause the page to report the error was at a specific URL by passing the url
parameter:
Code View
$this->cakeError('error404', array('url' => 'some/other.url'));
This all starts being a lot more useful by extending the error handler to use your own error-types.
Application error handlers are largely like controller actions; You typically will set() any passed parameters
to be available to the view and then render a view file from your app/views/errors directory.
Create a file app/app_error.php with the following definition.
Code View
<?php
class AppError extends ErrorHandler {
}
?>
Handlers for new error-types can be implemented by adding methods to this class. Simply create a new
method with the name you want to use as your error-type.
Let's say we have an application that writes a number of files to disk and that it is appropriate to report
write errors to the user. We don't want to add code for this all over the different parts of our application,
so this is a great case for using a new error type.
Add a new method to your AppError class. We'll take one parameter called file that will be the path to the
file we failed to write.
Code View
function cannotWriteFile($params) {
$this->controller->set('file', $params['file']);
$this->_outputMessage('cannot_write_file');
}
Create the view in app/views/errors/cannot_write_file.ctp
Code View
<h2>Unable to write file</h2>
<p>Could not write file <?php echo $file ?> to the disk.</p>
and throw the error in your controller/component
Code View
$this->cakeError('cannotWriteFile', array('file'=>'somefilename'));
The default implementation of $this->_outputMessage(<view-filename>) will just display the view in
views/errors/<view-filename>.ctp. If you wish to override this behaviour, you can redefine
_outputMessage($template) in your AppError class.
4.4 Debugging
• Edit
• Comments (0)
• History
Debugging is an inevitable and necessary part of any development cycle. While CakePHP doesn't offer
any tools that directly connect with any IDE or editor, CakePHP does provide several tools to assist in
debugging and exposing what is running under the hood of your application.
Output from this function is only shown if the core debug variable has been set to a value greater than 0.
dump($var)
Dump prints out the contents of a variable. It will print out all properties and methods (if any) of the
supplied variable.
Code View
$foo = array(1,2,3);
Debugger::dump($foo);
//outputs
array(
1,
2,
3
)
//simple object
$car = new Car();
Debugger::dump($car);
//outputs
Car::
Car::colour = 'red'
Car::make = 'Toyota'
Car::model = 'Camry'
Car::mileage = '15000'
Car::acclerate()
Car::decelerate()
Car::stop()
log($var, $level = 7)
Creates a detailed stack trace log at the time of invocation. The log() method prints out data similar to
that done by Debugger::dump(), but to the debug.log instead of the output buffer. Note your app/tmp
directory (and its contents) must be writable by the web server for log() to work correctly.
trace($options)
Returns the current stack trace. Each line of the trace includes the calling method, including which file
and line the call originated from.
Code View
//In PostsController::index()
pr( Debugger::trace() );
//outputs
PostsController::index() - APP/controllers/downloads_controller.php, line 48
Dispatcher::_invoke() - CORE/cake/dispatcher.php, line 265
Dispatcher::dispatch() - CORE/cake/dispatcher.php, line 237
[main] - APP/webroot/index.php, line 84
Above is the stack trace generated by calling Debugger::trace() in a controller action. Reading the stack
trace bottom to top shows the order of currently running functions (stack frames). In the above example,
index.php called Dispatcher::dispatch(), which in-turn called Dispatcher::_invoke(). The _invoke() method
then called PostsController::index(). This information is useful when working with recursive operations or
deep stacks, as it identifies which functions are currently running at the time of the trace().
exportVar($var, $recursion = 0)
Converts a variable of any type to a string for use in debug output. This method is also used by most of
Debugger for internal variable conversions, and can be used in your own Debuggers as well.
invoke($debugger)
Replace the CakePHP Debugger with a new Error Handler.
The Debugger Class overrides PHP's default error handling, replacing it with far more useful error reports.
The Debugger's error handling is used by default in CakePHP. As with all debugging functions,
Configure::debug must be set to a value higher than 0.
When an error is raised, Debugger both outputs information to the page and makes an entry in the
error.log file. The error report that is generated has both a stack trace and a code excerpt from where the
error was raised. Click on the "Error" link type to reveal the stack trace, and on the "Code" link to reveal
the error-causing lines.
4.5 Caching
• Edit
• Comments (1)
• History
Caching can be made use of on various levels within a CakePHP application. See how to disable browser
caching, full page or element caching, per-request query caching or the Cache class - to cache anything.
for more info.
4.6 Logging
• Edit
• Comments (0)
• History
While CakePHP core Configure Class settings can really help you see what's happening under the hood,
there are certain times that you'll need to log data to the disk in order to find out what's going on. In a
world that is becoming more dependent on technologies like SOAP and AJAX, debugging can be rather
difficult.
Logging can also be a way to find out what's been going on in your application over time. What search
terms are being used? What sorts of errors are my users being shown? How often is a particular query
being executed?
Logging data in CakePHP is easy - the log() function is a part of the Object class, which is the common
ancestor for almost all CakePHP classes. If the context is a CakePHP class (Model, Controller, Component...
almost anything), you can log your data.
4.7 Testing
• Edit
• Comments (0)
• History
As of CakePHP 1.2 there is support for a comprehensive testing framework built into CakePHP. The
framework is an extension of the SimpleTest framework for PHP. This section will discuss how to prepare
for testing and how to build and run your tests.
If you run all of the core tests at once or run core test groups most of them will fail. This is known by the
CakePHP developers and is normal so don't panic. Instead, try running each of the core test cases
individually.
« Testing | Testing overview - Unit testing vs. Web testing »
Let's start with an example. Assuming you have a model named Article available in your application (that
maps to a table named articles), change the example fixture given in the previous section
(app/tests/fixtures/article_fixture.php) to:
Code View
<?php
class ArticleFixture extends CakeTestFixture {
var $name = 'Article';
var $import = 'Article';
}
?>
This statement tells the test suite to import your table definition from the table linked to the model called
Article. You can use any model available in your application. The statement above does not import
records, you can do so by changing it to:
Code View
<?php
class ArticleFixture extends CakeTestFixture {
var $name = 'Article';
var $import = array('model' => 'Article', 'records' => true);
}
?>
If on the other hand you have a table created but no model available for it, you can specify that your
import will take place by reading that table information instead. For example:
Code View
<?php
class ArticleFixture extends CakeTestFixture {
var $name = 'Article';
var $import = array('table' => 'articles');
}
?>
Will import table definition from a table called 'articles' using your CakePHP database connection named
'default'. If you want to change the connection to use just do:
Code View
<?php
class ArticleFixture extends CakeTestFixture {
var $name = 'Article';
var $import = array('table' => 'articles', 'connection' => 'other');
}
?>
Since it uses your CakePHP database connection, if there's any table prefix declared it will be
automatically used when fetching table information. The two snippets above do not import records from
the table. To force the fixture to also import its records, change it to:
Code View
<?php
class ArticleFixture extends CakeTestFixture {
var $name = 'Article';
var $import = array('table' => 'articles', 'records' => true);
}
?>
You can naturally import your table definition from an existing model/table, but have your records defined
directly on the fixture as it was shown on previous section. For example:
Code View
<?php
class ArticleFixture extends CakeTestFixture {
var $name = 'Article';
var $import = 'Article';
start()
First method called in a test case.
end()
Last method called in a test case.
startCase()
called before a test case is started.
endCase()
called after a test case has run.
before($method)
Announces the start of a test method.
after($method)
Announces the end of a test method.
startTest($method)
Called just before a test method is executed.
endTest($method)
Called just after a test method has completed.
}
?>
We now want to set up a test that will use this model definition, but through fixtures, to test some
functionality in the model. CakePHP test suite loads a very minimum set of files (to keep tests isolated), so
we have to start by loading our parent model (in this case the Article model which we already defined),
and then inform the test suite that we want to test this model by specifying which DB configuration it
should use. CakePHP test suite enables a DB configuration named test that is used for all models that rely
on fixtures. Setting $useDbConfig to this configuration will let CakePHP know that this model uses the test
suite database connection.
CakePHP Models will only use the test DB config if they rely on fixtures in your testcase!
Since we also want to reuse all our existing model code we will create a test model that will extend from
Article, set $useDbConfig and $name appropiately. Let's now create a file named article.test.php in your
app/tests/cases/models directory, with the following contents:
Code View
<?php
App::import('Model','Article');
function testPublished() {
$this->Article =& ClassRegistry::init('Article');
$this->assertEqual($result, $expected);
}
}
?>
You can see we have added a method called testPublished(). We start by creating an instance of our
fixture based Article model, and then run our published() method. In $expected we set what we expect
should be the proper result (that we know since we have defined which records are initally populated to
the article table.) We test that the result equals our expectation by using the assertEqual method. See the
section Creating Tests for information on how to run the test.
4.7.6 Testing controllers
• Edit
• Comments (0)
• History
if (isset($this->params['requested'])) {
return $result;
}
$this->set('title', 'Articles');
$this->set('articles', $result);
}
}
?>
Create a file named articles_controller.test.php in your app/tests/cases/controllers directory and put the
following inside:
Code View
<?php
class ArticlesControllerTest extends CakeTestCase {
function startCase() {
echo '<h1>Starting Test Case</h1>';
}
function endCase() {
echo '<h1>Ending Test Case</h1>';
}
function startTest($method) {
echo '<h3>Starting method ' . $method . '</h3>';
}
function endTest($method) {
echo '<hr />';
}
function testIndex() {
$result = $this->testAction('/articles/index');
debug($result);
}
function testIndexShort() {
$result = $this->testAction('/articles/index/short');
debug($result);
}
function testIndexShortGetRenderedHtml() {
$result = $this->testAction('/articles/index/short',
array('return' => 'render'));
debug(htmlentities($result));
}
function testIndexShortGetViewVars() {
$result = $this->testAction('/articles/index/short',
array('return' => 'vars'));
debug($result);
}
function testIndexFixturized() {
$result = $this->testAction('/articles/index/short',
array('fixturize' => true));
debug($result);
}
function testIndexPostFixturized() {
$data = array('Article' => array('user_id' => 1, 'published'
=> 1, 'slug'=>'new-article', 'title' => 'New Article', 'body' => 'New Body'));
$result = $this->testAction('/articles/index',
array('fixturize' => true, 'data' => $data, 'method' => 'post'));
debug($result);
}
}
?>
4.7.6.3 Pitfalls
• Edit
• View just this section
• Comments (1)
• History
If you use testAction to test a method in a controller that does a redirect, your test will terminate
immediately, not yielding any results.
See https://trac.cakephp.org/ticket/4154 for a possible fix.
note: the link above is not working, if you have a new link please update In the mean time, please try this
article for Testing Cake controller.
Helper testing is a bit similar to the same approach for Components. Suppose we have a helper called
CurrencyRendererHelper located inapp/views/helpers/currency_renderer.php with its accompanying test
case file located in app/tests/cases/helpers/currency_renderer.test.php
function usd($amount)
This function will receive the amount to render. It will take 2 decimal digits filling empty space with zeros
and prefix 'USD'.
function euro($amount)
This function will do the same as usd() but prefix the output with 'EUR'. Just to make it a bit more
complex, we will also wrap the result in span tags:
Code View
<span class="euro"></span>
Let's create the tests first:
Code View
<?php
Once we know what our method should do, we can write the method itself:
Code View
<?php
class CurrencyRendererHelper extends AppHelper {
public function usd($amount) {
return 'USD ' . number_format($amount, 2, '.', ',');
}
}
Here we set the decimal places to 2, decimal separator to dot, thousands separator to comma, and prefix
the formatted number with 'USD' string.
Save this in app/views/helpers/currency_renderer.php and execute the test. You should see a green bar
and messaging indicating 4 passes.
First, when you want to write web tests, you must remember to extend CakeWebTestCase instead of
CakeTestCase:
Code View
class CompleteWebTestCase extends CakeWebTestCase
If you need to do some preparation before you start the test, create a constructor:
Code View
function CompleteWebTestCase(){
//Do stuff here
}
When writing the actual test cases, the first thing you need to do is get some output to look at. This can
be done by doing a get or post request, using get() or post()respectively. Both these methods take a full
url as the first parameter. This can be dynamically fetched if we assume that the test script is located
under http://your.domain/cake/folder/webroot/test.php by typing:
Code View
$this->baseurl = current(split("webroot", $_SERVER['PHP_SELF']));
You can then do gets and posts using Cake urls, like this:
Code View
$this->get($this->baseurl."/products/index/");
$this->post($this->baseurl."/customers/login", $data);
The second parameter to the post method, $data, is an associative array containing the post data in Cake
format:
Code View
$data = array(
"data[Customer][mail]" => "[email protected]",
"data[Customer][password]" => "userpass");
When you have requested the page you can do all sorts of asserts on it, using standard SimpleTest web
test methods.
/app
/plugins
/pizza
/tests
/cases
/fixtures
/groups
They work just like normal tests but you have to remember to use the naming conventions for plugins
when importing classes. This is an example of a testcase for the PizzaOrder model from the plugins
chapter of this manual. A difference from other tests is in the first line where 'Pizza.PizzaOrder' is
imported. You also need to prefix your plugin fixtures with 'plugin.plugin_name.'.
Code View
<?php
App::import('Model', 'Pizza.PizzaOrder');
function testSomething() {
// ClassRegistry makes the model use the test database connection
$this->PizzaOrderTest =& ClassRegistry::init('PizzaOrder');
4.7.11 Miscellaneous
• Edit
• Comments (0)
• History
paintHeader()
Prints before the test is started.
paintPass()
Prints everytime a test case has passed. Use $this->getTestList() to get an array of information pertaining
to the test, and $message to get the test result. Remember to call parent::paintPass($message).
paintFail()
Prints everytime a test case has failed. Remember to call parent::paintFail($message).
paintFooter()
Prints when the test is over, i.e. when all test cases has been executed.
If, when running paintPass and paintFail, you want to hide the parent output, enclose the call in html
comment tags, as in:
Code View
echo "\n<!-- ";
parent::paintFail($message);
echo " -->\n";
A sample cake_reporter.php setup that creates a table to hold the test results follows:
Code View
<?php
/**
* CakePHP(tm) Tests <https://trac.cakephp.org/wiki/Developement/TestSuite>
* Copyright 2005-2008, Cake Software Foundation, Inc.
* 1785 E. Sahara Avenue, Suite 490-204
* Las Vegas, Nevada 89104
*
* Licensed under The Open Group Test Suite License
* Redistributions of files must retain the above copyright notice.
*/
class CakeHtmlReporter extends HtmlReporter {
function CakeHtmlReporter($characterSet = 'UTF-8') {
parent::HtmlReporter($characterSet);
}
function paintHeader($testName) {
$this->sendNoCacheHeaders();
$baseUrl = BASE;
print "<h2>$testName</h2>\n";
print "<table style=\"\"><th>Res.</th><th>Test case</th><th>Message</th>\n";
flush();
}
function paintFooter($testName) {
$colour = ($this->getFailCount() + $this->getExceptionCount() > 0 ? "red" : "green");
print "</table>\n";
print "<div style=\"";
print "padding: 8px; margin-top: 1em; background-color: $colour; color: white;";
print "\">";
print $this->getTestCaseProgress() . "/" . $this->getTestCaseCount();
print " test cases complete:\n";
print "<strong>" . $this->getPassCount() . "</strong> passes, ";
print "<strong>" . $this->getFailCount() . "</strong> fails and ";
print "<strong>" . $this->getExceptionCount() . "</strong> exceptions.";
print "</div>\n";
}
function paintPass($message) {
parent::paintPass($message);
echo "<tr>\n\t<td width=\"20\" style=\"border: dotted 1px; border-top: hidden; border-left: hidden;
border-right: hidden\">\n";
print "\t\t<span style=\"color: green;\">Pass</span>: \n";
echo "\t</td>\n\t<td width=\"40%\" style=\"border: dotted 1px; border-top: hidden; border-left:
hidden; border-right: hidden\">\n";
$breadcrumb = $this->getTestList();
array_shift($breadcrumb);
array_shift($breadcrumb);
print implode("->", $breadcrumb);
echo "\n\t</td>\n\t<td width=\"40%\" style=\"border: dotted 1px; border-top: hidden; border-left:
hidden; border-right: hidden\">\n";
$message = split('at \[', $message);
print "->$message[0]<br />\n\n";
echo "\n\t</td>\n</tr>\n\n";
}
function paintFail($message) {
echo "\n<!-- ";
parent::paintFail($message);
echo " -->\n";
echo "<tr>\n\t<td width=\"20\" style=\"border: dotted 1px; border-top: hidden; border-left: hidden;
border-right: hidden\">\n";
print "\t\t<span style=\"color: red;\">Fail</span>: \n";
echo "\n\t</td>\n\t<td width=\"40%\" style=\"border: dotted 1px; border-top: hidden; border-left:
hidden; border-right: hidden\">\n";
$breadcrumb = $this->getTestList();
print implode("->", $breadcrumb);
echo "\n\t</td>\n\t<td width=\"40%\" style=\"border: dotted 1px; border-top: hidden; border-left:
hidden; border-right: hidden\">\n";
print "$message";
echo "\n\t</td>\n</tr>\n\n";
}
function _getCss() {
return parent::_getCss() . ' .pass { color: green; }';
}
}
?>
from app/
cake testsuite help
Usage:
cake testsuite category test_type file
- category - "app", "core" or name of a plugin
- test_type - "case", "group" or "all"
- test_file - file name with folder prefix and without the (test|group).php suffix
Examples:
cake testsuite app all
cake testsuite core all
Append 'cov' to any of the above in order to enable code coverage analysis
As the help menu suggests, you'll be able to run all, part, or just a single test case from your app, plugin,
or core, right from the command line.
If you have a model test of test/models/my_model.test.php you'd run just that test case by running:
cake testsuite app case models/my_model
« Grouping tests | Internationalization & Localization »
First, it’s important to understand some terminology. Internationalization refers to the ability of an
application to be localized. The term localization refers to the adaptation of an application to meet specific
language (or culture) requirements (i.e., a "locale"). Internationalization and localization are often
abbreviated as i18n and l10n respectively; 18 and 10 are the number of characters between the first and
last character.
Remember that po files are useful for short messages, if you find you want to translate long paragraphs,
or even whole pages - you should consider implementing a different solution. e.g.:
Code View
// App Controller Code.
function beforeFilter() {
$locale = Configure::read('Config.language');
if ($locale && file_exists(VIEWS . $locale . DS . $this->viewPath)) {
// e.g. use /app/views/fre/pages/tos.ctp instead of /app/views/pages/tos.ctp
$this->viewPath = $locale . DS . $this->viewPath;
}
}
or
Code View
// View code
echo $this->element(Configure::read('Config.language') . '/tos')
4.8.2 Localization in CakePHP
• Edit
• Comments (7)
• History
To change or set the language for your application, all you need to do is the following:
Code View
Configure::write('Config.language', 'fre');
This tells Cake which locale to use (if you use a regional locale, such as fr_FR, it will use the ISO 639-2
locale as a fallback if it doesn't exist), you can change the language at any time, e.g. in your bootstrap if
you're setting the application default language, in your (app) controller beforeFilter if it's specific to the
request or user, or in fact anytime at all before you want a message in a different language.
It's a good idea to serve up public content available in multiple languages from a unique url - this makes it
easy for users (and search engines) to find what they're looking for in the language they are expecting.
There are several ways to do this, it can be by using language specific subdomains (en.example.com,
fra.example.com, etc.), or using a prefix to the url such as is done with this application. You may also wish
to glean the information from the browser’s user-agent, among other things.
As mentioned in the previous section, displaying localized content is done using the __() convenience
function, or one of the other translation functions all of which are globally available, but probably be best
utilized in your views. The first parameter of the function is used as the msgid defined in the .po files.
Remember to use the return parameter for the various __* methods if you don't want the string echo'ed
directly. For example:
Code View
<?php
echo $form->error(
'Card.cardNumber',
__("errorCardNumber", true),
array('escape' => false)
);
?>
If you would like to have all of your validation error messages translated by default, a simple solution
would be to add the following code in you app_model.php:
Code View
function invalidate($field, $value = true) {
return parent::invalidate($field, __($value, true));
}
The i18n console task will not be able to determine the message id from the above example, which means
you'll need to add the entries to your pot file manually (or via your own script). To prevent the need to
edit your default.po(t) file every time you run the i18n console task, you can use a different domain such
as:
Code View
function invalidate($field, $value = true) {
return parent::invalidate($field, __d('validation_errors', $value, true));
}
This will look for $value in the validation_errors.po file.
There's one other aspect of localizing your application which is not covered by the use of the translate
functions, and that is date/money formats. Don't forget that CakePHP is PHP :), therefore to set the
formats for these things you need to use setlocale.
If you pass a locale that doesn't exist on your computer to setlocale it will have no effect. You can find the
list of available locales by running the command$locale -a in a terminal.
4.9 Pagination
• Edit
• Comments (3)
• History
One of the main obstacles of creating flexible and user-friendly web applications is designing an intuitive
UI. Many applications tend to grow in size and complexity quickly, and designers and programmers alike
find they are unable to cope with displaying hundreds or thousands of records. Refactoring takes time,
and performance and user satisfaction can suffer.
Displaying a reasonable number of records per page has always been a critical part of every application
and used to cause many headaches for developers. CakePHP eases the burden on the developer by
providing a quick, easy way to paginate data.
The PaginatorHelper offers a great solution because it's so easy to use. Apart from pagination, it bundles
some very easy-to-use sorting features. Last but not least, Ajax sorting and pagination are supported as
well.
Do not forget to add the RequestHandler component to use Ajax calls to your controller:
Code View
var $components = array('RequestHandler');
Here’s what a layout including those elements might look like (partially):
Code View
<head>
<title><?php echo $title_for_layout; ?></title>
<?php echo $javascript->link(array('prototype')); ?>
<style type="text/css">
div.disabled {
display: inline;
float: none;
clear: none;
color: #C0C0C0;
}
</style>
</head>
<body>
<div id="main">
<div id="spinner" style="display: none; float: right;">
<?php echo $html->image('spinner.gif'); ?>
</div>
<div id="content">
<?php echo $content_for_layout; ?>
</div>
</div>
</body>
</html>
If the ‘update’ key is not specifed, the PaginationHelper will output non-Ajax pagination sorting and
paging links.
Code View
<?php
//Sets the update and indicator elements by DOM ID
$paginator->options(array('update' => 'content', 'indicator' => 'spinner'));
echo $paginator->prev('<< Previous', null, null, array('class' => 'disabled'));
A good example of when you would need this is if the underlying DB does not support the SQL LIMIT
syntax. This is true of IBM's DB2. You can still use the CakePHP pagination by adding the custom query to
the model.
Should you need to create custom queries to generate the data you want to paginate, you can override
the paginate() and paginateCount() model methods used by the pagination controller logic.
Before continuing check you can't achieve your goal with the core model methods.
The paginate() method uses the same parameters as Model::find(). To use your own method/logic override
it in the model you wish to get the data from.
Code View
/**
* Overridden paginate method - group by week, away_team_id and home_team_id
*/
function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array()) {
$recursive = -1;
$group = $fields = array('week', 'away_team_id', 'home_team_id');
return $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive',
'group'));
}
You also need to override the core paginateCount(), this method expects the same arguments as
Model::find('count'). The example below uses some Postgres-specifc features, so please adjust accordingly
depending on what database you are using.
Code View
/**
* Overridden paginateCount method
*/
function paginateCount($conditions = null, $recursive = 0, $extra = array()) {
$sql = "SELECT DISTINCT ON(week, home_team_id, away_team_id) week, home_team_id,
away_team_id FROM games";
$this->recursive = $recursive;
$results = $this->query($sql);
return count($results);
}
The observant reader will have noticed that the paginate method we've defined wasn't actually necessary
- All you have to do is add the keyword in controller's$paginate class variable.
Code View
/**
* Add GROUP BY clause
*/
var $paginate = array(
'MyModel' => array('limit' => 20,
'order' => array('week' => 'desc'),
'group' => array('week', 'home_team_id', 'away_team_id'))
);
/**
* Or on-the-fly from within the action
*/
function index() {
$this->paginate = array(
'MyModel' => array('limit' => 20,
'order' => array('week' => 'desc'),
'group' => array('week', 'home_team_id', 'away_team_id'))
);
However, it will still be necessary to override the paginateCount() method to get an accurate value.
4.10 REST
• Edit
• Comments (0)
• History
Many newer application programmers are realizing the need to open their core functionality to a greater
audience. Providing easy, unfettered access to your core API can help get your platform accepted, and
allows for mashups and easy integration with other systems.
While other solutions exist, REST is a great way to provide easy access to the logic you've created in your
application. It's simple, usually XML-based (we're talking simple XML, nothing like a SOAP envelope), and
depends on HTTP headers for direction. Exposing an API via REST in CakePHP is simple.
Router::mapResources('recipes');
Router::parseExtensions();
The first line sets up a number of default routes for easy REST access. These routes are HTTP Request
Method sensitive.
The _method POST variable is helpful in using a browser as a REST client (or anything else that can do
POST easily). Just set the value of _method to the name of the HTTP request method you wish to emulate.
Once the router has been set up to map REST requests to certain controller actions, we can move on to
creating the logic in our controller actions. A basic controller might look something like this:
Code View
// controllers/recipes_controller.php
function index() {
$recipes = $this->Recipe->find('all');
$this->set(compact('recipes'));
}
function view($id) {
$recipe = $this->Recipe->findById($id);
$this->set(compact('recipe'));
}
function edit($id) {
$this->Recipe->id = $id;
if ($this->Recipe->save($this->data)) {
$message = 'Saved';
} else {
$message = 'Error';
}
$this->set(compact("message"));
}
function delete($id) {
if($this->Recipe->delete($id)) {
$message = 'Deleted';
} else {
$message = 'Error';
}
$this->set(compact("message"));
}
}
Since we've added a call to Router::parseExtensions(), the CakePHP router is already primed to serve up
different views based on different kinds of requests. Since we're dealing with REST requests, the view
type is XML. We place the REST views for our RecipesController inside app/views/xml. We can also use the
XmlHelper for quick-and-easy XML output in those views. Here's what our index view might look like:
Code View
// app/views/recipes/xml/index.ctp
<recipes>
<?php echo $xml->serialize($recipes); ?>
</recipes>
Experienced CakePHP users might notice that we haven't included the XmlHelper in our RecipesController
$helpers array. This is on purpose - when serving up a specific content type using parseExtensions(),
CakePHP automatically looks for a view helper that matches the type. Since we're using XML as the
content type, the XmlHelper is automatically loaded up for our use in those views.
We'll provide a simple example here, and allow you to tailor this route for your other RESTful purposes.
Here's what our edit REST route would look like, without using mapResources():
Code View
Router::connect(
"/:controller/:id",
array("action" => "edit", "[method]" => "PUT"),
array("id" => "[0-9]+")
)
Advanced routing techniques are covered elsewhere, so we'll focus on the most important point for our
purposes here: the [method] key of the options array in the second parameter. Once that key has been
set, the specified route works only for that HTTP request method (which could also be GET, DELETE, etc.)
5 Core Components
• Edit
• Comments (0)
• History
CakePHP has a number of built-in components. They provide out of the box functionality for several
commonly used tasks.
The Acl component provides an easy to use interface for database and ini based
Acl
access control lists.
The auth component provides an easy to use authentication system using a
Auth variety of authentication processes, such as controller callbacks, Acl, or Object
callbacks.
The cookie component behaves in a similar fashion to the SessionComponent in
Cookie
that it provides a wrapper for PHP's native cookie support.
An interface that can be used to send emails using one of several mail transfer
Email
agents including php's mail() and smtp.
The request handler allows you to introspect further into the requests your
RequestHandler visitors and inform your application about the content types and requested
information.
The security component allows you to set tighter security and use and manage
Security
HTTP authentication.
The session component provides a storage independent wrapper to PHP's
Session
sessions.
To learn more about each component see the menu on the left, or learn more about creating your own
components.
Be brave and stick with it, even if the going gets rough. Once you get the hang of it, it's an extremely
powerful tool to have on hand when developing your application.
Access control lists, or ACL, handle two main things: things that want stuff, and things that are wanted. In
ACL lingo, things (most often users) that want to use stuff are called access request objects, or AROs.
Things in the system that are wanted (most often actions or data) are called access control objects, or
ACOs. The entities are called 'objects' because sometimes the requesting object isn't a person -
sometimes you might want to limit the access certain Cake controllers have to initiate logic in other parts
of your application. ACOs could be anything you want to control, from a controller action, to a web
service, to a line on your grandma's online diary.
To review:
Essentially, ACL is what is used to decide when an ARO can have access to an ACO.
In order to help you understand how everything works together, let's use a semi-practical example.
Imagine, for a moment, a computer system used by a familiar group of fantasy novel adventurers from
the Lord of the Rings. The leader of the group, Gandalf, wants to manage the party's assets while
maintaining a healthy amount of privacy and security for the other members of the party. The first thing
he needs to do is create a list of the AROs involved:
• Gandalf
• Aragorn
• Bilbo
• Frodo
• Gollum
• Legolas
• Gimli
• Pippin
• Merry
Realize that ACL is not the same as authentication. ACL is what happens after a user has been
authenticated. Although the two are usually used in concert, it's important to realize the difference
between knowing who someone is (authentication) and knowing what they can do (ACL).
The next thing Gandalf needs to do is make an initial list of things, or ACOs, the system will handle. His list
might look something like:
• Weapons
• The One Ring
• Salted Pork
• Diplomacy
• Ale
Traditionally, systems were managed using a sort of matrix, that showed a basic set of users and
permissions relating to objects. If this information were stored in a table, it might look like the following
table:
For a small system like this, maybe a matrix setup would work. But for a growing system, or a system with
a large amount of resources (ACOs) and users (AROs), a table can become unwieldy rather quickly.
Imagine trying to control access to the hundreds of war encampments and trying to manage them by unit.
Another drawback to matrices is that you can't really logically group sections of users or make cascading
permissions changes to groups of users based on those logical groupings. For example, it would sure be
nice to automatically allow the hobbits access to the ale and pork once the battle is over: Doing it on an
individual user basis would be tedious and error prone. Making a cascading permissions change to all
'hobbits' would be easy.
ACL is most usually implemented in a tree structure. There is usually a tree of AROs and a tree of ACOs.
By organizing your objects in trees, permissions can still be dealt out in a granular fashion, while still
maintaining a good grip on the big picture. Being the wise leader he is, Gandalf elects to use ACL in his
new system, and organizes his objects along the following lines:
Using a tree structure for AROs allows Gandalf to define permissions that apply to entire groups of users
at once. So, using our ARO tree, Gandalf can tack on a few group-based permissions:
• Wizards
(Allow: Salted Pork, Diplomacy, Ale)
• Gandalf
• Hobbits
(Allow: Ale)
• Frodo
• Bilbo
• Merry
• Pippin
• Visitors
(Allow: Salted Pork)
• Gollum
If we wanted to use ACL to see if the Pippin was allowed to access the ale, we'd first get his path in the
tree, which is Fellowship->Hobbits->Pippin. Then we see the different permissions that reside at each of
those points, and use the most specific permission relating to Pippin and the Ale.
The tree also allows us to make finer adjustments for more granular control - while still keeping the ability
to make sweeping changes to groups of AROs:
• Wizards
(Allow: Salted Pork, Diplomacy, Ale)
• Gandalf
• Hobbits
(Allow: Ale)
• Frodo
(Allow: Ring)
• Bilbo
• Merry
(Deny: Ale)
• Pippin
(Allow: Diplomacy)
• Visitors
(Allow: Salted Pork)
• Gollum
This approach allows us both the ability to make wide-reaching permissions changes, but also fine-grained
adjustments. This allows us to say that all hobbits can have access to ale, with one exception—Merry. To
see if Merry can access the Ale, we'd find his path in the tree: Fellowship->Hobbits->Merry and work our
way down, keeping track of ale-related permissions:
By default, CakePHP's ACL is database-driven. To enable INI-based ACL, you'll need to tell CakePHP what
system you're using by updating the following lines in app/config/core.php
Code View
//Change these lines:
Configure::write('Acl.classname', 'DbAcl');
Configure::write('Acl.database', 'default');
ACOs are specified in INI sections that only include the allow and deny properties.
As an example, let's see how the Fellowship ARO structure we've been crafting would look like in INI
syntax:
;-------------------------------------
; AROs
;-------------------------------------
[aragorn]
groups = warriors
allow = diplomacy
[legolas]
groups = warriors
[gimli]
groups = warriors
[gandalf]
groups = wizards
[frodo]
groups = hobbits
allow = ring
[bilbo]
groups = hobbits
[merry]
groups = hobbits
deny = ale
[pippin]
groups = hobbits
[gollum]
groups = visitors
;-------------------------------------
; ARO Groups
;-------------------------------------
[warriors]
allow = weapons, ale, salted_pork
[wizards]
allow = salted_pork, diplomacy, ale
[hobbits]
allow = ale
[visitors]
allow = salted_pork
Now that you've got your permissions defined, you can skip along to the section on checking permissions
using the ACL component.
To get started, first you'll need to make sure your /app/config/database.php is present and correctly
configured. See section 4.1 for more information on database configuration.
Once you've done that, use the CakePHP console to create your ACL database tables:
You can also use the SQL file found in app/config/sql/db_acl.sql, but that's nowhere near as fun.
When finished, you should have three new database tables in your system: acos, aros, and aros_acos (the
join table to create permissions information between the two trees).
If you're curious about how Cake stores tree information in these tables, read up on modified database
tree traversal. The ACL component uses CakePHP's Tree Behavior to manage the trees' inheritances. The
model class files for ACL are all compiled in a single file db_acl.php.
Now that we're all set up, let's work on creating some ARO and ACO trees.
You create new ACL objects using the core CakePHP ACL models. In doing so, there are a number of fields
you'll want to use when saving data: model, foreign_key,alias, and parent_id.
The model and foreign_key fields for an ACL object allows you to link up the object to its corresponding
model record (if there is one). For example, many AROs will have corresponding User records in the
database. Setting an ARO's foreign_key to the User's ID will allow you to link up ARO and User information
with a single User model find() call if you've set up the correct model associations. Conversely, if you want
to manage edit operation on a specific blog post or recipe listing, you may choose to link an ACO to that
specific model record.
The alias for an ACL object is just a human-readable label you can use to identify an ACL object that has
no direct model record correlation. Aliases are usually useful in naming user groups or ACO collections.
The parent_id for an ACL object allows you to fill out the tree structure. Supply the ID of the parent node in
the tree to create a new child.
Before we can create new ACL objects, we'll need to load up their respective classes. The easiest way to
do this is to include Cake's ACL Component in your controller's $components array:
Code View
var $components = array('Acl');
Once we've got that done, let's see what some examples of creating these objects might look like. The
following code could be placed in a controller action somewhere:
While the examples here focus on ARO creation, the same techniques can be used to create an ACO tree.
Keeping with our Fellowship setup, let's first create our ARO groups. Because our groups won't really have
specific records tied to them, we'll use aliases to create these ACL objects. What we're doing here is from
the perspective of a controller action, but could be done elsewhere. What we'll cover here is a bit of an
artificial approach, but you should feel comfortable using these techniques to build AROs and ACOs on the
fly.
This shouldn't be anything drastically new - we're just using models to save data like we always do:
Code View
function anyAction()
{
$aro =& $this->Acl->Aro;
//Save data
$aro->save($data);
}
Aro tree:
---------------------------------------------------------------
[1]warriors
[2]wizards
[3]hobbits
[4]visitors
---------------------------------------------------------------
I suppose it's not much of a tree at this point, but at least we've got some verification that we've got four
top-level nodes. Let's add some children to those ARO nodes by adding our specific user AROs under
these groups. Every good citizen of Middle Earth has an account in our new system, so we'll tie these ARO
records to specific model records in our database.
When adding child nodes to a tree, make sure to use the ACL node ID, rather than a foreign_key value.
Code View
function anyAction()
{
$aro = new Aro();
//Here are our user records, ready to be linked up to new ARO records
//This data could come from a model and modified, but we're using static
//arrays here for demonstration purposes.
$users = array(
0 => array(
'alias' => 'Aragorn',
'parent_id' => 1,
'model' => 'User',
'foreign_key' => 2356,
),
1 => array(
'alias' => 'Legolas',
'parent_id' => 1,
'model' => 'User',
'foreign_key' => 6342,
),
2 => array(
'alias' => 'Gimli',
'parent_id' => 1,
'model' => 'User',
'foreign_key' => 1564,
),
3 => array(
'alias' => 'Gandalf',
'parent_id' => 2,
'model' => 'User',
'foreign_key' => 7419,
),
4 => array(
'alias' => 'Frodo',
'parent_id' => 3,
'model' => 'User',
'foreign_key' => 7451,
),
5 => array(
'alias' => 'Bilbo',
'parent_id' => 3,
'model' => 'User',
'foreign_key' => 5126,
),
6 => array(
'alias' => 'Merry',
'parent_id' => 3,
'model' => 'User',
'foreign_key' => 5144,
),
7 => array(
'alias' => 'Pippin',
'parent_id' => 3,
'model' => 'User',
'foreign_key' => 1211,
),
8 => array(
'alias' => 'Gollum',
'parent_id' => 4,
'model' => 'User',
'foreign_key' => 1337,
),
);
//Save data
$aro->save($data);
}
The output of that console application command should now be a little more interesting. Let's give it a try:
Aro tree:
---------------------------------------------------------------
[1]warriors
[5]Aragorn
[6]Legolas
[7]Gimli
[2]wizards
[8]Gandalf
[3]hobbits
[9]Frodo
[10]Bilbo
[11]Merry
[12]Pippin
[4]visitors
[13]Gollum
---------------------------------------------------------------
Now that we've got our ARO tree setup properly, let's discuss a possible approach for structuring an ACO
tree. While we can structure more of an abstract representation of our ACO's, it's often more practical to
model an ACO tree after Cake's Controller/Action setup. We've got five main objects we're handling in this
Fellowship scenario, and the natural setup for that in a Cake application is a group of models, and
ultimately the controllers that manipulate them. Past the controllers themselves, we'll want to control
access to specific actions in those controllers.
Based on that idea, let's set up an ACO tree that will mimic a Cake app setup. Since we have five ACOs,
we'll create an ACO tree that should end up looking something like the following:
• Weapons
• Rings
• PorkChops
• DiplomaticEfforts
• Ales
One nice thing about a Cake ACL setup is that each ACO automatically contains four properties related to
CRUD (create, read, update, and delete) actions. You can create children nodes under each of these five
main ACOs, but using Cake's built in action management covers basic CRUD operations on a given object.
Keeping this in mind will make your ACO trees smaller and easier to maintain. We'll see how these are
used later on when we discuss how to assign permissions.
Since you're now a pro at adding AROs, use those same techniques to create this ACO tree. Create these
upper level groups using the core Aco model.
Here we'll work in the context of a controller action. We do that because permissions are managed by the
Acl Component.
Code View
class SomethingsController extends AppController
{
// You might want to place this in the AppController
// instead, but here works great too.
}
Let's set up some basic permissions using the AclComponent in an action inside this controller.
Code View
function index()
{
//Allow warriors complete access to weapons
//Both these examples use the alias syntax
$this->Acl->allow('warriors', 'Weapons');
die(print_r('done', 1));
}
The first call we make to the AclComponent allows any user under the 'warriors' ARO group full access to
anything under the 'Weapons' ACO group. Here we're just addressing ACOs and AROs by their aliases.
Notice the usage of the third parameter? That's where we use those handy actions that are in-built for all
Cake ACOs. The default options for that parameter are create,read, update, and delete but you can add a
column in the aros_acos database table (prefixed with _ - for example _admin) and use it alongside the
defaults.
The second set of calls is an attempt to make a more fine-grained permission decision. We want Aragorn
to keep his full-access privileges, but deny other warriors in the group the ability to delete Weapons
records. We're using the alias syntax to address the AROs above, but you might want to use the
model/foriegn key syntax yourself. What we have above is equivalent to this:
Code View
// 6342 = Legolas
// 1564 = Gimli
5.2 Authentication
• Edit
• Comments (19)
• History
User authentication systems are a common part of many web applications. In CakePHP there are several
systems for authenticating users, each of which provides different options. At its core the authentication
component will check to see if a user has an account with a site. If they do, the component will give
access to that user to the complete site.
This component can be combined with the ACL (access control lists) component to create more complex
levels of access within a site. The ACL Component, for example, could allow you to grant one user access
to public site areas, while granting another user access to protected administrative portions of the site.
CakePHP's AuthComponent can be used to create such a system easily and quickly. Let's take a look at
how you would build a very simple authentication system.
Like all components, you use it by adding 'Auth' to the list of components in your controller:
Code View
class FooController extends AppController {
var $components = array('Auth');
Or add it to your AppController so all of your controllers will use it:
Code View
class AppController extends Controller {
var $components = array('Auth');
Now, there are a few conventions to think about when using AuthComponent. By default, the
AuthComponent expects you to have a table called 'users' with fields called 'username' and 'password' to
be used.
In some situations, databases don't let you use 'password' as a column name. See Setting Auth
Component Variables for an example how to change the default field names to work with your own
environment.
Let's set up our users table using the following SQL:
Code View
CREATE TABLE users (
id integer auto_increment,
username char(50),
password char(40),
first_name varchar(32),
last_name varchar(32),
PRIMARY KEY (id)
);
Something to keep in mind when creating a table to store all your user authentication data is that the
AuthComponent expects the password value stored in the database to be hashed instead of being stored
in plaintext. Make sure that the field you will be using to store passwords is long enough to store the hash
(40 characters for SHA1, for example).
If you want to add a user manually to the db - the simplest method to get the right data is to attempt to
login and look at the sql log.
For the most basic setup, you'll only need to create two actions in your controller:
Code View
class UsersController extends AppController {
/**
* The AuthComponent provides the needed functionality
* for login, so you can leave this function blank.
*/
function login() {
}
function logout() {
$this->redirect($this->Auth->logout());
}
}
While you can leave the login() function blank, you do need to create the login view template (saved in
app/views/users/login.ctp). This is the only UsersController view template you need to create, however.
The example below assumes you are already using the Form helper:
Code View
<?php
$session->flash('auth');
echo $form->create('User', array('action' => 'login'));
echo $form->input('username');
echo $form->input('password');
echo $form->end('Login');
?>
This view creates a simple login form where you enter a username and password. Once you submit this
form, the AuthComponent takes care of the rest for you. The session flash message will display any
notices generated by the AuthComponent for example when the username and password combination do
not match. Upon successful login the database record of the current logged in user is saved to session
under the key Auth.User.
It is common that on successful login the user should be redirected to some place else, such as a
dashboard. To achieve this Auth's loginRedirect property should be set to this location using either string
or array URL notation. For example if your UsersController uses the Auth component, we can configure
this property in a beforeFilter override method
Code View
class UsersController extends AppController {
function beforeFilter() {
parent::beforeFilter();
$this->Auth->loginRedirect = array('controller' => 'dashboard', 'action' => 'index');
}
function login() {
}
For example, to change the field name used for passwords from 'password' to 'secretword', you would do
the following:
Code View
class UsersController extends AppController {
var $components = array('Auth');
function beforeFilter() {
$this->Auth->fields = array(
'username' => 'username',
'password' => 'secretword'
);
}
}
In this particular situation, you would also need to remember to change the field name in the view
template!
Another common use of Auth component variables is to allow access to certain methods without the user
being logged in (by default Auth restricts access to every action except the login and logout methods).
For example if we want to allow all users access to the index and view methods ( but not any other), we
would do the following:
Code View
function beforeFilter() {
$this->Auth->allow('index','view');
}
Password hashing
When posting information to an action via a form, the Auth component automatically hashes the contents
of your password input field if you also have data in the username field. So, if you are trying to create
some sort of registration page, make sure to have the user fill out a 'confirm password' field so that you
can compare the two. Here's some sample code:
Code View
<?php
function register() {
if ($this->data) {
if ($this->data['User']['password'] == $this->Auth->password($this->data['User']
['password_confirm'])) {
$this->User->create();
$this->User->save($this->data);
}
}
}
?>
5.2.5.1 action
• Edit
• View just this section
• Comments (3)
• History
action (string $action = ':controller/:action')
If you are using ACO's as part of your ACL structure, you can get the path to the ACO node bound to a
particular controller/action pair:
Code View
$acoNode = $this->Auth->action('users/delete');
If you don't pass in any values, it uses the current controller / action pair
5.2.5.2 allow
• Edit
• View just this section
• Comments (5)
• History
If you have some actions in your controller that you don't have to authenticate against (such as a user
registration action), you can add methods that the AuthComponent should ignore. The following example
shows how to allow an action named 'register'.
Code View
function beforeFilter() {
...
$this->Auth->allow('register');
}
If you wish to allow multiple actions to skip authentication, you supply them as parameters to the allow()
method:
Code View
function beforeFilter() {
...
$this->Auth->allow('foo', 'bar', 'baz');
}
Shortcut: you may also allow all the actions in a controller by using '*'.
Code View
function beforeFilter() {
...
$this->Auth->allow('*');
}
If you are using requestAction in your layout or elements you should allow those actions in order to be
able to open login page properly.
The auth component assumes that your actions names follow conventions and are underscored.
5.2.5.3 deny
• Edit
• View just this section
• Comments (1)
• History
There may be times where you will want to remove actions from the list of allowed actions (set using
$this->Auth->allow()). Here's an example:
Code View
function beforeFilter() {
$this->Auth->authorize = 'controller';
$this->Auth->allow('delete');
}
function isAuthorized() {
if ($this->Auth->user('role') != 'admin') {
$this->Auth->deny('delete');
}
...
}
5.2.5.4 hashPasswords
• Edit
• View just this section
• Comments (3)
• History
hashPasswords ($data)
This method checks if the $data contains the username and password fields as specified by the variable
$fields indexed by the model name as specified by$userModel. If the $data array contains both the
username and password, it hashes the password field in the array and returns the data array in the same
format. This function should be used prior to insert or update calls of the user when the password field is
affected.
Code View
$data['User']['username'] = '[email protected]';
$data['User']['password'] = 'changeme';
$hashedPasswords = $this->Auth->hashPasswords($data);
pr($hashedPasswords);
/* returns:
Array
(
[User] => Array
(
[username] => [email protected]
[password] => 8ed3b7e8ced419a679a7df93eff22fae
)
)
*/
The $hashedPasswords['User']['password'] field would now be hashed using the password function of the
component.
If your controller uses the Auth component and posted data contains the fields as explained above, it will
automatically hash the password field using this function.
5.2.5.5 mapActions
• Edit
• View just this section
• Comments (0)
• History
If you are using Acl in CRUD mode, you may want to assign certain non-default actions to each part of
CRUD.
Code View
$this->Auth->mapActions(
array(
'create' => array('someAction'),
'read' => array('someAction', 'someAction2'),
'update' => array('someAction'),
'delete' => array('someAction')
)
);
5.2.5.6 login
• Edit
• View just this section
• Comments (0)
• History
login($data = null)
If you are doing some sort of Ajax-based login, you can use this method to manually log someone into the
system. If you don't pass any value for $data, it will automatically use POST data passed into the
controller.
for example, in an application you may wish to assign a user a password and auto log them in after
registration. In an over simplified example:
View:
Code View
echo $form->create('User',array('action'=>'register'));
echo $form->input('username');
echo $form->end('Register');
Controller:
Code View
function register() {
if(!empty($this->data)) {
$this->User->create();
$assigned_password = "password";
$this->data['User']['password'] = $this->Auth->password($assigned_password);
if($this->User->save($this->data)) {
// send signup email containing password to the user
$this->Auth->login($this->data);
$this->redirect("home");
}
}
One thing to note is that you must manually redirect the user after login as loginRedirect is not called.
$this->Auth->login($data) returns 1 on successful login, 0 on a failure
5.2.5.7 logout
• Edit
• View just this section
• Comments (0)
• History
Provides a quick way to de-authenticate someone, and redirect them to where they need to go. This
method is also useful if you want to provide a 'Log me out' link inside a members' area of your
application.
Example:
Code View
$this->redirect($this->Auth->logout());
5.2.5.8 password
• Edit
• View just this section
• Comments (0)
• History
password (string $password)
Pass in a string, and you can get what the hashed password would look like. This is an essential
functionality if you are creating a user registration screen where you have users enter their password a
second time to confirm it.
Code View
if ($this->data['User']['password'] ==
$this->Auth->password($this->data['User']['password2'])) {
Cake appends your password string to a salt value and then hashes it. The hashing function used depends
on the one set by the core utility class Security (sha1 by default). You can use the Security::setHash
function to change the hashing method. The salt value is used from your application's configuration
defined in yourcore.php
5.2.5.9 user
• Edit
• View just this section
• Comments (2)
• History
user(string $key = null)
This method provides information about the currently authenticated user. The information is taken from
the session. For example:
Code View
if ($this->Auth->user('role') == 'admin') {
$this->flash('You have admin access');
}
It can also be used to return the whole user session data like so:
Code View
$data['User'] = $this->Auth->user();
If this method returns null, the user is not logged in.
In the view you can use the Session helper to retrieve the currently authenticated user's information:
Code View
$session->read('Auth.User'); // returns complete user record
$session->read('Auth.User.first_name') //returns particular field value
The session key can be different depending on which model Auth is configured to use. Eg. If you use
model Account instead of User, then the session key would beAuth.Account
5.2.6.1 userModel
• Edit
• View just this section
• Comments (0)
• History
Don't want to use a User model to authenticate against? No problem, just change it by setting this value
to the name of the model you want to use.
Code View
<?php
$this->Auth->userModel = 'Member';
?>
5.2.6.2 fields
• Edit
• View just this section
• Comments (0)
• History
Overrides the default username and password fields used for authentication.
Code View
<?php
$this->Auth->fields = array('username' => 'email', 'password' => 'passwd');
?>
5.2.6.3 userScope
• Edit
• View just this section
• Comments (0)
• History
Use this to provide additional requirements for authentication to succeed.
Code View
<?php
$this->Auth->userScope = array('User.active' => true);
?>
5.2.6.4 loginAction
• Edit
• View just this section
• Comments (0)
• History
You can change the default login from /users/login to be any action of your choice.
Code View
<?php
$this->Auth->loginAction = array('admin' => false, 'controller' => 'members', 'action' => 'login');
?>
5.2.6.5 loginRedirect
• Edit
• View just this section
• Comments (0)
• History
The AuthComponent remembers what controller/action pair you were trying to get to before you were
asked to authenticate yourself by storing this value in the Session, under the Auth.redirect key. However,
if this session value is not set (if you're coming to the login page from an external link, for example), then
the user will be redirected to the URL specified in loginRedirect.
Example:
Code View
<?php
$this->Auth->loginRedirect = array('controller' => 'members', 'action' => 'home');
?>
5.2.6.6 logoutRedirect
• Edit
• View just this section
• Comments (0)
• History
You can also specify where you want the user to go after they are logged out, with the default being the
login action.
Code View
<?php
$this->Auth->logoutRedirect = array(Configure::read('Routing.admin') => false, 'controller' =>
'members', 'action' => 'logout');
?>
5.2.6.7 loginError
• Edit
• View just this section
• Comments (0)
• History
Change the default error message displayed when someone does not successfully log in.
Code View
<?php
$this->Auth->loginError = "No, you fool! That's not the right password!";
?>
5.2.6.8 authError
• Edit
• View just this section
• Comments (0)
• History
Change the default error message displayed when someone attempts to access an object or action to
which they do not have access.
Code View
<?php
$this->Auth->authError = "Sorry, you are lacking access.";
?>
5.2.6.9 autoRedirect
• Edit
• View just this section
• Comments (0)
• History
Normally, the AuthComponent will automatically redirect you as soon as it authenticates. Sometimes you
want to do some more checking before you redirect users:
Code View
<?php
function beforeFilter() {
...
$this->Auth->autoRedirect = false;
}
...
function login() {
//-- code inside this function will execute only when autoRedirect was set to false (i.e. in a
beforeFilter).
if ($this->Auth->user()) {
if (!empty($this->data['User']['remember_me'])) {
$cookie = array();
$cookie['username'] = $this->data['User']['username'];
$cookie['password'] = $this->data['User']['password'];
$this->Cookie->write('Auth.User', $cookie, true, '+2 weeks');
unset($this->data['User']['remember_me']);
}
$this->redirect($this->Auth->redirect());
}
if (empty($this->data)) {
$cookie = $this->Cookie->read('Auth.User');
if (!is_null($cookie)) {
if ($this->Auth->login($cookie)) {
// Clear auth message, just in case we use it.
$this->Session->delete('Message.auth');
$this->redirect($this->Auth->redirect());
}
}
}
}
?>
The code in the login function will not execute unless you set $autoRedirect to false in a beforeFilter. The
code present in the login function will only execute afterauthentication was attempted. This is the best
place to determine whether or not a successful login occurred by the AuthComponent (should you desire
to log the last successful login timestamp, etc.).
With autoRedirect set to false, you can also inject additional code such as keeping track of the last
successful login timestamp
Code View
<?php
function login() {
if( !(empty($this->data)) && $this->Auth->user() ){
$this->User->id = $this->Auth->user('id');
$this->User->saveField('last_login', date('Y-m-d H:i:s') );
$this->redirect($this->Auth->redirect());
}
}
?>
5.2.6.10 authorize
• Edit
• View just this section
• Comments (0)
• History
Normally, the AuthComponent will attempt to verify that the login credentials you've entered are accurate
by comparing them to what's been stored in your user model. However, there are times where you might
want to do some additional work in determining proper credentials. By setting this variable to one of
several different values, you can do different things. Here are some of the more common ones you might
want to use.
Code View
<?php
$this->Auth->authorize = 'controller';
?>
When authorize is set to 'controller', you'll need to add a method called isAuthorized() to your controller.
This method allows you to do some more authentication checks and then return either true or false.
Code View
<?php
function isAuthorized() {
if ($this->action == 'delete') {
if ($this->Auth->user('role') == 'admin') {
return true;
} else {
return false;
}
}
return true;
}
?>
Remember that this method will be checked after you have already passed the basic authentication check
against the user model.
Code View
<?php
$this->Auth->authorize = array('model'=>'User');
?>
Don't want to add anything to your controller and might be using ACO's? You can get the AuthComponent
to call a method in your user model called isAuthorized() to do the same sort of thing:
Code View
<?php
class User extends AppModel {
...
switch ($action) {
case 'default':
return false;
break;
case 'delete':
if ($user['User']['role'] == 'admin') {
return true;
}
break;
}
}
}
?>
Lastly, you can use authorize with actions such as below
Code View
<?php
$this->Auth->authorize = 'actions';
?>
By using actions, Auth will make use of ACL and check with AclComponent::check(). An isAuthorized
function is not needed.
Code View
<?php
$this->Auth->authorize = 'crud';
?>
By using crud, Auth will make use of ACL and check with AclComponent::check(). Actions should be
mapped to CRUD (see mapActions).
5.2.6.11 sessionKey
• Edit
• View just this section
• Comments (0)
• History
Name of the session array key where the record of the current authed user is stored.
5.2.6.12 ajaxLogin
• Edit
• View just this section
• Comments (0)
• History
If you are doing Ajax or Javascript based requests that require authenticated sessions, set this variable to
the name of a view element you would like to be rendered and returned when you have an invalid or
expired session.
As with any part of CakePHP, be sure to take a look at AuthComponent class for a more in-depth look at
the AuthComponent.
5.2.6.13 authenticate
• Edit
• View just this section
• Comments (0)
• History
This variable holds a reference to the object responsible for hashing passwords if it is necessary to
change/override the default password hashing mechanism. SeeChanging the Encryption Type for more
info.
5.2.6.14 actionPath
• Edit
• View just this section
• Comments (0)
• History
If using action-based access control, this defines how the paths to action ACO nodes is computed. If, for
example, all controller nodes are nested under an ACO node named 'Controllers', $actionPath should be
set to 'Controllers/'.
5.3 Cookies
• Edit
• Comments (0)
• History
The CookieComponent is a wrapper around the native PHP setcookie method. It also includes a host of
delicious icing to make coding cookies in your controllers very convenient. Before attempting to use the
CookieComponent, you must make sure that 'Cookie' is listed in your controllers' $components array.
Cookie
default description
variable
string
'CakeCookie' The name of the cookie.
$name
This string is used to encrypt the value written to the cookie. This string
string $key null
should be random and difficult to guess.
string The domain name allowed to access the cookie. e.g. Use
''
$domain '.yourdomain.com' to allow access from all your subdomains.
The time when your cookie will expire. Integers are interpreted as
int or seconds and a value of 0 is equivalent to a 'session cookie': i.e. the cookie
string '5 Days' expires when the browser is closed. If a string is set, this will be
$time interpreted with PHP function strtotime(). You can set this directly within
the write() method.
The server path on which the cookie will be applied. If $cookiePath is set
string to '/foo/', the cookie will only be available within the /foo/ directory and
'/'
$path all sub-directories such as /foo/bar/ of your domain. The default value is
the entire domain. You can set this directly within the write() method.
Indicates that the cookie should only be transmitted over a secure HTTPS
boolean
false connection. When set to true, the cookie will only be set if a secure
$secure
connection exists. You can set this directly within the write() method.
The following snippet of controller code shows how to include the CookieComponent and set up the
controller variables needed to write a cookie named 'baker_id' for the domain 'example.com' which needs
a secure connection, is available on the path ‘/bakers/preferences/’, and expires in one hour.
Code View
var $components = array('Cookie');
function beforeFilter() {
$this->Cookie->name = 'baker_id';
$this->Cookie->time = 3600; // or '1 hour'
$this->Cookie->path = '/bakers/preferences/';
$this->Cookie->domain = 'example.com';
$this->Cookie->secure = true; //i.e. only sent if using secure HTTPS
$this->Cookie->key = 'qSI232qs*&sXOw!';
}
Next, let’s look at how to use the different methods of the Cookie Component.
//Delete the cookie variable bar, but not all under foo
$this->Cookie->delete('foo.bar')
destroy()
Destroys the current cookie.
5.4 Email
• Edit
• Comments (0)
• History
The emailComponent is a way for you to add simple email sending functionality to your CakePHP
application. Using the same concepts of layouts and view ctp files to send formated messages as text,
html or both. It supports sending via the built in mail functions of PHP, via smtp server or a debug mode
where it writes the message out to a session flash message. It supports file attachments and does some
basic header injection checking/ filtering for you. There is a lot that it doesn't do for you but it will get you
started.
These are the files that hold the layout templates for your default messages. Some example content is
below
email/text/default.ctpCode View
<?php echo $content_for_layout; ?>
email/html/default.ctpCode View
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<body>
<?php echo $content_for_layout; ?>
</body>
</html>
<?php
function _sendNewUserMail($id) {
$User = $this->User->read(null,$id);
$this->Email->to = $User['User']['email'];
$this->Email->bcc = array('[email protected]');
$this->Email->subject = 'Welcome to our really cool thing';
$this->Email->replyTo = '[email protected]';
$this->Email->from = 'Cool Web App <[email protected]>';
$this->Email->template = 'simple_message'; // note no '.ctp'
//Send as 'html', 'text' or 'both' (default is 'text')
$this->Email->sendAs = 'both'; // because we like to send pretty mail
//Set view variables as normal
$this->set('User', $User);
//Do not pass any args to send()
$this->Email->send();
}
?>
You have sent a message, you could call this from another method like Code View
$this->_sendNewUserMail( $this->User->id );
5.4.2.4 Attachments
• Edit
• View just this section
• Comments (0)
• History
Here's how you can send file attachments along with your message. You set an array containing the paths
to the files to attach to the attachments property of the component.
Code View
$this->Email->attachments = array(
TMP . 'foo.doc',
'bar.doc' => TMP . 'some-temp-name'
);
The first file foo.doc will be attached with the same filename. For the second file we specify an alias
bar.doc will be be used for attaching instead of its actual filenamesome-temp-name
« Debugging Emails | Sending A Message Using SMTP »
By default RequestHandler will automatically detect Ajax requests based on the HTTP-X-Requested-With
header that many javascript libraries use. When used in conjunction with Router::parseExtensions()
RequestHandler will automatically switch the layout and view files to those that match the requested
type. Furthermore, if a helper with the same name as the requested extension exists, it will be added to
the Controllers Helper array. Lastly, if XML data is POST'ed to your Controllers, it will be parsed into an
XML object which is assigned to Controller::data, and can then be saved as model data. In order to make
use of Request Handler it must be included in your $components array.
Code View
<?php
class WidgetController extends AppController {
function beforeFilter () {
if ($this->RequestHandler->accepts('html')) {
// Execute code only if client accepts an HTML (text/html) response
} elseif ($this->RequestHandler->accepts('xml')) {
// Execute XML-only code
}
if ($this->RequestHandler->accepts(array('xml', 'rss', 'atom'))) {
// Executes if the client accepts any of the above: XML, RSS or Atom
}
}
}
Other request 'type' detection methods include:
isAjax()
Returns true if the request contains the X-Requested-Header equal to XMLHttpRequest.
isSSL()
Returns true if the current request was made over an SSL connection.
isXml()
Returns true if the current request accepts XML as a response.
isRss()
Returns true if the current request accepts RSS as a response.
isAtom()
Returns true if the current call accepts an Atom response, false otherwise.
isMobile()
Returns true if user agent string matches a mobile web browser, or if the client accepts WAP content. The
supported Mobile User Agent strings are:
• iPhone
• MIDP
• AvantGo
• BlackBerry
• J2ME
• Opera Mini
• DoCoMo
• NetFront
• Nokia
• PalmOS
• PalmSource
• portalmmm
• Plucker
• ReqwirelessWeb
• SonyEricsson
• Symbian
• UP.Browser
• Windows CE
• Xiino
isWap()
Returns true if the client accepts WAP content.
All of the above request detection methods can be used in a similar fashion to filter functionality intended
for specific content types. For example when responding to Ajax requests, you often will want to disable
browser caching, and change the debug level. However, you want to allow caching for non-ajax requests.
The following would accomplish that:
Code View
if ($this->RequestHandler->isAjax()) {
Configure::write('debug', 0);
$this->header('Pragma: no-cache');
$this->header('Cache-control: no-cache');
$this->header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
}
//Continue Controller action
You could also disable caching with the functionally analogous Controller::disableCache
Code View
if ($this->RequestHandler->isAjax()) {
$this->disableCache();
}
//Continue Controller action
« Request Handling | Request Type Detection »
isPut()
Returns true if the request is a PUT request.
isGet()
Returns true if the request is a GET request.
isDelete()
Returns true if the request is a DELETE request.
getReferer()
Returns the domain name from which the request originated
getAjaxVersion()
Gets Prototype version if call is Ajax, otherwise empty string. The Prototype library sets a special
"Prototype version" HTTP header.
setContent adds/sets the Content-types for the given name. Allows content-types to be mapped to
friendly aliases and or extensions. This allows RequestHandler to automatically respond to requests of
each type in its startup method. If you are using Router::parseExtension, you should use the file extension
as the name of the Content-type. Furthermore, these content types are used by prefers() and accepts().
setContent is best used in the beforeFilter() of your controllers, as this will best leverage the
automagicness of content-type aliases.
renderAs($controller, $type)
• $controller - Controller Reference
• $type - friendly content type name to render content for ex. xml, rss.
Change the render mode of a controller to the specified type. Will also append the appropriate helper to
the controller's helper array if available and not already in the array.
respondAs($type, $options)
• $type - Friendly content type name ex. xml, rss or a full content type like application/x-shockwave
• $options - If $type is a friendly type name that has more than one content association, $index is
used to select the content type.
Sets the response header based on content-type map names. If DEBUG is greater than 1, the header is
not set.
responseType()
Returns the current response type Content-type header or null if one has yet to be set.
mapType($ctype)
Maps a content-type back to an alias
5.6 Security Component
• Edit
• Comments (0)
• History
The Security Component creates an easy way to integrate tighter security in your application. An
interface for managing HTTP-authenticated requests can be created with Security Component. It is
configured in the beforeFilter() of your controllers. It has several configurable parameters. All of these
properties can be set directly or through setter methods of the same name.
If an action is restricted using the Security Component it is black-holed as an invalid request which will
result in a 404 error by default. You can configure this behavior by setting the $this->Security-
>blackHoleCallback property to a callback function in the controller. Keep in mind that black holes from all
of the Security Component's methods will be run through this callback method.
By using the Security Component you automatically get CSRF and form tampering protection. Hidden
token fields will automatically be inserted into forms and checked by the Security component. Among
other things, a form submission will not be accepted after a certain period of inactivity, which depends on
the setting of Security.level. On 'high', this timeout is as short as 10 minutes. Other token fields include a
randomly generated nonce (one-time id) and a hash of fields which (and only which) must be present in
the submitted POST data.
If you are using Security component's form protection features and other components that process form
data in their startup() callbacks, be sure to place Security Component before those components in your
$components array.
When using the Security Component you must use the FormHelper to create your forms. The Security
Component looks for certain indicators that are created and managed by the FormHelper (especially those
created in create() and end()). Dynamically altering the fields that are submitted in a POST request (e.g.
disabling, deleting or creating new fields via JavaScript) is likely to trigger a black-holing of the request.
See the $validatePost or $disabledFieldsconfiguration parameters.
« Responding To Requests | Configuration »
5.6.1 Configuration
• Edit
• Comments (0)
• History
$blackHoleCallback
A Controller callback that will handle and requests that are blackholed.
$requirePost
A List of controller actions that require a POST request to occur. An array of controller actions or '*' to
force all actions to require a POST.
$requireSecure
List of actions that require an SSL connection to occur. An array of controller actions or '*' to force all
actions to require a SSL connection.
$requireAuth
List of actions that requires a valid authentication key. This validation key is set by Security Component.
$requireLogin
List of actions that require HTTP-Authenticated logins (basic or digest). Also accepts '*' indicating that all
actions of this controller require HTTP-authentication.
$loginOptions
Options for HTTP-Authenticate login requests. Allows you to set the type of authentication and the
controller callback for the authentication process.
$loginUsers
An associative array of usernames => passwords that are used for HTTP-authenticated logins. If you are
using digest authentication, your passwords should be MD5-hashed.
$allowedControllers
A List of Controller from which the actions of the current controller are allowed to receive requests from.
This can be used to control cross controller requests.
$allowedActions
Actions from which actions of the current controller are allowed to receive requests. This can be used to
control cross controller requests.
$disabledFields
List of form fields that shall be ignored when validating POST - The value, presence or absence of
these form fields will not be taken into account when evaluating whether a form submission is valid.
Specify fields as you do for the Form Helper (Model.fieldname).
$validatePost
Set to false to completely skip the validation of POST requests, essentially turning CSRF protection
off.
« Security Component | Methods »
5.6.2 Methods
• Edit
• Comments (0)
• History
5.6.2.1 requirePost()
• Edit
• View just this section
• Comments (0)
• History
Sets the actions that require a POST request. Takes any number of arguments. Can be called with no
arguments to force all actions to require a POST.
5.6.2.2 requireSecure()
• Edit
• View just this section
• Comments (0)
• History
Sets the actions that require a SSL-secured request. Takes any number of arguments. Can be called with
no arguments to force all actions to require a SSL-secured.
5.6.2.3 requireAuth()
• Edit
• View just this section
• Comments (0)
• History
Sets the actions that require a valid Security Component generated token. Takes any number of
arguments. Can be called with no arguments to force all actions to require a valid authentication.
5.6.2.4 requireLogin()
• Edit
• View just this section
• Comments (0)
• History
Sets the actions that require a valid HTTP-Authenticated request. Takes any number of arguments. Can be
called with no arguments to force all actions to require valid HTTP-authentication.
5.6.2.5 loginCredentials(string $type)
• Edit
• View just this section
• Comments (0)
• History
Attempt to validate login credentials for a HTTP-authenticated request. $type is the type of HTTP-
Authentication you want to check. Either 'basic', or 'digest'. If left null/empty both will be tried. Returns an
array with login name and password if successful.
$options generally contains a 'type', 'realm' . Type indicate which HTTP-Authenticate method to use.
Realm defaults to the current HTTP server environment.
5.6.3 Usage
• Edit
• Comments (0)
• History
Using the security component is generally done in the controller beforeFilter(). You would specify the
security restrictions you want and the Security Component will enforce them on its startup.
Code View
<?php
class WidgetController extends AppController {
function beforeFilter() {
$this->Security->requirePost('delete');
}
}
?>
In this example the delete action can only be successfully triggered if it recieves a POST request.
Code View
<?php
class WidgetController extends AppController {
function beforeFilter() {
if(isset($this->params[Configure::read('Routing.admin')])){
$this->Security->requireSecure();
}
}
}
?>
This example would force all actions that had admin routing to require secure SSL requests.
Code View
<?php
class WidgetController extends AppController {
function beforeFilter() {
if(isset($this->params[Configure::read('Routing.admin')])){
$this->Security->blackHoleCallback = 'forceSSL';
$this->Security->requireSecure();
}
}
function forceSSL() {
$this->redirect('https://' . env('SERVER_NAME') . $this->here);
}
}
?>
This example would force all actions that had admin routing to require secure SSL requests. When the
request is black holed, it will call the nominated forceSSL() callback which will redirect non-secure
requests to secure requests automatically.
Using the SecurityComponent for HTTP authentication is easy. The code example below includes the
SecurityComponent and adds a few lines of code inside the controller's beforeFilter method.
Code View
class ApiController extends AppController {
var $name = 'Api';
var $uses = array();
var $components = array('Security');
function beforeFilter() {
$this->Security->loginOptions = array(
'type'=>'basic',
'realm'=>'MyRealm'
);
$this->Security->loginUsers = array(
'john'=>'johnspassword',
'jane'=>'janespassword'
);
$this->Security->requireLogin();
}
function index() {
//protected application logic goes here...
}
}
The loginOptions property of the SecurityComponent is an associative array specifying how logins should
be handled. You only need to specify the type as basic to get going. Specify the realm if you want display
a nice message to anyone trying to login or if you have several authenticated sections (= realms) of your
application you want to keep separate.
The loginUsers property of the SecurityComponent is an associative array containing users and passwords
that should have access to this realm. The examples here use hard-coded user information, but you'll
probably want to use a model to make your authentication credentials more manageable.
Finally, requireLogin() tells SecurityComponent that this Controller requires login. As with requirePost(),
above, providing method names will protect those methods while keeping others open.
5.7 Sessions
• Edit
• Comments (0)
• History
The CakePHP session component provides a way to persist client data between page requests. It acts as a
wrapper for the $_SESSION as well as providing convenience methods for several $_SESSION related
functions.
Sessions can be persisted in a few different ways. The default is to use the settings provided by PHP;
however, other options exist.
cake
Saves the session files in your app's tmp/sessions directory.
database
Uses CakePHP's database sessions.
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.
php
The default setting. Saves session files as indicated by php.ini
To change the default Session handling method alter the Session.save Configuration to reflect the option
you desire. If you choose 'database' you should also uncomment the Session.database settings and run
the database session SQL file located in app/config
To provide a custom configuration, set Session.save Configuration to a filename. CakePHP will use your file
in the CONFIGS directory for the settings.
Code View
// app/config/core.php
Configure::write('Session.save','my_session');
This will allow you to customize the session handling.
Code View
// app/config/my_session.php
//
// Revert value and get rid of the referrer check even when,
// Security.level is medium
ini_restore('session.referer_check');
ini_set('session.use_trans_sid', 0);
ini_set('session.name', Configure::read('Session.cookie'));
5.7.1 Methods
• Edit
• Comments (0)
• History
The Session component is used to interact with session information. It includes basic CRUD functions as
well as features for creating feedback messages to users.
It should be noted that Array structures can be created in the Session by using dot notation. So
User.username would reference the following:
Code View
array('User' =>
array('username' => '[email protected]')
);
Dots are used to indicate nested arrays. This notation is used for all Session component methods
wherever a $name is used.
5.7.1.1 write
• Edit
• View just this section
• Comments (0)
• History
write($name, $value)
Write to the Session puts $value into $name. $name can be a dot separated array. For example:
Code View
$this->Session->write('Person.eyeColor', 'Green');
This writes the value 'Green' to the session under Person => eyeColor.
5.7.1.2 setFlash
• Edit
• View just this section
• Comments (1)
• History
setFlash($message, $element = 'default', $params = array(), $key = 'flash')
Used to set a session variable that can be used for output in the View. $element allows you to control
which element (located in /app/views/elements) should be used to render the message in. In the element
the message is available as $message. If you leave the $element set to 'default', the message will be
wrapped with the following:
Code View
<div id="flashMessage" class="message"> [message] </div>
$params allows you to pass additional view variables to the rendered layout. $key sets the $messages
index in the Message array. Default is 'flash'.
Parameters can be passed affecting the rendered div, for example adding "class" in the $params array
will apply a class to the div output using $session->flash() in your layout or view.
Code View
$this->Session->setFlash('Example message text', 'default', array('class' => 'example_class'))
The output from using $session->flash() with the above example would be:
Code View
<div id="flashMessage" class="example_class">Example message text</div>
5.7.1.3 read
• Edit
• View just this section
• Comments (0)
• History
read($name)
Returns the value at $name in the Session. If $name is null the entire session will be returned. E.g.
Code View
$green = $this->Session->read('Person.eyeColor');
Retrieve the value Green from the session.
5.7.1.4 check
• Edit
• View just this section
• Comments (0)
• History
check($name)
Used to check if a Session variable has been set. Returns true on existence and false on non-existence.
5.7.1.5 delete
• Edit
• View just this section
• Comments (0)
• History
delete($name)
Clear the session data at $name. E.g.
Code View
$this->Session->delete('Person.eyeColor');
Our session data no longer has the value 'Green', or the index eyeColor set. However, Person is still in the
Session. To delete the entire Person information from the session use.
Code View
$this->Session->delete('Person');
5.7.1.6 destroy
• Edit
• View just this section
• Comments (0)
• History
The destroy method will delete the session cookie and all session data stored in the temporary file
system. It will then destroy the PHP session and then create a fresh session.
Code View
$this->Session->destroy()
5.7.1.7 error
• Edit
• View just this section
• Comments (0)
• History
error()
Used to determine the last error in a session.
6 Core Behaviors
• Edit
• Comments (0)
• History
Behaviors add extra functionality to your models. CakePHP comes with a number of built-in behaviors
such as Tree and Containable.
« Methods | ACL »
6.1 ACL
• Edit
• Comments (0)
• History
The Acl behavior provides a way to seamlessly integrate a model with your ACL system. It can create both
AROs or ACOs transparently.
To use the new behavior, you can add it to the $actsAs property of your model. When adding it to the
actsAs array you choose to make the related Acl entry an ARO or an ACO. The default is to create AROs.
Code View
class User extends AppModel {
var $actsAs = array('Acl' => array('type' => 'requester'));
}
This would attach the Acl behavior in ARO mode. To join the ACL behavior in ACO mode use:
Code View
class Post extends AppModel {
var $actsAs = array('Acl' => array('type' => 'controlled'));
}
You can also attach the behavior on the fly like so:
Code View
$this->Post->Behaviors->attach('Acl', array('type' => 'controlled'));
« Core Behaviors | Using the AclBehavior »
« ACL | node() »
6.1.2 node()
• Edit
• Comments (0)
• History
The AclBehavior also allows you to retrieve the Acl node associated with a model record. After setting
$model->id. You can use $model->node() to retrieve the associated Acl node.
You can also retrieve the Acl Node for any row, by passing in a data array.
Code View
$this->User->id = 1;
$node = $this->User->node();
6.2 Containable
• Edit
• Comments (0)
• History
A new addition to the CakePHP 1.2 core is the ContainableBehavior. This model behavior allows you to
filter and limit model find operations. Using Containable will help you cut down on needless wear and tear
on your database, increasing the speed and overall performance of your application. The class will also
help you search and filter your data for your users in a clean and consistent way.
Containable allows you to streamline and simplify operations on your model bindings. It works by
temporarily or permanently altering the associations of your models. It does this by using supplied the
containments to generate a series of bindModel and unbindModel calls.
To use the new behavior, you can add it to the $actsAs property of your model:
Code View
class Post extends AppModel {
var $actsAs = array('Containable');
}
You can also attach the behavior on the fly:
Code View
$this->Post->Behaviors->attach('Containable');
# Using Containable
• Edit
• View just this section
• Comments (0)
• History
To see how Containable works, let's look at a few examples. First, we'll start off with a find() call on a
model named Post. Let's say that Post hasMany Comment, and Post hasAndBelongsToMany Tag. The
amount of data fetched in a normal find() call is rather extensive:
Code View
debug($this->Post->find('all'));
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[author] => Daniel
[email] => [email protected]
[website] => http://example.com
[comment] => First comment
[created] => 2008-05-18 00:00:00
)
[1] => Array
(
[id] => 2
[post_id] => 1
[author] => Sam
[email] => [email protected]
[website] => http://example.net
[comment] => Second comment
[created] => 2008-05-18 00:00:00
)
)
[Tag] => Array
(
[0] => Array
(
[id] => 1
[name] => Awesome
)
[1] => Array
(
[id] => 2
[name] => Baking
)
)
)
[1] => Array
(
[Post] => Array
(...
For some interfaces in your application, you may not need that much information from the Post model.
One thing the ContainableBehavior does is help you cut down on what find() returns.
For example, to get only the post-related information, you can do the following:
Code View
$this->Post->contain();
$this->Post->find('all');
You can also invoke Containable's magic from inside the find() call:
Code View
$this->Post->find('all', array('contain' => false));
Having done that, you end up with something a lot more concise:
//or..
You can also filter the associated Comment data by specifying a condition:
Code View
$this->Post->contain('Comment.author = "Daniel"');
$this->Post->find('all');
//or...
User->Profile
User->Account->AccountSummary
User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes
User->Post->Tag
This is how we retrieve the above associations with Containable:
Code View
$this->User->find('all', array(
'contain'=>array(
'Profile',
'Account' => array(
'AccountSummary'
),
'Post' => array(
'PostAttachment' => array(
'fields' => array('id', 'name'),
'PostAttachmentHistory' => array(
'HistoryNotes' => array(
'fields' => array('id', 'note')
)
)
),
'Tag' => array(
'conditions' => array('Tag.name LIKE' => '%happy%')
)
)
)
));
Keep in mind that contain key is only used once in the main model, you don't to use 'contain' again for
related models
When using 'fields' and 'contain' options - be careful to include all foreign keys that your query directly or
indirectly requires. Please also note that because Containable must to be attached to all models used in
containment, you may consider attaching it to your AppModel.
$users = $this->paginate('User');
By including the 'contain' parameter in the $paginate property it will apply to both the find('count') and
the find('all') done on the model
# ContainableBehavior options
• Edit
• View just this section
• Comments (0)
• History
The ContainableBehavior has a number of options that can be set when the Behavior is attached to a
model. The settings allow you to fine tune the behavior of Containable and work with other behaviors
more easily.
• recursive (boolean, optional) set to true to allow containable to automatically determine the
recursiveness level needed to fetch specified models, and set the model recursiveness to this
level. setting it to false disables this feature. The default value is true.
• notices (boolean, optional) issues E_NOTICES for bindings referenced in a containable call that are
not valid. The default value is true.
•autoFields: (boolean, optional) auto-add needed fields to fetch requested bindings. The default
value is true.
You can change ContainableBehavior settings at run time by reattaching the behavior as seen in Using
behaviors
ContainableBehavior can sometimes cause issues with other behaviors or queries that use aggregate
functions and/or GROUP BY statements. If you get invalid SQL errors due to mixing of aggregate and non-
aggregate fields, try disabling the autoFields setting.
Code View
$this->Post->Behaviors->attach('Containable', array('autoFields' =
6.3 Translate
• Edit
• Comments (0)
• History
TranslateBehavior is actually quite easy to setup and works out of the box with very little configuration. In
this section, you will learn how to add and setup the behavior to use in any model.
If you are using TranslateBehavior in alongside containable issue, be sure to set the 'fields' key for your
queries. Otherwise you could end up with invalid SQL generated.
The current locale is the current value of Configure::read('Config.language'). The value of Config.language
is assigned in the L10n Class - unless it is already set. However, the TranlateBehavior allows you to
override this on-the-fly, which allows the user of your page to create multiple versions without the need to
change his preferences. More about this in the next section.
« Defining the Fields | Retrieve all translation records for a field »
)
)
Note: The model record contains a virtual field called "locale". It indicates which locale is used in this
result.
Note that only fields of the model you are directly doing `find` on will be translated. Models attached via
associations won't be translated because triggering callbacks on associated models is currently not
supported.
bindTranslation($fields, $reset)
$fields is a named-key array of field and association name, where the key is the translatable field and the
value is the fake association name.
Code View
$this->Post->bindTranslation(array ('name' => 'nameTranslation'));
$this->Post->find('all', array ('recursive'=>1)); // need at least recursive 1 for this to work.
With this setup the result of your find() should look something like this:
Code View
Array
(
[Post] => Array
(
[id] => 1
[name] => Beispiel Eintrag
[body] => lorem ipsum...
[locale] => de_de
)
)
)
« Conclusion | Saving in another language »
To tell a model in what language the content is going to be you simply change the value of the $locale
property on the model before you save the data to the database. You can do that either in your controller
or you can define it directly in the model.
Example A: In your controllerCode View
<?php
class PostsController extends AppController {
var $name = 'Posts';
function add() {
if ($this->data) {
$this->Post->locale = 'de_de'; // we are going to save the german version
$this->Post->create();
if ($this->Post->save($this->data)) {
$this->redirect(array('action' => 'index'));
}
}
}
}
?>
Example B: In your modelCode View
<?php
class Post extends AppModel {
var $name = 'Post';
var $actsAs = array(
'Translate' => array(
'name'
)
);
6.4 Tree
• Edit
• Comments (0)
• History
It's fairly common to want to store hierarchical data in a database table. Examples of such data might be
categories with unlimited subcategories, data related to a multilevel menu system or a literal
representation of hierarchy such as is used to store access control objects with ACL logic.
For small trees of data, or where the data is only a few levels deep it is simple to add a parent_id field to
your database table and use this to keep track of which item is the parent of what. Bundled with cake
however, is a powerful behavior which allows you to use the benefits of MPTT logic without worrying about
any of the intricacies of the technique - unless you want to ;).
6.4.1 Requirements
• Edit
• Comments (0)
• History
To use the tree behavior, your database table needs 3 fields as listed below (all are ints):
If you are familiar with MPTT logic you may wonder why a parent field exists - quite simply it's easier to do
certain tasks if a direct parent link is stored on the database - such as finding direct children.
6.4.2 Basic Usage
• Edit
• Comments (0)
• History
The tree behavior has a lot packed into it, but let's start with a simple example - create the following
database table and put some data in it:
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(1, 'My Categories', NULL, 1,
30);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(2, 'Fun', 1, 2, 15);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(3, 'Sport', 2, 3, 8);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(4, 'Surfing', 3, 4, 5);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(5, 'Extreme knitting', 3, 6, 7);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(6, 'Friends', 2, 9, 14);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(7, 'Gerald', 6, 10, 11);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(8, 'Gwendolyn', 6, 12, 13);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(9, 'Work', 1, 16, 29);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(10, 'Reports', 9, 17, 22);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(11, 'Annual', 10, 18, 19);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(12, 'Status', 10, 20, 21);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(13, 'Trips', 9, 23, 28);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(14, 'National', 13, 24, 25);
INSERT INTO `categories` (`id`, `name`, `parent_id`, `lft`, `rght`) VALUES(15, 'International', 13, 26, 27);
For the purpose of checking that everything is setup correctly, we can create a test method and output
the contents of our category tree to see what it looks like. With a simple controller:
Code View
<?php
class CategoriesController extends AppController {
function index() {
$this->data = $this->Category->generatetreelist(null, null, null,
' ');
debug ($this->data); die;
}
}
?>
and an even simpler model definition:
Code View
<?php
// app/models/category.php
class Category extends AppModel {
var $name = 'Category';
var $actsAs = array('Tree');
}
?>
We can check what our category tree data looks like by visiting /categories You should see something like
this:
• My Categories
• Fun
• Sport
• Surfing
• Extreme knitting
• Friends
• Gerald
• Gwendolyn
• Work
• Reports
• Annual
• Status
• Trips
• National
• International
• My Categories
• Fun
• Sport
• Surfing
• Extreme knitting
• Skating New
• Friends
• Gerald
• Gwendolyn
• Work
• Reports
• Annual
• Status
• Trips
• National
• International
• My Categories
• Fun
• Sport
• Surfing
Moving data around in your tree is also a simple affair. Let's say that Extreme fishing does not belong
under Sport, but instead should be located under Other People's Categories. With the following code:
Code View
// pseudo controller code
$this->Category->id = 5; // id of Extreme fishing
$newParentId = $this->Category->field('id', array('name' => 'Other People\'s Categories'));
$this->Category->save(array('parent_id' => $newParentId));
As would be expected the structure would be modified to:
• My Categories
• Fun
• Sport
• Surfing
• Skating
• Friends
• Gerald
• Gwendolyn
• Work
• Reports
• Annual
• Status
• Trips
• National
• International
• Other People's Categories
• My Categories
• Fun
• Sport
• Surfing
• Skating
• Friends
• Gerald
• Gwendolyn
• Work
• Trips
• National
• International
• Other People's Categories
• Extreme fishing
Most tree behavior methods return and rely on data being sorted by the lft field. If you call find() and do
not order by lft, or call a tree behavior method and pass a sort order, you may get undesirable results.
6.4.2.4.1 Children
• Edit
• View just this section
• Comments (0)
• History
The children method takes the primary key value (the id) of a row and returns the children, by default in
the order they appear in the tree. The second optional parameter defines whether or not only direct
children should be returned. Using the example data from the previous section:
Code View
$allChildren = $this->Category->children(1); // a flat array with 11 items
// -- or --
$this->Category->id = 1;
$allChildren = $this->Category->children(); // a flat array with 11 items
• $conditions = null
• $keyPath = Model's primary key
• $valuePath = Model's displayField
• $spacer = '_'
• $recursive = Model's recursive setting
Code View
$treelist = $this->Category->generatetreelist();
Output:
array(
[1] => "My Categories",
[2] => "_Fun",
[3] => "__Sport",
[4] => "___Surfing",
[16] => "___Skating",
[6] => "__Friends",
[7] => "___Gerald",
[8] => "___Gwendolyn",
[9] => "_Work",
[13] => "__Trips",
[14] => "___National",
[15] => "___International",
[17] => "Other People's Categories",
[5] => "_Extreme fishing"
)
6.4.2.4.4 getparentnode
• Edit
• View just this section
• Comments (0)
• History
This convenience function will, as the name suggests, return the parent node for any node, or false if the
node has no parent (its the root node). For example:
Code View
$parent = $this->Category->getparentnode(2); //<- id for fun
// $parent contains All categories
6.4.2.4.5 getpath
• Edit
• View just this section
• Comments (0)
• History
The 'path' when refering to hierachial data is how you get from where you are to the top. So for example
the path from the category "International" is:
• My Categories
• ...
• Work
• Trips
• ...
• International
Using the id of "International" getpath will return each of the parents in turn (starting from the top).
Code View
$parents = $this->Category->getpath(15);
// contents of $parents
array(
[0] => array('Category' => array('id' => 1, 'name' => 'My Categories', ..)),
[1] => array('Category' => array('id' => 9, 'name' => 'Work', ..)),
[2] => array('Category' => array('id' => 13, 'name' => 'Trips', ..)),
[3] => array('Category' => array('id' => 15, 'name' => 'International', ..)),
)
« Requirements | Advanced Usage »
6.4.3.1 moveDown
• Edit
• View just this section
• Comments (0)
• History
Used to move a single node down the tree. You need to provide the ID of the element to be moved and a
positive number of how many positions the node should be moved down. All child nodes for the specified
node will also be moved.
Here is an example of a controller action (in a controller named Categories) that moves a specified node
down the tree:
Code View
function movedown($name = null, $delta = null) {
$cat = $this->Category->findByName($name);
if (empty($cat)) {
$this->Session->setFlash('There is no category named ' . $name);
$this->redirect(array('action' => 'index'), null, true);
}
$this->Category->id = $cat['Category']['id'];
if ($delta > 0) {
$this->Category->moveDown($this->Category->id, abs($delta));
} else {
$this->Session->setFlash('Please provide the number of positions the field should be
moved down.');
}
6.4.3.2 moveUp
• Edit
• View just this section
• Comments (0)
• History
Used to move a single node up the tree. You need to provide the ID of the element to be moved and a
positive number of how many positions the node should be moved up. All child nodes will also be moved.
Here's an example of a controller action (in a controller named Categories) that moves a node up the tree:
Code View
function moveup($name = null, $delta = null){
$cat = $this->Category->findByName($name);
if (empty($cat)) {
$this->Session->setFlash('There is no category named ' . $name);
$this->redirect(array('action' => 'index'), null, true);
}
$this->Category->id = $cat['Category']['id'];
if ($delta > 0) {
$this->Category->moveup($this->Category->id, abs($delta));
} else {
$this->Session->setFlash('Please provide a number of positions the category should be
moved up.');
}
}
For example, if you would like to move the category "Gwendolyn" up one position you would request
/categories/moveup/Gwendolyn/1. Now the order of Friends will be Gwendolyn, Gerald.
6.4.3.3 removeFromTree
• Edit
• View just this section
• Comments (0)
• History
removeFromTree($id=null, $delete=false)
Using this method wil either delete or move a node but retain its sub-tree, which will be reparented one
level higher. It offers more control than delete(), which for a model using the tree behavior will remove the
specified node and all of its children.
Taking the following tree as a starting point:
• My Categories
• Fun
• Sport
• Surfing
• Extreme knitting
• Skating
• My Categories
• Fun
• Surfing
• Extreme knitting
• Skating
• Sport Moved
This demonstrates the default behavior of removeFromTree of moving the node to have no parent, and re-
parenting all children.
If however the following code snippet was used with the id for 'Sport'
Code View
$this->Node->removeFromTree($id,true);
The tree would become
• My Categories
• Fun
• Surfing
• Extreme knitting
• Skating
This demonstrates the alternate use for removeFromTree, the children have been reparented and 'Sport'
has been deleted.
6.4.3.4 reorder
• Edit
• View just this section
• Comments (0)
• History
This method can be used to sort hierarchical data.
6.4.4.1 Recover
• Edit
• View just this section
• Comments (0)
• History
recover(&$model, $mode = 'parent', $missingParentAction = null)
The mode parameter is used to specify the source of info that is valid/correct. The opposite source of data
will be populated based upon that source of info. E.g. if the MPTT fields are corrupt or empty, with the
$mode 'parent' the values of the parent_id field will be used to populate the left and right fields. The
missingParentActionparameter only applies to "parent" mode and determines what to do if the parent
field contains an id that is not present.
Available $mode options:
• 'parent' - use the existing parent_id's to update the lft and rght fields
• 'tree' - use the existing lft and rght fields to update parent_id
Available missingParentActions options when using mode='parent':
• null - do nothing and carry on
• 'return' - do nothing and return
• 'delete' - delete the node
• int - set the parent_id to this id
Code View
// Rebuild all the left and right fields based on the parent_id
$this->Category->recover();
// or
$this->Category->recover('parent');
// Rebuild all the parent_id's based on the lft and rght fields
$this->Category->recover('tree');
6.4.4.2 Reorder
• Edit
• View just this section
• Comments (0)
• History
reorder(&$model, $options = array())
Reorders the nodes (and child nodes) of the tree according to the field and direction specified in the
parameters. This method does not change the parent of any node.
Reordering affects all nodes in the tree by default, however the following options can affect the process:
6.4.4.3 Verify
• Edit
• View just this section
• Comments (0)
• History
verify(&$model)
Returns true if the tree is valid otherwise an array of errors, with fields for type, incorrect index and
message.
Each record in the output array is an array of the form (type, id, message)
Array
(
[0] => Array
(
[0] => node
[1] => 3
[2] => left and right values identical
)
[1] => Array
(
[0] => node
[1] => 2
[2] => The parent node 999 doesn't exist
)
[10] => Array
(
[0] => index
[1] => 123
[2] => missing
)
[99] => Array
(
[0] => node
[1] => 163
[2] => left greater than right
)
)
« reorder | Core Helpers »
7 Core Helpers
• Edit
• Comments (0)
• History
Helpers are the component-like classes for the presentation layer of your application. They contain
presentational logic that is shared between many views, elements, or layouts.
This section describes each of the helpers that come with CakePHP such as Form, Html, JavaScript and
RSS.
Read Helpers to learn more about helpers and how you can build your own helpers.
« Data Integrity | AJAX »
7.1 AJAX
• Edit
• Comments (2)
• History
The AjaxHelper utilizes the ever-popular Prototype and script.aculo.us libraries for Ajax operations and
client side effects. To use the AjaxHelper, you must have a current version of the JavaScript libraries from
www.prototypejs.org and http://script.aculo.us placed in /app/webroot/js/. In addition, you must include
the Prototype and script.aculo.us JavaScript libraries in any layouts or views that require AjaxHelper
functionality.
You'll need to include the Ajax and Javascript helpers in your controller:
Code View
class WidgetsController extends AppController {
var $name = 'Widgets';
var $helpers = array('Html','Ajax','Javascript');
}
Once you have the javascript helper included in your controller, you can use the javascript helper link()
method to include Prototype and Scriptaculous:
Code View
echo $html->script('prototype');
echo $html->script('scriptaculous');
Now you can use the Ajax helper in your view:
Code View
$ajax->whatever();
If the RequestHandler Component is included in the controller then CakePHP will automatically apply the
Ajax layout when an action is requested via AJAX
Code View
class WidgetsController extends AppController {
var $name = 'Widgets';
var $helpers = array('Html','Ajax','Javascript');
var $components = array( 'RequestHandler' );
}
$options['with'] A URL-encoded string which will be added to the URL for get methods or in to the post body for any other
method. Example:x=1&foo=bar&y=2. The parameters will be available in $this-
>params['form'] or available in $this->data depending on formatting. For more information see
the Prototype Serialize method.
$options['before'] Executed before request is made. A common use for this callback is to enable the visibility of a progress
indicator.
$options['loading'] Callback code to be executed while data is being fetched from server.
$options['after'] JavaScript called immediately after request has run; fires before the $options['loading'] callback runs.
$options['loaded'] Callback code to be executed when the remote document has been received by client.
$options['interactive']Called when the user can interact with the remote document, even though it has not finished loading.
7.1.2 Methods
• Edit
• Comments (0)
• History
7.1.2.1 link
• Edit
• View just this section
• Comments (0)
• History
link(string $title, mixed $href, array $options, string $confirm, boolean
$escapeTitle)
Returns a link to a remote action defined by $options['url'] or $href that's called in the background using
XMLHttpRequest when the link is clicked. The result of that request can then be inserted into a DOM
object whose id can be specified with $options['update'].
If $options['url'] is blank the href is used instead
Example:
Code View
<div id="post">
</div>
<?php echo $ajax->link(
'View Post',
array( 'controller' => 'posts', 'action' => 'view', 1 ),
array( 'update' => 'post' )
);
?>
By default, these remote requests are processed asynchronously during which various callbacks can be
triggered
Example:
Code View
<div id="post">
</div>
<?php echo $ajax->link(
'View Post',
array( 'controller' => 'posts', 'action' => 'post', 1 ),
array( 'update' => 'post', 'complete' => 'alert( "Hello World" )' )
);
?>
To use synchronous processing specify $options['type'] = 'synchronous'.
To automatically set the ajax layout include the RequestHandler component in your controller
By default the contents of the target element are replaced. To change this behaviour set the
$options['position']
Example:
Code View
<div id="post">
</div>
<?php echo $ajax->link(
'View Post',
array( 'controller' => 'posts', 'action' => 'view', 1),
array( 'update' => 'post', 'position' => 'top' )
);
?>
$confirm can be used to call up a JavaScript confirm() message before the request is run. Allowing the
user to prevent execution.
Example:
Code View
<div id="post">
</div>
<?php echo $ajax->link(
'Delete Post',
array( 'controller' => 'posts', 'action' => 'delete', 1 ),
array( 'update' => 'post' ),
'Do you want to delete this post?'
);
?>
7.1.2.2 remoteFunction
• Edit
• View just this section
• Comments (0)
• History
remoteFunction(array $options);
This function creates the JavaScript needed to make a remote call. It is primarily used as a helper for
link(). This is not used very often unless you need to generate some custom scripting.
The $options for this function are the same as for the link method
Example:
Code View
<div id="post">
</div>
<script type="text/javascript">
<?php echo $ajax->remoteFunction(
array(
'url' => array( 'controller' => 'posts', 'action' => 'view', 1 ),
'update' => 'post'
)
); ?>
</script>
It can also be assigned to HTML Event Attributes:
Code View
<?php
$remoteFunction = $ajax->remoteFunction(
array(
'url' => array( 'controller' => 'posts', 'action' => 'view', 1 ),
'update' => 'post' )
);
?>
<div id="post" onmouseover="<?php echo $remoteFunction; ?>" >
Mouse Over This
</div>
If $options['update'] is not passed, the browser will ignore the server response.
7.1.2.3 remoteTimer
• Edit
• View just this section
• Comments (0)
• History
remoteTimer(array $options)
Periodically calls the action at $options['url'], every $options['frequency'] seconds. Usually used to update
a specific div (specified by $options['update']) with the result of the remote call. Callbacks can be used.
remoteTimer is the same as the remoteFunction except for the extra $options['frequency']
Example:
Code View
<div id="post">
</div>
<?php
echo $ajax->remoteTimer(
array(
'url' => array( 'controller' => 'posts', 'action' => 'view', 1 ),
'update' => 'post', 'complete' => 'alert( "request completed" )',
'position' => 'bottom', 'frequency' => 5
)
);
?>
The default $options['frequency'] is 10 seconds
7.1.2.4 form
• Edit
• View just this section
• Comments (0)
• History
form(string $action, string $type, array $options)
Returns a form tag that submits to $action using XMLHttpRequest instead of a normal HTTP request via
$type ('post' or 'get'). Otherwise, form submission will behave exactly like normal: data submitted is
available at $this->data inside your controllers. If $options['update'] is specified, it will be updated with
the resulting document. Callbacks can be used.
The options array should include the model name e.g. Code View
$ajax->form('edit','post',array('model'=>'User','update'=>'UserInfoDiv'));
Alternatively, if you need to cross post to another controller from your form: Code View
$ajax->form(array('type' => 'post',
'options' => array(
'model'=>'User',
'update'=>'UserInfoDiv',
'url' => array(
'controller' => 'comments',
'action' => 'edit'
)
)
));
You should not use the $ajax->form() and $ajax->submit() in the same form. If you want the form
validation to work properly use the $ajax->submit()method as shown below.
7.1.2.5 submit
• Edit
• View just this section
• Comments (0)
• History
submit(string $title, array $options)
Returns a submit button that submits the form to $options['url'] and updates the div specified in
$options['update']
Code View
<div id='testdiv'>
<?php
echo $form->create('User');
echo $form->input('email');
echo $form->input('name');
echo $ajax->submit('Submit', array('url'=> array('controller'=>'users', 'action'=>'add'), 'update' =>
'testdiv'));
echo $form->end();
?>
</div>
Use the $ajax->submit() method if you want form validation to work properly. i.e. You want the messages
you specify in your validation rules to show up correctly.
7.1.2.6 observeField
• Edit
• View just this section
• Comments (0)
• History
observeField(string $fieldId, array $options)
Observes the field with the DOM id specified by $field_id (every $options['frequency'] seconds ) and
makes an XMLHttpRequest when its contents have changed.
Code View
<?php echo $form->create( 'Post' ); ?>
<?php $titles = array( 1 => 'Tom', 2 => 'Dick', 3 => 'Harry' ); ?>
<?php echo $form->input( 'title', array( 'options' => $titles ) ) ?>
</form>
<?php
echo $ajax->observeField( 'PostTitle',
array(
'url' => array( 'action' => 'edit' ),
'frequency' => 0.2,
)
);
?>
observeField uses the same options as link
The field to send up can be set using $options['with']. This defaults to Form.Element.serialize('$fieldId').
Data submitted is available at $this->datainside your controllers. Callbacks can be used with this function.
To send up the entire form when the field changes use $options['with'] = Form.serialize( $('Form ID') )
7.1.2.7 observeForm
• Edit
• View just this section
• Comments (0)
• History
observeForm(string $form_id, array $options)
Similar to observeField(), but operates on an entire form identified by the DOM id $form_id. The supplied
$options are the same as observeField(), except the default value of the $options['with'] option evaluates
to the serialized (request string) value of the form.
7.1.2.8 autoComplete
• Edit
• View just this section
• Comments (0)
• History
autoComplete(string $fieldId, string $url, array $options)
Renders a text field with $fieldId with autocomplete. The remote action at $url should return a suitable list
of autocomplete terms. Often an unordered list is used for this. First, you need to set up a controller action
that fetches and organizes the data you'll need for your list, based on user input:
Code View
function autoComplete() {
//Partial strings will come from the autocomplete field as
//$this->data['Post']['subject']
$this->set('posts', $this->Post->find('all', array(
'conditions' => array(
'Post.subject LIKE' => $this->data['Post']
['subject'].'%'
),
'fields' => array('subject')
)));
$this->layout = 'ajax';
}
Next, create app/views/posts/auto_complete.ctp that uses that data and creates an unordered list in
(X)HTML:
Code View
<ul>
<?php foreach($posts as $post): ?>
<li><?php echo $post['Post']['subject']; ?></li>
<?php endforeach; ?>
</ul>
Finally, utilize autoComplete() in a view to create your auto-completing form field:
Code View
<?php echo $form->create('User', array('url' => '/users/index')); ?>
<?php echo $ajax->autoComplete('Post.subject', '/posts/autoComplete')?>
<?php echo $form->end('View Post')?>
Once you've got the autoComplete() call working correctly, use CSS to style the auto-complete suggestion
box. You might end up using something similar to the following:
div.auto_complete {
position :absolute;
width :250px;
background-color :white;
border :1px solid #888;
margin :0px;
padding :0px;
}
li.selected { background-color: #ffb; }
7.1.2.9 isAjax
• Edit
• View just this section
• Comments (0)
• History
isAjax()
Allows you to check if the current request is a Prototype Ajax request inside a view. Returns a boolean.
Can be used for presentational logic to show/hide blocks of content.
$options['handleImage'] The id of the image that represents the handle. This is used to swap out the image src with disabled
image src when the slider is enabled. Used in conjunction with handleDisabled.
$options['increment'] Sets the relationship of pixels to values. Setting to 1 will make each pixel adjust the slider value by one.
$options['handleDisabled'] The id of the image that represents the disabled handle. This is used to change the image src when the
slider is disabled. Used in conjunction handleImage.
$options['change'] JavaScript callback fired when the slider has finished moving, or has its value changed. The callback
$options['onChange'] function receives the slider's current value as a parameter.
$options['slide'] JavaScript callback that is called whenever the slider is moved by dragging. It receives the slider's
$options['onSlide'] current value as a parameter.
7.1.2.12 editor
• Edit
• View just this section
• Comments (0)
• History
editor(string $id, string $url, array $options)
Creates an in-place editor at DOM id. The supplied $url should be an action that is responsible for saving
element data. For more information and demos seehttp://github.com/madrobby/scriptaculous/wikis/ajax-
inplaceeditor.
Common options might include:
$options['savingText'] The text shown while the text is sent to the server
$options['formId']
$options['externalControl']
$options['rows'] The row height of the input field
$options['savingClassName']
$options['formClassName']
$options['loadingText']
$options['loadTextURL']
Example
Code View
<div id="in_place_editor_id">Text To Edit</div>
<?php
echo $ajax->editor(
"in_place_editor_id",
array(
'controller' => 'Posts',
'action' => 'update_title',
$id
),
array()
);
?>
7.1.2.13 sortable
• Edit
• View just this section
• Comments (0)
• History
sortable(string $id, array $options)
Makes a list or group of floated objects contained by $id sortable. The options array supports a number of
parameters. To find out more about sortable seehttp://wiki.github.com/madrobby/scriptaculous/sortable.
Common options might include:
$options['only'] Allows for further filtering of child elements. Accepts a CSS class.
$options['constraint'] Restrict the movement of the draggable elements. accepts 'horizontal' or 'vertical'. Defaults to vertical.
$options['handle'] Makes the created Draggables use handles, see the handle option on Draggables.
$options['onUpdate'] Called when the drag ends and the Sortable's order is changed in any way. When dragging from one Sortable to
another, the callback is called once on each Sortable.
$options['ghosting'] If set to true, dragged elements of the sortable will be cloned and appear as a ghost, instead
7.2 Cache
• Edit
• Comments (0)
• History
The Cache helper assists in caching entire layouts and views, saving time repetitively retrieving data.
View Caching in Cake temporarily stores parsed layouts and views with the storage engine of choice. It
should be noted that the Cache helper works quite differently than other helpers. It does not have
methods that are directly called. Instead a view is marked with cache tags indicating which blocks of
content should not be cached.
When a URL is requested, Cake checks to see if that request string has already been cached. If it has, the
rest of the url dispatching process is skipped. Any nocache blocks are processed normally and the view is
served. This creates a big savings in processing time for each request to a cached URL as minimal code is
executed. If Cake doesn't find a cached view, or the cache has expired for the requested URL it continues
to process the request normally.
With this in mind caching is not permanent storage and should never be used to permanently store
anything. And only cache things that can be regenerated when needed.
The File Engine is the default caching engine used by cake. It writes flat files to the
File
filesystem and it has several optional parameters but works well with the defaults.
The APC engine implements the Alternative PHP Cache opcode Cacher. Like XCache,
APC
this engine caches the compiled PHP opcode.
The XCache caching engine is functionally similar to APC other than it implements
XCache the XCache opcode caching engine. It requires the entry of a user and password to
work properly.
The Memcache engine works with a memcaching server allowing you to create a
Memcache cache object in system memory. More information on memcaching can be found on
php.net and memcached
To use the cache helper in any view or controller, you must first uncomment and set
Configure::Cache.check to true in core.php of your app/config folder. If this is not set to true, then the
cache will not be checked or created.
Using the example of an ArticlesController, that receives a lot of traffic that needs to be cached.
If you need to manually clear the cache, you can do so by calling Cache::clear(). This will clear all cached
data, excluding cached view files. If you need to clear the cached view files, use clearCache().
7.3 Form
• Edit
• Comments (0)
• History
The FormHelper is a new addition to CakePHP. Most of the heavy lifting in form creation is now done using
this new class, rather than (now deprecated) methods in the HtmlHelper. The FormHelper focuses on
creating forms quickly, in a way that will streamline validation, re-population and layout. The FormHelper
is also flexible - it will do almost everything for you automagically, or you can use specific methods to get
only what you need.
7.3.1 Creating Forms
• Edit
• Comments (0)
• History
The first method you’ll need to use in order to take advantage of the FormHelper is create(). This special
method outputs an opening form tag.
create(string $model = null, array $options = array())
All parameters are optional. If create() is called with no parameters supplied, it assumes you are building
a form that submits to the current controller, via either theadd() or edit() action. The default method for
form submission is POST. The form element also is returned with a DOM ID. The ID is generated using the
name of the model, and the name of the controller action, CamelCased. If I were to call create() inside a
UsersController view, I’d see something like the following output in the rendered view:
Code View
<form id="UserAddForm" method="post" action="/users/add">
You can also pass false for $model. This will place your form data into the array: $this->data (instead of in
the sub-array: $this->data['Model']). This can be handy for short forms that may not represent anything in
your database.
The create() method allows us to customize much more using the parameters, however. First, you can
specify a model name. By specifying a model for a form, you are creating that form's context. All fields are
assumed to belong to this model (unless otherwise specified), and all models referenced are assumed to
be associated with it. If you do not specify a model, then it assumes you are using the default model for
the current controller.
Code View
<?php echo $form->create('Recipe'); ?>
//Output:
<form id="RecipeAddForm" method="post" action="/recipes/add">
This will POST the form data to the add() action of RecipesController. However, you can also use the same
logic to create an edit form. The FormHelper uses the $this->data property to automatically detect
whether to create an add or edit form. If $this->data contains an array element named after the form's
model, and that array contains a non-empty value of the model's primary key, then the FormHelper will
create an edit form for that record. For example, if we browse tohttp://site.com/recipes/edit/5, we might
get the following:
Code View
// controllers/recipes_controller.php:
<?php
function edit($id = null) {
if (empty($this->data)) {
$this->data = $this->Recipe->findById($id);
} else {
// Save logic goes here
}
}
?>
// views/recipes/edit.ctp:
//Output:
<form id="RecipeEditForm" method="post" action="/recipes/edit/5">
<input type="hidden" name="_method" value="PUT" />
Since this is an edit form, a hidden input field is generated to override the default HTTP method.
The $options array is where most of the form configuration happens. This special array can contain a
number of different key-value pairs that affect the way the form tag is generated.
7.3.1.1 $options[‘type’]
• Edit
• View just this section
• Comments (0)
• History
This key is used to specify the type of form to be created. Valid values include ‘post’, ‘get’, ‘file’, ‘put’ and
‘delete’.
Supplying either ‘post’ or ‘get’ changes the form submission method accordingly.
Code View
<?php echo $form->create('User', array('type' => 'get')); ?>
//Output:
<form id="UserAddForm" method="get" action="/users/add">
Specifying ‘file’ changes the form submission method to ‘post’, and includes an enctype of
“multipart/form-data” on the form tag. This is to be used if there are any file elements inside the form.
The absence of the proper enctype attribute will cause the file uploads not to function.
Code View
<?php echo $form->create('User', array('type' => 'file')); ?>
//Output:
<form id="UserAddForm" enctype="multipart/form-data" method="post" action="/users/add">
When using ‘put’ or ‘delete’, your form will be functionally equivalent to a 'post' form, but when
submitted, the HTTP request method will be overridden with 'PUT' or 'DELETE', respectively. This allows
CakePHP to emulate proper REST support in web browsers.
7.3.1.2 $options[‘action’]
• Edit
• View just this section
• Comments (0)
• History
The action key allows you to point the form to a specific action in your current controller. For example, if
you’d like to point the form to the login() action of the current controller, you would supply an $options
array like the following:
Code View
<?php echo $form->create('User', array('action' => 'login')); ?>
//Output:
<form id="UserLoginForm" method="post" action="/users/login">
</form>
7.3.1.3 $options[‘url’]
• Edit
• View just this section
• Comments (0)
• History
If the desired form action isn’t in the current controller, you can specify a URL for the form action using
the ‘url’ key of the $options array. The supplied URL can be relative to your CakePHP application, or can
point to an external domain.
Code View
<?php echo $form->create(null, array('url' => '/recipes/add')); ?>
// or
<?php echo $form->create(null, array('url' => array('controller' => 'recipes', 'action' => 'add'))); ?>
//Output:
<form method="post" action="/recipes/add">
//Output:
<form method="get" action="http://www.google.com/search">
Also check HtmlHelper::url method for more examples of different types of urls.
7.3.1.4 $options[‘default’]
• Edit
• View just this section
• Comments (0)
• History
If ‘default’ has been set to boolean false, the form’s submit action is changed so that pressing the submit
button does not submit the form. If the form is meant to be submitted via AJAX, setting ‘default’ to false
suppresses the form’s default behavior so you can grab the data and submit it via AJAX instead.
Output:
<div class="submit">
<input type="submit" value="Finish" />
</div>
</form>
<?php
echo $form->input('username'); //text
echo $form->input('password'); //password
echo $form->input('approved'); //day, month, year, hour, minute, meridian
echo $form->input('quote'); //textarea
?>
7.3.3.2 $options[‘type’]
• Edit
• View just this section
• Comments (0)
• History
You can force the type of an input (and override model introspection) by specifying a type. In addition to
the field types found in the table above, you can also create ‘file’, and ‘password’ inputs.
Code View
<?php echo $form->input('field', array('type' => 'file')); ?>
Output:
<div class="input">
<label for="UserField">Field</label>
<input type="file" name="data[User][field]" value="" id="UserField" />
</div>
Output:
<div class="input">
--before--
<label for="UserField">Field</label>
--between---
<input name="data[User][field]" type="text" value="" id="UserField" />
--after--
</div>
For radio type input the 'separator' attribute can be used to inject markup to separate each input/label
pair.
Code View
<?php echo $form->input('field', array(
'before' => '--before--',
'after' => '--after--',
'between' => '--between---',
'separator' => '--separator--',
'options' => array('1', '2')
));?>
Output:
<div class="input">
--before--
<input name="data[User][field]" type="radio" value="1" id="UserField1" />
<label for="UserField1">1</label>
--separator--
<input name="data[User][field]" type="radio" value="2" id="UserField2" />
<label for="UserField2">2</label>
--between---
--after--
</div>
For date and datetime type elements the 'separator' attribute can be used to change the string between
select elements. Defaults to '-'.
7.3.3.4 $options[‘options’]
• Edit
• View just this section
• Comments (0)
• History
This key allows you to manually specify options for a select input, or for a radio group. Unless the ‘type’ is
specified as ‘radio’, the FormHelper will assume that the target output is a select input.
Code View
<?php echo $form->input('field', array('options' => array(1,2,3,4,5))); ?>
Output:
<div class="input">
<label for="UserField">Field</label>
<select name="data[User][field]" id="UserField">
<option value="0">1</option>
<option value="1">2</option>
<option value="2">3</option>
<option value="3">4</option>
<option value="4">5</option>
</select>
</div>
Options can also be supplied as key-value pairs.
Code View
<?php echo $form->input('field', array('options' => array(
'Value 1'=>'Label 1',
'Value 2'=>'Label 2',
'Value 3'=>'Label 3'
))); ?>
Output:
<div class="input">
<label for="UserField">Field</label>
<select name="data[User][field]" id="UserField">
<option value="Value 1">Label 1</option>
<option value="Value 2">Label 2</option>
<option value="Value 3">Label 3</option>
</select>
</div>
If you would like to generate a select with optgroups, just pass data in hierarchical format. Works on
multiple checkboxes and radio buttons too, but instead of optgroups wraps elements in fieldsets.
Code View
<?php echo $form->input('field', array('options' => array(
'Label1' => array(
'Value 1'=>'Label 1',
'Value 2'=>'Label 2'
),
'Label2' => array(
'Value 3'=>'Label 3'
)
))); ?>
Output:
<div class="input">
<label for="UserField">Field</label>
<select name="data[User][field]" id="UserField">
<optgroup label="Label1">
<option value="Value 1">Label 1</option>
<option value="Value 2">Label 2</option>
</optgroup>
<optgroup label="Label2">
<option value="Value 3">Label 3</option>
</optgroup>
</select>
</div>
7.3.3.5 $options[‘multiple’]
• Edit
• View just this section
• Comments (0)
• History
If ‘multiple’ has been set to true for an input that outputs a select, the select will allow multiple selections.
Code View
echo $form->input('Model.field', array( 'type' => 'select', 'multiple' => true ));
Alternatively set ‘multiple’ to ‘checkbox’ to output a list of related check boxes.
Code View
echo $form->input('Model.field', array(
'type' => 'select',
'multiple' => 'checkbox',
'options' => array(
'Value 1' => 'Label 1',
'Value 2' => 'Label 2'
)
));
Output:
7.3.3.6 $options[‘maxLength’]
• Edit
• View just this section
• Comments (1)
• History
Defines the maximum number of characters allowed in a text input.
7.3.3.7 $options[‘div’]
• Edit
• View just this section
• Comments (0)
• History
Use this option to set attributes of the input's containing div. Using a string value will set the div's class
name. An array will set the div's attributes to those specified by the array's keys/values. Alternatively, you
can set this key to false to disable the output of the div.
<div class="class_name">
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
Setting multiple attributes:
Code View
echo $form->input('User.name', array('div' => array('id' => 'mainDiv', 'title' => 'Div Title', 'style'
=> 'display:block')));
Output:
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
7.3.3.8 $options[‘label’]
• Edit
• View just this section
• Comments (0)
• History
Set this key to the string you would like to be displayed within the label that usually accompanies the
input.
Code View
<?php echo $form->input( 'User.name', array( 'label' => 'The User Alias' ) );?>
Output:
<div class="input">
<label for="UserName">The User Alias</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
Alternatively, set this key to false to disable the output of the label.
Code View
<?php echo $form->input( 'User.name', array( 'label' => false ) ); ?>
Output:
<div class="input">
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
Set this to an array to provide additional options for the label element. If you do this, you can use a text
key in the array to customize the label text.
Code View
<?php echo $form->input( 'User.name', array( 'label' => array('class' => 'thingy', 'text' => 'The User
Alias') ) ); ?>
Output:
<div class="input">
<label for="UserName" class="thingy">The User Alias</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
7.3.3.9 $options['legend']
• Edit
• View just this section
• Comments (0)
• History
Some inputs like radio buttons will be automatically wrapped in a fieldset with a legend title derived from
the fields name. The title can be overridden with this option. Setting this option to false will completely
eliminate the fieldset.
7.3.3.10 $options[‘id’]
• Edit
• View just this section
• Comments (0)
• History
Set this key to force the value of the DOM id for the input.
7.3.3.11 $options['error']
• Edit
• View just this section
• Comments (0)
• History
Using this key allows you to override the default model error messages and can be used, for example, to
set i18n messages. It has a number of suboptions which control the wrapping element, wrapping element
class name, and whether HTML in the error message will be escaped.
7.3.3.12 $options['default']
• Edit
• View just this section
• Comments (0)
• History
Used to set a default value for the input field. The value is used if the data passed to the form does not
contain a value for the field (or if no data is passed at all).
Example usage:
Code View
<?php
echo $form->input('ingredient', array('default'=>'Sugar'));
?>
Example with select field (Size "Medium" will be selected as default):
Code View
<?php
$sizes = array('s'=>'Small', 'm'=>'Medium', 'l'=>'Large');
echo $form->input('size', array('options'=>$sizes, 'default'=>'m'));
?>
You cannot use default to check a checkbox - instead you might set the value in $this->data in your
controller, $form->data in your view, or set the input option checked to true.
Date and datetime fields' default values can be set by using the 'selected' key.
7.3.3.13 $options[‘selected’]
• Edit
• View just this section
• Comments (1)
• History
Used in combination with a select-type input (i.e. For types select, date, time, datetime). Set ‘selected’ to
the value of the item you wish to be selected by default when the input is rendered.
Code View
echo $form->input('close_time', array('type' => 'time', 'selected' => '13:30:00'));
The selected key for date and datetime inputs may also be a UNIX timestamp.
7.3.3.15 $options[‘empty’]
• Edit
• View just this section
• Comments (0)
• History
If set to true, forces the input to remain empty.
When passed to a select list, this creates a blank option with an empty value in your drop down list. If you
want to have a empty value with text displayed instead of just a blank option, pass in a string to empty.
Code View
<?php echo $form->input('field', array('options' => array(1,2,3,4,5), 'empty' => '(choose one)')); ?>
Output:
<div class="input">
<label for="UserField">Field</label>
<select name="data[User][field]" id="UserField">
<option value="">(choose one)</option>
<option value="0">1</option>
<option value="1">2</option>
<option value="2">3</option>
<option value="3">4</option>
<option value="4">5</option>
</select>
</div>
If you need to set the default value in a password field to blank, use 'value' => '' instead.
7.3.3.16 $options[‘timeFormat’]
• Edit
• View just this section
• Comments (0)
• History
Used to specify the format of the select inputs for a time-related set of inputs. Valid values include ‘12’,
‘24’, and ‘none’.
7.3.3.17 $options[‘dateFormat’]
• Edit
• View just this section
• Comments (0)
• History
Used to specify the format of the select inputs for a date-related set of inputs. Valid values include ‘DMY’,
‘MDY’, ‘YMD’, and ‘NONE’.
7.3.3.19 $options['interval']
• Edit
• View just this section
• Comments (0)
• History
This option specifies the number of minutes between each option in the minutes select box.
Code View
<?php echo $form->input('Model.time', array('type' => 'time', 'interval' => 15)); ?>
Would create 4 options in the minute select. One for each 15 minutes.
7.3.3.20 $options['class']
• Edit
• View just this section
• Comments (0)
• History
You can set the classname for an input field using $options['class']
Code View
echo $form->input('title', array('class' => 'custom-class'));
« Closing the Form | File Fields »
// or
echo $form->file('Document.submittedfile');
Due to the limitations of HTML itself, it is not possible to put default values into input fields of type 'file'.
Each time the form is displayed, the value inside will be empty.
Upon submission, file fields provide an expanded data array to the script receiving the form data.
For the example above, the values in the submitted data array would be organized as follows, if the
CakePHP was installed on a Windows server. 'tmp_name' will have a different path in a Unix environment.
Code View
$this->data['Document']['submittedfile'] = array(
'name' => conference_schedule.pdf
'type' => application/pdf
'tmp_name' => C:/WINDOWS/TEMP/php1EE.tmp
'error' => 0
'size' => 41737
);
This array is generated by PHP itself, so for more detail on the way PHP handles data passed via file fields
read the PHP manual section on file uploads.
function isUploadedFile($params){
$val = array_shift($params);
if ((isset($val['error']) && $val['error'] == 0) ||
(!empty( $val['tmp_name']) && $val['tmp_name'] != 'none')) {
return is_uploaded_file($val['tmp_name']);
}
return false;
}
7.3.5.1 checkbox
• Edit
• View just this section
• Comments (0)
• History
checkbox(string $fieldName, array $options)
Creates a checkbox form element. This method also generates an associated hidden form input to force
the submission of data for the specified field.
Code View
<?php echo $form->checkbox('done'); ?>
Will output:
7.3.5.2 button
• Edit
• View just this section
• Comments (0)
• History
button(string $title, array $options = array())
Creates an HTML button with the specified title and a default type of "button". Setting $options['type'] will
output one of the three possible button types:
1. button: Creates a standard push button (the default).
2. reset: Creates a form reset button.
7.3.5.3 year
• Edit
• View just this section
• Comments (0)
• History
year(string $fieldName, int $minYear, int $maxYear, mixed $selected, array
$attributes, boolean $showEmpty)
Creates a select element populated with the years from $minYear to $maxYear, with the $selected year
selected by default. HTML attributes may be supplied in $attributes. If $showEmpty is false, the select will
not include an empty option.
Code View
<?php
echo $form->year('purchased',2000,date('Y'));
?>
Will output:
<option value="2002">2002</option>
<option value="2001">2001</option>
<option value="2000">2000</option>
</select>
7.3.5.4 month
• Edit
• View just this section
• Comments (0)
• History
month(string $fieldName, mixed $selected, array $attributes, boolean
$showEmpty)
Creates a select element populated with month names.
Code View
<?php
echo $form->month('mob');
?>
Will output:
7.3.5.5 dateTime
• Edit
• View just this section
• Comments (0)
• History
dateTime($fieldName, $dateFormat = 'DMY', $timeFormat = '12', $selected =
null, $attributes = array())
Creates a set of select inputs for date and time. Valid values for $dateformat are ‘DMY’, ‘MDY’, ‘YMD’ or
‘NONE’. Valid values for $timeFormat are ‘12’, ‘24’, and ‘NONE’.
You can specify not to display empty values by setting "array('empty' => false)" in the attributes
parameter. You also can pre-select the current datetime by setting $selected = null and $attributes =
array("empty" => false).
7.3.5.6 day
• Edit
• View just this section
• Comments (0)
• History
day(string $fieldName, mixed $selected, array $attributes, boolean
$showEmpty)
Creates a select element populated with the (numerical) days of the month.
To create an empty option with prompt text of your choosing (e.g. the first option is 'Day'), you can supply
the text as the final parameter as follows:
Code View
<?php
echo $form->day('created');
?>
Will output:
7.3.5.7 hour
• Edit
• View just this section
• Comments (0)
• History
hour(string $fieldName, boolean $format24Hours, mixed $selected, array
$attributes, boolean $showEmpty)
Creates a select element populated with the hours of the day.
7.3.5.8 minute
• Edit
• View just this section
• Comments (0)
• History
minute(string $fieldName, mixed $selected, array $attributes, boolean
$showEmpty)
Creates a select element populated with the minutes of the hour.
7.3.5.9 meridian
• Edit
• View just this section
• Comments (0)
• History
meridian(string $fieldName, mixed $selected, array $attributes, boolean
$showEmpty)
Creates a select element populated with ‘am’ and ‘pm’.
7.3.5.10 error
• Edit
• View just this section
• Comments (0)
• History
error(string $fieldName, mixed $text, array $options)
Shows a validation error message, specified by $text, for the given field, in the event that a validation
error has occurred.
Options:
• 'escape' bool Whether or not to html escape the contents of the error.
• 'wrap' mixed Whether or not the error message should be wrapped in a div. If a string, will be used
as the HTML tag to use.
• 'class' string The classname for the error message
7.3.5.11 file
• Edit
• View just this section
• Comments (0)
• History
file(string $fieldName, array $options)
Creates a file input.
Code View
<?php
echo $form->create('User',array('type'=>'file'));
echo $form->file('avatar');
?>
Will output:
7.3.5.13 isFieldError
• Edit
• View just this section
• Comments (0)
• History
isFieldError(string $fieldName)
Returns true if the supplied $fieldName has an active validation error.
Code View
<?php
if ($form->isFieldError('gender')){
echo $form->error('gender');
}
?>
When using $form->input(), errors are rendered by default.
7.3.5.14 label
• Edit
• View just this section
• Comments (0)
• History
label(string $fieldName, string $text, array $attributes)
Creates a label tag, populated with $text.
Code View
<?php
echo $form->label('status');
?>
Will output:
<label for="UserStatus">Status</label>
7.3.5.15 password
• Edit
• View just this section
• Comments (0)
• History
password(string $fieldName, array $options)
Creates a password field.
Code View
<?php
echo $form->password('password');
?>
Will output:
7.3.5.16 radio
• Edit
• View just this section
• Comments (0)
• History
radio(string $fieldName, array $options, array $attributes)
Creates a radio button input. Use $attributes['value'] to set which value should be selected default.
Use $attributes['separator'] to specify HTML in between radio buttons (e.g. <br />).
Radio elements are wrapped with a label and fieldset by default. Set $attributes['legend'] to false to
remove them.
Code View
<?php
$options=array('M'=>'Male','F'=>'Female');
$attributes=array('legend'=>false);
echo $form->radio('gender',$options,$attributes);
?>
Will output:
7.3.5.17 select
• Edit
• View just this section
• Comments (2)
• History
select(string $fieldName, array $options, mixed $selected, array $attributes)
Creates a select element, populated with the items in $options, with the option specified by $selected
shown as selected by default. If you wish to display your own default option, add your string value to the
'empty' key in the $attributes variable, or set it to false to turn off the default empty option
Code View
<?php
$options=array('M'=>'Male','F'=>'Female');
echo $form->select('gender',$options)
?>
Will output:
7.3.5.18 submit
• Edit
• View just this section
• Comments (0)
• History
submit(string $caption, array $options)
Creates a submit button with caption $caption. If the supplied $caption is a URL to an image (it contains a
‘.’ character), the submit button will be rendered as an image.
It is enclosed between div tags by default; you can avoid this by declaring $options['div'] = false.
Code View
<?php
echo $form->submit();
?>
Will output:
7.3.5.19 text
• Edit
• View just this section
• Comments (0)
• History
text(string $fieldName, array $options)
Creates a text input field.
Code View
<?php
echo $form->text('first_name');
?>
Will output:
7.3.5.20 textarea
• Edit
• View just this section
• Comments (0)
• History
textarea(string $fieldName, array $options)
Creates a textarea input field.
Code View
<?php
echo $form->textarea('notes');
?>
Will output:
Model introspection
Support for adding 'required' classes, and properties like maxlength to hasMany and other associations
has been improved. In the past only 1 model and a limited set of associations would be introspected. In
1.3 models are introspected as needed, providing validation and additional information such as
maxlength.
Default options for input()
In the past if you needed to use 'div' => false, or 'label' => false you would need to set those options on
each and every call to input(). Instead in 1.3 you can declare a set of default options for input() with the
inputDefaults key.
Code View
echo $this->Form->create('User', array(
'inputDefaults' => array(
'label' => false,
'div' => false
)
));
All inputs created from that point forward would inherit the options declared in inputDefaults. You can
override the defaultOptions by declaring the option in the input() call.
Code View
echo $this->Form->input('password'); // No div, no label
echo $this->Form->input('username', array('label' => 'Username')); // has a label element
Omit attributes
You can now set any attribute key to null or false in an options/attributes array to omit that attribute from
a particular html tag.
Code View
echo $this->Form->input('username', array(
'div' => array('class' => false)
)); // Omits the 'class' attribute added by default to div tag
Accept-charset
Forms now get an accept-charset set automatically, it will match the value of App.encoding, it can be
overridden or removed using the 'encoding' option when calling create().
Code View
// To remove the accept-charset attribute.
echo $this->Form->create('User', array('encoding' => null));
Removed parameters
Many methods such as select, year, month, day, hour, minute, meridian and datetime took a $showEmpty
parameter, these have all been removed and rolled into the $attributes parameter using the 'empty' key.
Default url
The default url for forms either was add or edit depending on whether or not a primary key was detected
in the data array. In 1.3 the default url will be the current action, making the forms submit to the action
you are currently on.
Disabling hidden inputs for radio and checkbox
The automatically generated hidden inputs for radio and checkbox inputs can be disabled by setting the
'hiddenField' option to false.
button()
button() now creates button elements, these elements by default do not have html entity encoding
enabled. You can enable html escaping using the escape option. The former features of
FormHelper::button have been moved to FormHelper::submit.
submit()
Due to changes in button(), submit() can now generate reset, and other types of input buttons. Use the
type option to change the default type of button generated. In addition to creating all types of buttons,
submit() has before and after options that behave exactly like their counterparts in input().
$options['format']
The HTML generated by the form helper is now more flexible than ever before. The $options parameter to
Form::input() now supports an array of strings describing the template you would like said element to
follow. It's just been recently added to SCM, and has a few bugs for non PHP 5.3 users, but should be quite
useful for all. The supported array keys are array('before', 'input', 'between', 'label', 'after', 'error').
7.4 HTML
• Edit
• Comments (0)
• History
The role of the HtmlHelper in CakePHP is to make HTML-related options easier, faster, and more resilient
to change. Using this helper will enable your application to be more light on its feet, and more flexible on
where it is placed in relation to the root of a domain.
Before we look at HtmlHelper's methods, you'll need to know about a few configuration and usage
situations that will help you use this class. First in an effort to assuage those who dislike short tags (<?= ?
>) or many echo() calls in their view code all methods of HtmlHelper are passed to the output() method. If
you wish to enable automatic output of the generated helper HTML you can simply implement output() in
your AppHelper.
Code View
function output($str) {
echo $str;
}
Doing this will remove the need to add echo statements to your view code.
Many HtmlHelper methods also include a $htmlAttributes parameter, that allow you to tack on any extra
attributes on your tags. Here are a few examples of how to use the $htmlAttributes parameter:
Code View
Desired attributes: <tag class="someClass" />
Array parameter: array('class'=>'someClass')
7.4.1.1 charset
• Edit
• View just this section
• Comments (0)
• History
charset(string $charset=null)
Used to create a meta tag specifying the document's character. Defaults to UTF-8.
Code View
7.4.1.2 css
• Edit
• View just this section
• Comments (0)
• History
css(mixed $path, string $rel = null, array $options = array())
Creates a link(s) to a CSS style-sheet. If key 'inline' is set to false in $options parameter, the link tags are
added to the $scripts_for_layout variable which you can print inside the head tag of the document.
This method of CSS inclusion assumes that the CSS file specified resides inside the /app/webroot/css
directory.
Code View
<?php echo $html->css('forms'); ?>
Will output:
7.4.1.3 meta
• Edit
• View just this section
• Comments (0)
• History
meta(string $type, string $url = null, array $attributes = array(), boolean
$inline = true)
This method is handy for linking to external resources like RSS/Atom feeds and favicons. Like css(), you
can specify whether or not you'd like this tag to appear inline or in the head tag using the fourth
parameter.
If you set the "type" attribute using the $htmlAttributes parameter, CakePHP contains a few shortcuts:
7.4.1.4 docType
• Edit
• View just this section
• Comments (0)
• History
docType(string $type = 'xhtml-strict')
Returns a (X)HTML doctype tag. Supply the doctype according to the following table:
7.4.1.5 style
• Edit
• View just this section
• Comments (0)
• History
style(array $data, boolean $oneline = true)
Builds CSS style definitions based on the keys and values of the array passed to the method. Especially
handy if your CSS file is dynamic.
Code View
<?php echo $html->style(array(
'background' => '#633',
'border-bottom' => '1px solid #000',
'padding' => '10px'
)); ?>
Will output:
7.4.1.6 image
• Edit
• View just this section
• Comments (1)
• History
image(string $path, array $htmlAttributes = array())
Creates a formatted image tag. The path supplied should be relative to /app/webroot/img/.
Code View
<?php echo $html->image('cake_logo.png', array('alt' => 'CakePHP'))?>
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
7.4.1.7 link
• Edit
• View just this section
• Comments (0)
• History
link(string $title, mixed $url = null, array $options = array(), string
$confirmMessage = false)
General purpose method for creating HTML links. Use $options to specify attributes for the element and
whether or not the $title should be escaped.
Code View
<?php echo $html->link('Enter', '/pages/home', array('class'=>'button','target'=>'_blank')); ?>
Will output:
<a href="/recipes/delete/6" onclick="return confirm('Are you sure you wish to delete this
recipe?');">Delete</a>
Query strings can also be created with link().
Code View
<?php echo $html->link('View image', array(
'controller' => 'images',
'action' => 'view',
1,
'?' => array( 'height' => 400, 'width' => 500))
);
Will output:
?>
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
Also check HtmlHelper::url method for more examples of different types of urls.
7.4.1.8 tag
• Edit
• View just this section
• Comments (0)
• History
tag(string $tag, string $text, array $htmlAttributes)
Returns text wrapped in a specified tag. If no text is specified then only the opening <tag> is returned.
Code View
<?php echo $html->tag('span', 'Hello World.', array('class' => 'welcome'));?>
//Output
<span class="welcome">Hello World</span>
//Output
<span class="welcome">
7.4.1.9 div
• Edit
• View just this section
• Comments (0)
• History
div(string $class, string $text, array $htmlAttributes, boolean $escape =
false)
Used for creating div-wrapped sections of markup. The first parameter specifies a CSS class, and the
second is used to supply the text to be wrapped by div tags. If the last parameter has been set to true,
$text will be printed HTML-escaped.
//Output
<div class="error">Please enter your credit card number.</div>
7.4.1.10 para
• Edit
• View just this section
• Comments (0)
• History
para(string $class, string $text, array $htmlAttributes, boolean $escape =
false)
Returns a text wrapped in a CSS-classed <p> tag. If no text is supplied, only a starting <p> tag is
returned.
Code View
<?php echo $html->para(null, 'Hello World.');?>
//Output
<p>Hello World.</p>
7.4.1.11 script
• Edit
• View just this section
• Comments (0)
• History
script(mixed $url, mixed $options)
Creates link(s) to a javascript file. If key inline is set to false in $options, the link tags are added to the
$scripts_for_layout variable which you can print inside the head tag of the document.
Include a script file into the page. $options['inline'] controls whether or not a script should be returned
inline or added to $scripts_for_layout. $options['once']controls, whether or not you want to include this
script once per request or more than once.
You can also use $options to set additional properties to the generated script tag. If an array of script tags
is used, the attributes will be applied to all of the generated script tags.
This method of javascript file inclusion assumes that the javascript file specified resides inside the
/app/webroot/js directory.
Code View
<?php echo $html->script('scripts'); ?>
Will output:
7.4.1.12 scriptBlock
• Edit
• View just this section
• Comments (0)
• History
scriptBlock($code, $options = array())
Generate a code block containing $code set $options['inline'] to false to have the script block appear in
$scripts_for_layout. Also new is the ability to add attributes to script tags. $html->scriptBlock('stuff',
array('defer' => true)); will create a script tag with defer="defer" attribute.
7.4.1.13 scriptStart
• Edit
• View just this section
• Comments (0)
• History
scriptStart($options = array())
Begin a buffering code block. This code block will capture all output between scriptStart() and scriptEnd()
and create an script tag. Options are the same asscriptBlock()
7.4.1.14 scriptEnd
• Edit
• View just this section
• Comments (0)
• History
scriptEnd()
End a buffering script block, returns the generated script element or null if the script block was opened
with inline = false.
An example of using scriptStart() and scriptEnd() would be:
Code View
$html->scriptStart(array('inline' => false));
$html->scriptEnd();
7.4.1.15 tableHeaders
• Edit
• View just this section
• Comments (0)
• History
tableHeaders(array $names, array $trOptions = null, array $thOptions = null)
Creates a row of table header cells to be placed inside of <table> tags.
Code View
<?php echo $html->tableHeaders(array('Date','Title','Active'));?>
//Output
<tr>
<th>Date</th>
<th>Title</th>
<th>Active</th>
</tr>
//Output
<tr class="status">
<th class="product_table">Date</th>
<th class="product_table">Title</th>
<th class="product_table">Active</th>
</tr>
7.4.1.16 tableCells
• Edit
• View just this section
• Comments (0)
• History
tableCells(array $data, array $oddTrOptions = null, array $evenTrOptions =
null, $useCount = false, $continueOddEven = true)
Creates table cells, in rows, assigning <tr> attributes differently for odd- and even-numbered rows. Wrap
a single table cell within an array() for specific <td>-attributes.
Code View
<?php echo $html->tableCells(array(
array('Jul 7th, 2007', 'Best Brownies', 'Yes'),
array('Jun 21st, 2007', 'Smart Cookies', 'Yes'),
array('Aug 1st, 2006', 'Anti-Java Cake', 'No'),
));
?>
//Output
<tr><td>Jul 7th, 2007</td><td>Best Brownies</td><td>Yes</td></tr>
<tr><td>Jun 21st, 2007</td><td>Smart Cookies</td><td>Yes</td></tr>
<tr><td>Aug 1st, 2006</td><td>Anti-Java Cake</td><td>No</td></tr>
<?php echo $html->tableCells(array(
array('Jul 7th, 2007', array('Best Brownies', array('class'=>'highlight')) , 'Yes'),
array('Jun 21st, 2007', 'Smart Cookies', 'Yes'),
array('Aug 1st, 2006', 'Anti-Java Cake', array('No', array('id'=>'special'))),
));
?>
//Output
<tr><td>Jul 7th, 2007</td><td class="highlight">Best Brownies</td><td>Yes</td></tr>
<tr><td>Jun 21st, 2007</td><td>Smart Cookies</td><td>Yes</td></tr>
<tr><td>Aug 1st, 2006</td><td>Anti-Java Cake</td><td id="special">No</td></tr>
//Output
<tr class="darker"><td>Red</td><td>Apple</td></tr>
<tr><td>Orange</td><td>Orange</td></tr>
<tr class="darker"><td>Yellow</td><td>Banana</td></tr>
7.4.1.17 url
• Edit
• View just this section
• Comments (1)
• History
url(mixed $url = NULL, boolean $full = false)
Returns an URL pointing to a combination of controller and action. If $url is empty, it returns the
REQUEST_URI, otherwise it generates the url for the controller and action combo. If full is true, the full
base URL will be prepended to the result.
Code View
<?php echo $html->url(array(
"controller" => "posts",
"action" => "view",
"bar"));?>
// Output
/posts/view/bar
Here are a few more usage examples:
// Output
/posts/view/foo:bar
// Output
/posts/list.rss
URL (starting with '/') with the full base URL prepended.
Code View
<?php echo $html->url('/posts', true); ?>
//Output
http://somedomain.com/posts
//Output
/posts/search?foo=bar#first
Javascript Engines form the backbone of the new JsHelper. A Javascript engine translates an abstract
Javascript element into concrete Javascript code specific to the Javascript library being used. In addition
they create an extensible system for others to use.
# Common options
• Edit
• View just this section
• Comments (0)
• History
In attempts to simplify development where Js libraries can change, a common set of options is supported
by JsHelper, these common options will be mapped out to the library specific options internally. If you are
not planning on switching Javascript libraries, each library also supports all of its native callbacks and
options.
# Callback wrapping
• Edit
• View just this section
• Comments (0)
• History
By default all callback options are wrapped with the an anonymous function with the correct arguments.
You can disable this behavior by supplying the wrapCallbacks = false in your options array.
Options
• inline - Set to true to have scripts output as a script block inline if cache is also true, a script link
tag will be generated. (default true)
• cache - Set to true to have scripts cached to a file and linked in (default false)
• clear - Set to false to prevent script cache from being cleared (default true)
• onDomReady - wrap cached scripts in domready event (default true)
• safe - if an inline block is generated should it be wrapped in <![CDATA[ ... ]]> (default true)
Creating a cache file with writeBuffer() requires that webroot/js be world writable and allows a browser to
cache generated script resources for any page.
buffer($content)
Add $content to the internal script buffer.
getBuffer($clear = true)
Get the contents of the current buffer. Pass in false to not clear the buffer at the same time.
7.5.4 Methods
• Edit
• Comments (1)
• History
The core Javascript Engines provide the same feature set across all libraries, there is also a subset of
common options that are translated into library specific options. This is done to provide end developers
with as unified an API as possible. The following list of methods are supported by all the Engines included
in the CakePHP core. Whenever you see separate lists for Options and Event Options both sets of
parameters are supplied in the $options array for the method.
object($data, $options = array())
Converts values into JSON. There are a few differences between this method and
JavascriptHelper::object(). Most notably there is no affordance for stringKeys or qoptions found in the
JavascriptHelper. Furthermore $js->object(); cannot make script tags.
Options:
• prefix - String prepended to the returned data.
• postfix - String appended to the returned data.
Example Use:
Code View
$json = $js->object($data);
sortable($options = array())
Sortable generates a javascript snippet to make a set of elements (usually a list) drag and drop sortable.
Options
• containment - Container for move action
• handle - Selector to handle element. Only this element will start sort action.
• revert - Whether or not to use an effect to move sortable into final position.
• opacity - Opacity of the placeholder
• distance - Distance a sortable must be dragged before sorting starts.
Event Options
• start - Event fired when sorting starts
• sort - Event fired during sorting
• complete - Event fired when sorting completes.
Other options are supported by each Javascript library, and you should check the documentation for your
javascript library for more detailed information on its options and parameters.
Example use:
Code View
$js->get('#my-list');
$js->sortable(array(
'distance' => 5,
'containment' => 'parent',
'start' => 'onStart',
'complete' => 'onStop',
'sort' => 'onSort',
'wrapCallbacks' => false
));
Assuming you were using the jQuery engine, you would get the following code in your generated
Javascript block:
Code View
$("#myList").sortable({containment:"parent", distance:5, sort:onSort, start:onStart, stop:onStop});
request($url, $options = array())
Generate a javascript snippet to create an XmlHttpRequest or 'AJAX' request.
Event Options
• complete - Callback to fire on complete.
• success - Callback to fire on success.
• before - Callback to fire on request initialization.
• error - Callback to fire on request failure.
Options
• method - The method to make the request with defaults to GET in more libraries
• async - Whether or not you want an asynchronous request.
• data - Additional data to send.
• update - Dom id to update with the content of the request.
• type - Data type for response. 'json' and 'html' are supported. Default is html for most libraries.
• evalScripts - Whether or not <script> tags should be eval'ed.
• dataExpression - Should the data key be treated as a callback. Useful for supplying
$options['data'] as another Javascript expression.
Example use
get($selector)
Set the internal 'selection' to a CSS selector. The active selection is used in subsequent operations until a
new selection is made.
Code View
$js->get('#element');
The JsHelper now will reference all other element based methods on the selection of #element. To change
the active selection, call get() again with a new element.
drag($options = array())
Make an element draggable.
Options
• handle - selector to the handle element.
• snapGrid - The pixel grid that movement snaps to, an array(x, y)
• container - The element that acts as a bounding box for the draggable element.
Event Options
• start - Event fired when the drag starts
• drag - Event fired on every step of the drag
• stop - Event fired when dragging stops (mouse release)
Example use
Code View
$js->get('#element');
$js->drag(array(
'container' => '#content',
'start' => 'onStart',
'drag' => 'onDrag',
'stop' => 'onStop',
'snapGrid' => array(10, 10),
'wrapCallbacks' => false
));
If you were using the jQuery engine the following code would be added to the buffer.
Code View
$("#element").draggable({containment:"#content", drag:onDrag, grid:[10,10], start:onStart,
stop:onStop});
drop($options = array())
Make an element accept draggable elements and act as a dropzone for dragged elements.
Options
• accept - Selector for elements this droppable will accept.
• hoverclass - Class to add to droppable when a draggable is over.
Event Options
• drop - Event fired when an element is dropped into the drop zone.
• hover - Event fired when a drag enters a drop zone.
• leave - Event fired when a drag is removed from a drop zone without being dropped.
Example use
Code View
$js->get('#element');
$js->drop(array(
'accept' => '.items',
'hover' => 'onHover',
'leave' => 'onExit',
'drop' => 'onDrop',
'wrapCallbacks' => false
));
If you were using the jQuery engine the following code would be added to the buffer:
Code View
<code class=
"php">$("#element").droppable({accept:".items", drop:onDrop, out:onExit, over:onHover});</code>
''Note'' about MootoolsEngine::drop
Droppables in Mootools function differently from other libraries. Droppables are implemented as an
extension of Drag. So in addtion to making a get() selection for the droppable element. You must also
provide a selector rule to the draggable element. Furthermore, Mootools droppables inherit all options
from Drag.
slider()
Create snippet of Javascript that converts an element into a slider ui widget. See your libraries
implementation for additional usage and features.
Options
• handle - The id of the element used in sliding.
• direction - The direction of the slider either 'vertical' or 'horizontal'
• min - The min value for the slider.
• max - The max value for the slider.
• step - The number of steps or ticks the slider will have.
• value - The initial offset of the slider.
Events
• change - Fired when the slider's value is updated
• complete - Fired when the user stops sliding the handle
Example use
Code View
$js->get('#element');
$js->slider(array(
'complete' => 'onComplete',
'change' => 'onChange',
'min' => 0,
'max' => 10,
'value' => 2,
'direction' => 'vertical',
'wrapCallbacks' => false
));
If you were using the jQuery engine the following code would be added to the buffer:
Code View
$("#element").slider({change:onChange, max:10, min:0, orientation:"vertical", stop:onComplete,
value:2});
effect($name, $options = array())
Creates a basic effect. By default this method is not buffered and returns its result.
7.6 Javascript
• Edit
• Comments (0)
• History
The Javascript helper is used to aid in creating well formatted related javascript tags and codeblocks.
There are several methods some of which are designed to work with the Prototype Javascript library.
The Javascript Helper is deprecated in 1.3 and will be removed in future versions of CakePHP. See the new
JsHelper and HtmlHelper, as well as the migration guide for where JavascriptHelper's methods have
moved to
« Ajax Pagination | Methods »
7.6.1 Methods
• Edit
• Comments (0)
• History
codeBlock($script, $options =
array('allowCache'=>true,'safe'=>true,'inline'=>true), $safe)
• string $script - The JavaScript to be wrapped in SCRIPT tags
• array $options - Set of options:
• allowCache: boolean, designates whether this block is cacheable using the current cache
settings.
• safe: boolean, whether this block should be wrapped in CDATA tags. Defaults to helper's
object configuration.
• inline: whether the block should be printed inline, or written to cached for later output (i.e.
$scripts_for_layout).
• boolean $safe - DEPRECATED. Use $options['safe'] instead
codeBlock returns a formatted script element containing $script. But can also return null if Javascript
helper is set to cache events. See JavascriptHelper::cacheEvents(). And can write in $scripts_for_layout if
you set $options['inline'] to false.
blockEnd()
Ends a block of cached Javascript. Can return either a end script tag, or empties the buffer, adding the
contents to the cachedEvents array. Its return value depends on the cache settings. See
JavascriptHelper::cacheEvents()
• boolean $inline If true, the <script> tag will be printed inline, otherwise it will be printed in
$scripts_for_layout
Creates a javascript link to a single or many javascript files. Can output inline or in $scripts_for_layout.
If the filename is prefixed with "/", the path will be relative to the base path of your application.
Otherwise, the path will be relative to your JavaScript path, usually webroot/js.
escapeString($string)
• string $script - String that needs to get escaped.
Attach a javascript event handler specified by $event to an element DOM element specified by $object.
Object does not have to be an ID reference it can refer to any valid javascript object or CSS selectors. If a
CSS selector is used the event handler is cached and should be retrieved with
JavascriptHelper::getCache(). This method requires the Prototype library.
cacheEvents($file, $all)
• boolean $file - If true, code will be written to a file
• boolean $all - If true, all code written with JavascriptHelper will be sent to a file
Allows you to control how the JavaScript Helper caches event code generated by event(). If $all is set to
true, all code generated by the helper is cached and can be retrieved with getCache() or written to file or
page output with writeCache().
getCache($clear)
• boolean $clear - If set to true the cached javascript is cleared. Defaults to true.
writeEvents($inline)
• boolean $inline - If true, returns JavaScript event code. Otherwise it is added to the output of
$scripts_for_layout in the layout.
Returns cached javascript code. If $file was set to true with cacheEvents(), code is cached to a file and a
script link to the cached events file is returned. If inline is true, the event code is returned inline. Else it is
added to the $scripts_for_layout for the page.
includeScript($script)
• string $script - File name of script to include.
Includes the named $script. If $script is left blank the helper will include every script in your
app/webroot/js directory. Includes the contents of each file inline. To create a script tag with an src
attribute use link().
object($data, $options)
• array $data - Data to be converted
• array $options - Set of options: block, prefix, postfix, stringKeys, quoteKeys, q
• boolean $options['block'] - Wraps return value in a <script /> block if true. Defaults to
false.
• string $options['prefix'] - Prepends the string to the returned data.
• string $options['postfix'] - Appends the string to the returned data.
• array $options['stringKeys'] - A list of array keys to be treated as a string.
• boolean $options['quoteKeys'] - If false, treats $stringKey as a list of keys *not* to be
quoted. Defaults to true.
• string $options['q'] - The type of quote to use.
Generates a JavaScript object in JavaScript Object Notation (JSON) from $data array.
7.7 Number
• Edit
• Comments (0)
• History
The NumberHelper contains convenience methods that enable display numbers in common formats in
your views. These methods include ways to format currency, percentages, data sizes, format numbers to
specific precisions and also to give you more flexibility with formating numbers.
All of these functions return the formated number; They do not automatically echo the output into the
view.
7.7.1 currency
• Edit
• Comments (0)
• History
currency(mixed $number, string $currency= 'USD', $options = array())
This method is used to display a number in common currency formats (EUR,GBP,USD). Usage in a view
looks like:
Code View
<?php echo $number->currency($number,$currency); ?>
The first parameter, $number, should be a floating point number that represents the amount of money
you are expressing. The second parameter is used to choose a predefined currency formatting scheme:
Option Description
before The currency symbol to place before whole numbers ie. '$'
The currency symbol to place after decimal numbers ie. 'c'. Set to boolean false to use
after
no decimal symbol. eg. 0.35 => $0.35.
zero The text to use for zero values, can be a string or a number. ie. 0, 'Free!'
places Number of decimal places to use. ie. 2
thousands Thousands separator ie. ','
decimals Decimal separator symbol ie. '.'
negative Symbol for negative numbers. If equal to '()', the number will be wrapped with ( and )
escape Should the output be htmlentity escaped? Defaults to true
If a non-recognized $currency value is supplied, it is prepended to a USD formatted number. For example:
Code View
<?php echo $number->currency('1234.56', 'FOO'); ?>
//Outputs:
FOO 1,234.56
7.7.2 precision
• Edit
• Comments (0)
• History
precision (mixed $number, int $precision = 3)
This method displays a number with the specified amount of precision (decimal places). It will round in
order to maintain the level of precision defined.
Code View
<?php echo $number->precision(456.91873645, 2 ); ?>
//Outputs:
456.92
7.7.3 toPercentage
• Edit
• Comments (0)
• History
toPercentage(mixed $number, int $precision = 2)
Like precision(), this method formats a number according to the supplied precision (where numbers are
rounded to meet the given precision). This method also expresses the number as a percentage and
prepends the output with a percent sign.
Code View
<?php echo $number->toPercentage(45.691873645); ?>
//Outputs:
45.69%
7.7.4 toReadableSize
• Edit
• Comments (0)
• History
toReadableSize(string $data_size)
This method formats data sizes in human readable forms. It provides a shortcut way to convert bytes to
KB, MB, GB, and TB. The size is displayed with a two-digit precision level, according to the size of data
supplied (i.e. higher sizes are expressed in larger terms):
Code View
echo $number->toReadableSize(0); // 0 Bytes
echo $number->toReadableSize(1024); // 1 KB
echo $number->toReadableSize(1321205.76); // 1.26 MB
echo $number->toReadableSize(5368709120); // 5.00 GB
« toPercentage | format »
7.7.5 format
• Edit
• Comments (0)
• History
format (mixed $number, mixed $options=false)
This method gives you much more control over the formatting of numbers for use in your views (and is
used as the main method by most of the other NumberHelper methods). Using this method might looks
like:
Code View
$number->format($number, $options);
The $number parameter is the number that you are planning on formatting for output. With no $options
supplied, the number 1236.334 would output as 1,236. Note that the default precision is zero decimal
places.
The $options parameter is where the real magic for this method resides.
• If you pass an integer then this becomes the amount of precision or places for the function.
• If you pass an associated array, you can use the following keys:
• places (integer): the amount of desired precision
• before (string): to be put before the outputted number
• escape (boolean): if you want the value in before to be escaped
• decimals (string): used to delimit the decimal places in a number
• thousands (string): used to mark off thousand, millions, … places
Code View
echo $number->format('123456.7890', array(
'places' => 2,
'before' => '¥ ',
'escape' => false,
'decimals' => '.',
'thousands' => ','
));
// output '¥ 123,456.79'
7.8 Paginator
• Edit
• Comments (0)
• History
The Pagination helper is used to output pagination controls such as page numbers and next/previous
links.
See also Common Tasks With CakePHP - Pagination for additional information.
7.8.1 Methods
• Edit
• Comments (0)
• History
options($options = array())
• mixed options() Default options for pagination links. If a string is supplied - it is used as the DOM
id element to update. See #options for list of keys.
options() sets all the options for the Paginator Helper. Supported options are:
format
Format of the counter. Supported formats are 'range' and 'pages' and custom which is the default. In the
default mode the supplied string is parsed and tokens are replaced with actual values. The available
tokens are:
Now that you know the available tokens you can use the counter() method to display all sorts of
information about the returned results, for example:
Code View
echo $paginator->counter(array(
'format' => 'Page %page% of %pages%,
showing %current% records out of %count% total,
starting on record %start%, ending on %end%'
));
separator
The separator between the actual page and the number of pages. Defaults to ' of '. This is used in
conjunction with format = 'pages'
url
The url of the paginating action. url has a few sub options as well
escape
Defines if the title field for links should be HTML escaped. Defaults to true.
update
The DOM id of the element to update with the results of AJAX pagination calls. If not specified, regular
links will be created.
indicator
DOM id of the element that will be shown as a loading or working indicator while doing AJAX requests.
7.9 RSS
• Edit
• Comments (0)
• History
The RSS helper makes generating XML for RSS feeds easy.
$posts = $this->paginate();
$this->set(compact('posts'));
}
}
With all the View variables set we need to create an rss layout.
7.9.1.1.1 Layout
• Edit
• View just this section
• Comments (0)
• History
An Rss layout is very simple, put the following contents in app/views/layouts/rss/default.ctp:
Code View
echo $rss->header();
if (!isset($documentData)) {
$documentData = array();
}
if (!isset($channelData)) {
$channelData = array();
}
if (!isset($channelData['title'])) {
$channelData['title'] = $title_for_layout;
}
$channel = $rss->channel(array(), $channelData, $content_for_layout);
echo $rss->document($documentData,$channel);
It doesn't look like much but thanks to the power in the RssHelper its doing a lot of lifting for us. We
haven't set $documentData or $channelData in the controller, however in CakePHP 1.3 your views can
pass variables back to the layout. Which is where our $channelData array will come from setting all of the
meta data for our feed.
Next up is view file for my posts/index. Much like the layout file we created, we need to create a
views/posts/rss/ directory and create a new index.ctp inside that folder. The contents of the file are below.
7.9.1.1.2 View
• Edit
• View just this section
• Comments (2)
• History
Our view, located at app/views/posts/rss/index.ctp, begins by setting the $documentData and
$channelData variables for the layout, these contain all the metadata for our RSS feed. This is done by
using the View::set() method which is analogous to the Controller::set() method. Here though we are
passing the channel's metadata back to the layout.
Code View
$this->set('documentData', array(
'xmlns:dc' => 'http://purl.org/dc/elements/1.1/'));
$this->set('channelData', array(
'title' => __("Most Recent Posts", true),
'link' => $html->url('/', true),
'description' => __("Most recent posts.", true),
'language' => 'en-us'));
The second part of the view generates the elements for the actual records of the feed. This is
accomplished by looping through the data that has been passed to the view ($items) and using the
RssHelper::item() method. The other method you can use, RssHelper::items() which takes a callback and
an array of items for the feed. (The method I have seen used for the callback has always been called
transformRss(). There is one downfall to this method, which is that you cannot use any of the other helper
classes to prepare your data inside the callback method because the scope inside the method does not
include anything that is not passed inside, thus not giving access to the TimeHelper or any other helper
that you may need. The RssHelper::item() transforms the associative array into an element for each key
value pair.
Code View
foreach ($posts as $post) {
$postTime = strtotime($post['Post']['created']);
$postLink = array(
'controller' => 'entries',
'action' => 'view',
'year' => date('Y', $postTime),
'month' => date('m', $postTime),
'day' => date('d', $postTime),
$post['Post']['slug']);
// You should import Sanitize
App::import('Sanitize');
// This is the part where we clean the body text for output as the description
// of the rss item, this needs to have only text to make sure the feed validates
$bodyText = preg_replace('=\(.*?\)=is', '', $post['Post']['body']);
$bodyText = $text->stripLinks($bodyText);
$bodyText = Sanitize::stripAll($bodyText);
$bodyText = $text->truncate($bodyText, 400, '...', true, true);
You may need to set the value of 'debug' in your core configuration to 1 or to 0 to get a valid feed,
because of the various debug information added automagically under higher debug settings that break
XML syntax or feed validation rules.
7.10 Session
• Edit
• Comments (2)
• History
As a natural counterpart to the Session Component, the Session Helper replicates most of the
components functionality and makes it available in your view. The Session Helper is no longer
automatically added to your view — so it is necessary to add it to the $helpers array in the controller.
The major difference between the Session Helper and the Session Component is that the helper does not
have the ability to write to the session.
As with the Session Component, data is written to and read by using dot separated array structures.
Code View
array('User' =>
array('username' => '[email protected]')
);
Given the previous array structure, the node would be accessed by User.username, with the dot indicating
the nested array. This notation is used for all Session helper methods wherever a $key is used.
If you have Session.start set to false in your config/core.php, you need to call $session->activate(); in
your view or layout before you can use any other method of Session helper. Just like you need to call
$this->Session->activate(); in your controller to activate Session component.
« Creating an RSS feed with the RssHelper | Methods »
7.10.1 Methods
• Edit
• Comments (0)
• History
read($key Read from the Session. Returns a string or array depending on the contents of the
) session.
id() Returns the current session ID.
check($ke
Check to see if a key is in the Session. Returns a boolean on the key's existence.
y)
flash($key This will return the contents of the $_SESSION.Message. It is used in conjunction with
) the Session Component's setFlash() method.
error() Returns the last error in the session if one exists.
7.10.2 flash
• Edit
• Comments (0)
• History
The flash method uses the default key set by setFlash(). You can also retrieve specific keys in the session.
For example, the Auth component sets all of its Session messages under the 'auth' key
Code View
// Controller code
$this->Session->setFlash('My Message');
// In view
echo $session->flash();
// outputs "<div id='flashMessage' class='message'>My Message</div>"
One way to achieve this is to use Session->flash() with the layout parameter. With the layout parameter
we can be in control of the resultant html for the message.
7.11 Text
• Edit
• Comments (0)
• History
The TextHelper contains methods to make text more usable and friendly in your views. It aids in enabling
links, formatting urls, creating excerpts of text around chosen words or phrases, highlighting key words in
blocks of text, and to gracefully truncating long stretches of text.
# autoLinkEmails
• Edit
• View just this section
• Comments (0)
• History
autoLinkEmails(string $text, array $htmlOptions=array())
Adds links to the well-formed email addresses in $text, according to any options defined in $htmlOptions
(see HtmlHelper::link()).
Code View
$my_text = 'For more information regarding our world-famous pastries and desserts, contact
[email protected]';
$linked_text = $text->autoLinkEmails($my_text);
Output:
# autoLinkUrls
• Edit
• View just this section
• Comments (0)
• History
autoLinkUrls(string $text, array $htmlOptions=array())
Same as in autoLinkEmails(), only this method searches for strings that start with https, http, ftp, or nntp
and links them appropriately.
# autoLink
• Edit
• View just this section
• Comments (0)
• History
autoLink(string $text, array $htmlOptions=array())
Performs the functionality in both autoLinkUrls() and autoLinkEmails() on the supplied $text. All URLs and
emails are linked appropriately given the supplied$htmlOptions.
# excerpt
• Edit
• View just this section
• Comments (0)
• History
excerpt(string $haystack, string $needle, int $radius=100, string
$ending="...")
Extracts an excerpt from $haystack surrounding the $needle with a number of characters on each side
determined by $radius, and suffixed with $ending. This method is especially handy for search results. The
query string or keywords can be shown within the resulting document.
Code View
echo $text->excerpt($last_paragraph, 'method', 50);
Output:
mined by $radius, and suffixed with $ending. This method is especially handy for
search results. The query...
# highlight
• Edit
• View just this section
• Comments (0)
• History
highlight(string $haystack, string $needle, $highlighter='<span
class="highlight">\1</span>')
Highlights $needle in $haystack using the $highlighter string specified.
Code View
echo $text->highlight($last_sentence, 'using');
Output:
# stripLinks
• Edit
• View just this section
• Comments (0)
• History
stripLinks($text)
Strips the supplied $text of any HTML links.
# toList
• Edit
• View just this section
• Comments (0)
• History
toList(array $list, $and='and')
Creates a comma-separated list where the last two items are joined with ‘and’.
Code View
echo $text->toList($colors);
Output:
# trim
• Edit
• View just this section
• Comments (0)
• History
trim()
An alias for truncate.
7.12 Time
• Edit
• Comments (0)
• History
The Time Helper does what it says on the tin: saves you time. It allows for the quick processing of time
related information. The Time Helper has two main tasks that it can perform:
7.13 XML
• Edit
• Comments (0)
• History
The XML Helper simplifies the output of XML documents.
7.13.1 serialize
• Edit
• Comments (0)
• History
serialize($data, $options = array())
• mixed $data - The content to be converted to XML
• mixed $options - The data formatting options. For a list of valid options, see Xml::__construct()
• string $options['root'] - The name of the root element, defaults to '#document'
• string $options['version'] - The XML version, defaults to '1.0'
• string $options['encoding'] - Document encoding, defaults to 'UTF-8'
• array $options['namespaces'] - An array of namespaces (as strings) used in this document
• string $options['format'] - Specifies the format this document converts to when parsed or
rendered out as text, either 'attributes' or 'tags', defaults to 'attributes'
• array $options['tags'] - An array specifying any tag-specific formatting options, indexed by
tag name. See XmlNode::normalize()
The serialize method takes an array and creates an XML string of the data. This is commonly used for
serializing model data.
Code View
<?php
echo $xml->serialize($data);
format will be similar to:
<model_name id="1" field_name="content" />
?>
The serialize method acts as a shortcut to instantiating the XML built-in class and using the toString
method of that. If you need more control over serialization, you may wish to invoke the XML class directly.
You can modify how a data is serialized by using the format attribute. By default the data will be serialized
as attributes. If you set the format as "tags" the data will be serialized as tags.
Code View
pr($data);
Array
(
[Baker] => Array
(
[0] => Array
(
[name] => The Baker
[weight] => heavy
)
[1] => Array
(
[name] => The Cook
[weight] => light-weight
)
)
)
Code View
pr($xml->serialize($data));
<baker>
<baker name="The Baker" weight="heavy" />
<baker name="The Cook" weight="light-weight" />
</baker>
Code View
pr($xml->serialize($data, array('format' => 'tags')));
<baker>
<baker>
<name><![CDATA[The Baker]]></name>
<weight><![CDATA[heavy]]></weight>
</baker>
<baker>
<name><![CDATA[The Cook]]></name>
<weight><![CDATA[light-weight]]></weight>
</baker>
</baker>
7.13.2 elem
• Edit
• Comments (0)
• History
The elem method allows you to build an XML node string with attributes and internal content, as well.
string elem (string $name, $attrib = array(), mixed $content = null, $endTag
= true)
Code View
echo $xml->elem('count', array('namespace' => 'myNameSpace'), 'content');
// generates: <myNameSpace:count>content</count>
If you want to wrap your text node with CDATA, the third argument should be an array containing two
keys: 'cdata' and 'value'
Code View
echo $xml->elem('count', null, array('cdata'=>true,'value'=>'content'));
// generates: <count><![CDATA[content]]></count>
« serialize | header »
7.13.3 header
• Edit
• Comments (0)
• History
The header() method is used to output the XML declaration.
Code View
<?php
echo $xml->header();
// generates: <?xml version="1.0" encoding="UTF-8" ?>
?>
You can pass in a different version number and encoding type as parameters of the header method.
Code View
<?php
echo $xml->header(array('version'=>'1.1'));
// generates: <?xml version="1.1" encoding="UTF-8" ?>
?>
8.2 String
• Edit
• Comments (0)
• History
The String class includes convenience methods for creating and manipulating strings and is normally
accessed statically. Example: String::uuid().
8.2.1 uuid
• Edit
• Comments (0)
• History
The uuid method is used to generate unique identifiers as per RFC 4122. The uuid is a 128bit string in the
format of 485fc381-e790-47a3-9794-1337c0a8fe68.
Code View
String::uuid(); // 485fc381-e790-47a3-9794-1337c
8.2.2 tokenize
• Edit
• Comments (0)
• History
string tokenize ($data, $separator = ',', $leftBound = '(', $rightBound = ')')
Tokenizes a string using $separator, ignoring any instance of $separator that appears between $leftBound
and $rightBound.
8.2.3 insert
• Edit
• Comments (0)
• History
string insert ($string, $data, $options = array())
The insert method is used to create string templates and to allow for key/value replacements.
Code View
String::insert('My name is :name and I am :age years old.', array('name' => 'Bob', 'age' => '65'));
// generates: "My name is Bob and I am 65 years old."
8.2.4 cleanInsert
• Edit
• Comments (0)
• History
string cleanInsert ($string, $options = array())
Cleans up a Set::insert formatted string with given $options depending on the 'clean' key in $options. The
default method used is text but html is also available. The goal of this function is to replace all whitespace
and unneeded markup around placeholders that did not get replaced by Set::insert.
echo $xml->children[0]->children[0]->children[0]->children[0]->value;
// outputs 'My Element'
echo $xml->children[0]->child('element')->attributes['id'];
//outputs 'first-el'
In addition to the above it often makes it easier to obtain data from XML if you convert the Xml document
object to a array.
Code View
$xml = new Xml($input);
// This converts the Xml document object to a formatted array
$xmlAsArray = Set::reverse($xml);
// Or you can convert simply by calling toArray();
$xmlAsArray = $xml->toArray();
8.4 Set
• Edit
• Comments (0)
• History
Array management, if done right, can be a very powerful and useful tool for building smarter, more
optimized code. CakePHP offers a very useful set of static utilities in the Set class that allow you to do just
that.
CakePHP's Set class can be called from any model or controller in the same way Inflector is called.
Example: Set::combine().
Expression Definition
{n} Represents a numeric key
{s} Represents a string
Foo Any string (without enclosing brackets) is treated like a string literal.
Any string enclosed in brackets (besides {n} and {s}) is interpreted as a regular
{[a-z]+}
expression.
This section needs to be expanded.
« Set | insert »
8.4.2 insert
• Edit
• Comments (0)
• History
array Set::insert ($list, $path, $data = null)
Inserts $data into an array as defined by $path.
Code View
$a = array(
'pages' => array('name' => 'page')
);
$result = Set::insert($a, 'files', array('name' => 'files'));
/* $result now looks like:
Array
(
[pages] => Array
(
[name] => page
)
[files] => Array
(
[name] => files
)
)
*/
$a = array(
'pages' => array('name' => 'page')
);
$result = Set::insert($a, 'pages.name', array());
/* $result now looks like:
Array
(
[pages] => Array
(
[name] => Array
(
)
)
)
*/
$a = array(
'pages' => array(
0 => array('name' => 'main'),
1 => array('name' => 'about')
)
);
$result = Set::insert($a, 'pages.1.vars', array('title' => 'page title'));
/* $result now looks like:
Array
(
[pages] => Array
(
[0] => Array
(
[name] => main
)
[1] => Array
(
[name] => about
[vars] => Array
(
[title] => page title
)
)
)
)
*/
8.4.3 sort
• Edit
• Comments (0)
• History
array Set::sort ($data, $path, $dir)
Sorts an array by any value, determined by a Set-compatible path.
Code View
$a = array(
0 => array('Person' => array('name' => 'Jeff')),
1 => array('Shirt' => array('color' => 'black'))
);
$result = Set::sort($a, '{n}.Person.name', 'asc');
/* $result now looks like:
Array
(
[0] => Array
(
[Shirt] => Array
(
[color] => black
)
)
[1] => Array
(
[Person] => Array
(
[name] => Jeff
)
)
)
*/
$a = array(
array(7,6,4),
array(3,4,5),
array(3,2,1),
);
$result = Set::reverse($map);
/* $result now looks like:
Array
(
[Post] => Array
(
[id] => 1
[title] => First Post
[Comment] => Array
(
[0] => Array
(
[id] => 1
[title] => First Comment
)
[1] => Array
(
[id] => 2
[title] => Second Comment
)
)
[Tag] => Array
(
[0] => Array
(
[id] => 1
[title] => First Tag
)
[1] => Array
(
[id] => 2
[title] => Second Tag
)
)
)
)
*/
8.4.5 combine
• Edit
• Comments (0)
• History
array Set::combine ($data, $path1 = null, $path2 = null, $groupPath = null)
Creates an associative array using a $path1 as the path to build its keys, and optionally $path2 as path to
get the values. If $path2 is not specified, all values will be initialized to null (useful for Set::merge). You
can optionally group the values by what is obtained when following the path specified in $groupPath.
Code View
$result = Set::combine(array(), '{n}.User.id', '{n}.User.Data');
// $result == array();
$a = array(
array(
'User' => array(
'id' => 2,
'group_id' => 1,
'Data' => array(
'user' => 'mariano.iglesias',
'name' => 'Mariano Iglesias'
)
)
),
array(
'User' => array(
'id' => 14,
'group_id' => 2,
'Data' => array(
'user' => 'phpnut',
'name' => 'Larry E. Masters'
)
)
),
array(
'User' => array(
'id' => 25,
'group_id' => 1,
'Data' => array(
'user' => 'gwoo',
'name' => 'The Gwoo'
)
)
)
);
$result = Set::combine($a, '{n}.User.id');
/* $result now looks like:
Array
(
[2] =>
[14] =>
[25] =>
)
*/
8.4.6 normalize
• Edit
• Comments (0)
• History
array Set::normalize ($list, $assoc = true, $sep = ',', $trim = true)
Normalizes a string or array list.
Code View
$a = array('Tree', 'CounterCache',
'Upload' => array(
'folder' => 'products',
'fields' => array('image_1_id', 'image_2_id', 'image_3_id', 'image_4_id',
'image_5_id')));
$b = array('Cacheable' => array('enabled' => false),
'Limit',
'Bindable',
'Validator',
'Transactional');
$result = Set::normalize($a);
/* $result now looks like:
Array
(
[Tree] =>
[CounterCache] =>
[Upload] => Array
(
[folder] => products
[fields] => Array
(
[0] => image_1_id
[1] => image_2_id
[2] => image_3_id
[3] => image_4_id
[4] => image_5_id
)
)
)
*/
$result = Set::normalize($b);
/* $result now looks like:
Array
(
[Cacheable] => Array
(
[enabled] =>
)
[Limit] =>
[Bindable] =>
[Validator] =>
[Transactional] =>
)
*/
$result = Set::merge($a, $b); // Now merge the two and normalize
/* $result now looks like:
Array
(
[0] => Tree
[1] => CounterCache
[Upload] => Array
(
[folder] => products
[fields] => Array
(
[0] => image_1_id
[1] => image_2_id
[2] => image_3_id
[3] => image_4_id
[4] => image_5_id
)
)
[Cacheable] => Array
(
[enabled] =>
)
[2] => Limit
[3] => Bindable
[4] => Validator
[5] => Transactional
)
*/
$result = Set::normalize(Set::merge($a, $b));
/* $result now looks like:
Array
(
[Tree] =>
[CounterCache] =>
[Upload] => Array
(
[folder] => products
[fields] => Array
(
[0] => image_1_id
[1] => image_2_id
[2] => image_3_id
[3] => image_4_id
[4] => image_5_id
)
)
[Cacheable] => Array
(
[enabled] =>
)
[Limit] =>
[Bindable] =>
[Validator] =>
[Transactional] =>
)
*/
8.4.7 countDim
• Edit
• Comments (0)
• History
integer Set::countDim ($array = null, $all = false, $count = 0)
Counts the dimensions of an array. If $all is set to false (which is the default) it will only consider the
dimension of the first element in the array.
Code View
$data = array('one', '2', 'three');
$result = Set::countDim($data);
// $result == 1
$data = array('1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => '3.1.1'));
$result = Set::countDim($data);
// $result == 2
$data = array('1' => '1.1', '2', '3' => array('3.1' => '3.1.1'));
$result = Set::countDim($data);
// $result == 1
$data = array('1' => '1.1', '2', '3' => array('3.1' => '3.1.1'));
$result = Set::countDim($data, true);
// $result == 2
$data = array('1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => array('3.1.1' => '3.1.1.1')));
$result = Set::countDim($data);
// $result == 2
$data = array('1' => array('1.1' => '1.1.1'), '2', '3' => array('3.1' => array('3.1.1' => '3.1.1.1')));
$result = Set::countDim($data, true);
// $result == 3
$data = array('1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => '2.1.1.1'))), '3'
=> array('3.1' => array('3.1.1' => '3.1.1.1')));
$result = Set::countDim($data, true);
// $result == 4
$data = array('1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' =>
array('2.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')));
$result = Set::countDim($data, true);
// $result == 5
$data = array('1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => array('2.1.1.1'
=> '2.1.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')));
$result = Set::countDim($data, true);
// $result == 5
$set = array('1' => array('1.1' => '1.1.1'), array('2' => array('2.1' => array('2.1.1' => array('2.1.1.1' =>
'2.1.1.1.1')))), '3' => array('3.1' => array('3.1.1' => '3.1.1.1')));
$result = Set::countDim($set, false, 0);
// $result == 2
8.4.9 check
• Edit
• Comments (0)
• History
boolean/array Set::check ($data, $path = null)
Checks if a particular path is set in an array. If $path is empty, $data will be returned instead of a boolean
value.
Code View
$set = array(
'My Index 1' => array('First' => 'The first item')
);
$result = Set::check($set, 'My Index 1.First');
// $result == True
$result = Set::check($set, 'My Index 1');
// $result == True
$result = Set::check($set, array());
// $result == array('My Index 1' => array('First' => 'The first item'))
$set = array(
'My Index 1' => array('First' =>
array('Second' =>
array('Third' =>
array('Fourth' => 'Heavy. Nesting.'))))
);
$result = Set::check($set, 'My Index 1.First.Second');
// $result == True
$result = Set::check($set, 'My Index 1.First.Second.Third');
// $result == True
$result = Set::check($set, 'My Index 1.First.Second.Third.Fourth');
// $result == True
$result = Set::check($set, 'My Index 1.First.Seconds.Third.Fourth');
// $result == False
« diff | remove »
8.4.10 remove
• Edit
• Comments (0)
• History
array Set::remove ($list, $path = null)
Removes an element from a Set or array as defined by $path.
Code View
$a = array(
'pages' => array('name' => 'page'),
'files' => array('name' => 'files')
);
)
*/
8.4.11 classicExtract
• Edit
• Comments (0)
• History
array Set::classicExtract ($data, $path = null)
Gets a value from an array or object that is contained in a given path using an array path syntax, i.e.:
• "{n}.Person.{[a-z]+}" - Where "{n}" represents a numeric key, "Person" represents a string literal
• "{[a-z]+}" (i.e. any string literal enclosed in brackets besides {n} and {s}) is interpreted as a
regular expression.
Example 1Code View
$a = array(
array('Article' => array('id' => 1, 'title' => 'Article 1')),
array('Article' => array('id' => 2, 'title' => 'Article 2')),
array('Article' => array('id' => 3, 'title' => 'Article 3')));
$result = Set::classicExtract($a, '{n}.Article.id');
/* $result now looks like:
Array
(
[0] => 1
[1] => 2
[2] => 3
)
*/
$result = Set::classicExtract($a, '{n}.Article.title');
/* $result now looks like:
Array
(
[0] => Article 1
[1] => Article 2
[2] => Article 3
)
*/
$result = Set::classicExtract($a, '1.Article.title');
// $result == "Article 2"
$result = Set::classicExtract($a, '3.Article.title');
// $result == null
Example 2Code View
$a = array(
0 => array('pages' => array('name' => 'page')),
1 => array('fruites'=> array('name' => 'fruit')),
'test' => array(array('name' => 'jippi')),
'dot.test' => array(array('name' => 'jippi'))
);
8.4.12 matches
• Edit
• Comments (0)
• History
boolean Set::matches ($conditions, $data=array(), $i = null, $length=null)
Set::matches can be used to see if a single item or a given xpath match certain conditions.
Code View
$a = array(
array('Article' => array('id' => 1, 'title' => 'Article 1')),
array('Article' => array('id' => 2, 'title' => 'Article 2')),
array('Article' => array('id' => 3, 'title' => 'Article 3')));
$res=Set::matches(array('id>2'), $a[1]['Article']);
// returns false
$res=Set::matches(array('id>=2'), $a[1]['Article']);
// returns true
$res=Set::matches(array('id>=3'), $a[1]['Article']);
// returns false
$res=Set::matches(array('id<=2'), $a[1]['Article']);
// returns true
$res=Set::matches(array('id<2'), $a[1]['Article']);
// returns false
$res=Set::matches(array('id>1'), $a[1]['Article']);
// returns true
$res=Set::matches(array('id>1', 'id<3', 'id!=0'), $a[1]['Article']);
// returns true
$res=Set::matches(array('3'), null, 3);
// returns true
$res=Set::matches(array('5'), null, 5);
// returns true
$res=Set::matches(array('id'), $a[1]['Article']);
// returns true
$res=Set::matches(array('id', 'title'), $a[1]['Article']);
// returns true
$res=Set::matches(array('non-existant'), $a[1]['Article']);
// returns false
$res=Set::matches('/Article[id=2]', $a);
// returns true
$res=Set::matches('/Article[id=4]', $a);
// returns false
$res=Set::matches(array(), $a);
// returns true
« classicExtract | extract »
8.4.13 extract
• Edit
• Comments (0)
• History
array Set::extract ($path, $data=null, $options=array())
Set::extract uses basic XPath 2.0 syntax to return subsets of your data from a find or a find all. This
function allows you to retrieve your data quickly without having to loop through multi dimentional arrays
or traverse through tree structures.
Selector Note
/User/id Similar to the classic {n}.User.id
/User[2]/name Selects the name of the second User
/User[id<2] Selects all Users with an id < 2
/User[id>2][<5] Selects all Users with an id > 2 but < 5
/
Selects the name of all Posts that have at least one Comment
Post/Comment[author_name=john]/..
written by john
/name
/Posts[title] Selects all Posts that have a 'title' key
/Comment/.[1] Selects the contents of the first comment
/Comment/.[:last] Selects the last comment
/Comment/.[:first] Selects the first comment
Selects all comments that have a text matching the regex
/Comment[text=/cakephp/i]
/cakephp/i
/Comment/@* Selects the key names of all comments
Currently only absolute paths starting with a single '/' are supported. Please report any bugs as you find
them. Suggestions for additional features are welcome.
8.4.14 format
• Edit
• Comments (0)
• History
array Set::format ($data, $format, $keys)
Returns a series of values extracted from an array, formatted in a format string.
Code View
$data = array(
array('Person' => array('first_name' => 'Nate', 'last_name' => 'Abele', 'city' => 'Boston',
'state' => 'MA', 'something' => '42')),
array('Person' => array('first_name' => 'Larry', 'last_name' => 'Masters', 'city' => 'Boondock',
'state' => 'TN', 'something' => '{0}')),
array('Person' => array('first_name' => 'Garrett', 'last_name' => 'Woodworth', 'city' =>
'Venice Beach', 'state' => 'CA', 'something' => '{1}')));
8.4.15 enum
• Edit
• Comments (0)
• History
string Set::enum ($select, $list=null)
The enum method works well when using html select elements. It returns a value from an array list if the
key exists.
If a comma separated $list is passed arrays are numeric with the key of the first being 0 $list = 'no, yes'
would translate to $list = array(0 => 'no', 1 => 'yes');
If an array is used, keys can be strings example: array('no' => 0, 'yes' => 1);
8.4.16 numeric
• Edit
• Comments (0)
• History
boolean Set::numeric ($array=null)
Checks to see if all the values in the array are numeric
Code View
$data = array('one');
$res = Set::numeric(array_keys($data));
// $res is true
// $res is false
$data = array('one');
$res = Set::numeric($data);
// $res is false
// $res is false
// $res is true
$data = array(0);
$res = Set::numeric($data);
// $res is true
// $res is true
$data = array(1 => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five');
$res = Set::numeric(array_keys($data));
// $res is true
$data = array('1' => 'one', 2 => 'two', 3 => 'three', 4 => 'four', 5 => 'five');
$res = Set::numeric(array_keys($data));
// $res is true
$data = array('one', 2 => 'two', 3 => 'three', 4 => 'four', 'a' => 'five');
$res = Set::numeric(array_keys($data));
// $res is false
8.4.17 map
• Edit
• Comments (0)
• History
object Set::map ($class = 'stdClass', $tmp = 'stdClass')
This method Maps the contents of the Set object to an object hierarchy while maintaining numeric keys as
arrays of objects.
Basically, the map function turns array items into initialized class objects. By default it turns an array into
a stdClass Object, however you can map values into any type of class. Example:
Set::map($array_of_values, 'nameOfYourClass');
Code View
$data = array(
array(
"IndexedPage" => array(
"id" => 1,
"url" => 'http://blah.com/',
'hash' => '68a9f053b19526d08e36c6a9ad150737933816a5',
'get_vars' => '',
'redirect' => '',
'created' => "1195055503",
'updated' => "1195055503",
)
),
array(
"IndexedPage" => array(
"id" => 2,
"url" => 'http://blah.com/',
'hash' => '68a9f053b19526d08e36c6a9ad150737933816a5',
'get_vars' => '',
'redirect' => '',
'created' => "1195055503",
'updated' => "1195055503",
),
)
);
$mapped = Set::map($data);
Array
(
[0] => stdClass Object
(
[_name_] => IndexedPage
[id] => 1
[url] => http://blah.com/
[hash] => 68a9f053b19526d08e36c6a9ad150737933816a5
[get_vars] =>
[redirect] =>
[created] => 1195055503
[updated] => 1195055503
)
)
*/
Using Set::map() with a custom class for second parameter:
Code View
class MyClass {
function sayHi() {
echo 'Hi!';
}
}
8.4.18 pushDiff
• Edit
• Comments (0)
• History
array Set::pushDiff ($array1, $array2)
This function merges two arrays and pushes the differences in array2 to the bottom of the resultant array.
8.4.20 merge
• Edit
• Comments (0)
• History
array Set::merge ($arr1, $arr2=null)
This function can be thought of as a hybrid between PHP's array_merge and array_merge_recursive. The
difference to the two is that if an array key contains another array then the function behaves recursive
(unlike array_merge) but does not do if for keys containing strings (unlike array_merge_recursive). See the
unit test for more information.
This function will work with an unlimited amount of arguments and typecasts non-array parameters into
arrays.
Code View
$arry1 = array(
array(
'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
'description' => 'Importing an sql dump'
),
array(
'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
'name' => 'pbpaste | grep -i Unpaid | pbcopy',
'description' => 'Remove all lines that say "Unpaid".',
)
);
$arry2 = 4;
$arry3 = array(0=>"test array", "cats"=>"dogs", "people" => 1267);
$arry4 = array("cats"=>"felines", "dog"=>"angry");
$res = Set::merge($arry1, $arry2, $arry3, $arry4);
/* $res now looks like:
Array
(
[0] => Array
(
[id] => 48c2570e-dfa8-4c32-a35e-0d71cbdd56cb
[name] => mysql raleigh-workshop-08 < 2008-09-05.sql
[description] => Importing an sql dump
)
[2] => 4
[3] => test array
[cats] => felines
[people] => 1267
[dog] => angry
)
*/
8.4.21 contains
• Edit
• Comments (0)
• History
boolean Set::contains ($val1, $val2 = null)
Determines if one Set or array contains the exact keys and values of another.
Code View
$a = array(
0 => array('name' => 'main'),
1 => array('name' => 'about')
);
$b = array(
0 => array('name' => 'main'),
1 => array('name' => 'about'),
2 => array('name' => 'contact'),
'a' => 'b'
);
8.5 Security
• Edit
• Comments (0)
• History
The security library handles basic security measures such as providing methods for hashing and
encrypting data.
8.6 Cache
• Edit
• Comments (0)
• History
The Cache class in CakePHP provides a generic frontend for several backend caching systems. Different
Cache configurations and engines can be setup in your app/config/core.php
8.6.1 Cache::read()
• Edit
• Comments (0)
• History
Cache::read($key, $config = null)
Cache::read() is used to read the cached value stored under $key from the $config. If $config is null the
default config will be used. Cache::read() will return the cached value if it is a valid cache or false if the
cache has expired or doesn't exist. The contents of the cache might evaluate false, so make sure you use
the strict comparison operator === or !==.
For example:
Code View
$cloud = Cache::read('cloud');
8.6.2 Cache::write()
• Edit
• Comments (0)
• History
Cache::write($key, $value, $config = null);
Cache::write() will write a $value to the Cache. You can read or delete this value later by refering to it by
$key. You may specify an optional configuration to store the cache in as well. If no $config is specified
default will be used. Cache::write() can store any type of object and is ideal for storing results of model
finds.
Code View
if (($posts = Cache::read('posts')) === false) {
$posts = $this->Post->find('all');
Cache::write('posts', $posts);
}
Using Cache::write() and Cache::read() to easily reduce the number of trips made to the database to fetch
posts.
8.6.3 Cache::delete()
• Edit
• Comments (0)
• History
Cache::delete($key, $config = null)
Cache::delete() will allow you to completely remove a cached object from the Cache store.
8.6.4 Cache::config()
• Edit
• Comments (0)
• History
Cache::config() is used to create additional Cache configurations. These additional configurations can
have different duration, engines, paths, or prefixes than your default cache config. Using multiple cache
configurations can help reduce the number of times you need to use Cache::set() as well as centralize all
your cache settings.
You must specify which engine to use. It does not default to File.
Code View
Cache::config('short', array(
'engine' => 'File',
'duration'=> '+1 hours',
'path' => CACHE,
'prefix' => 'cake_short_'
));
// long
Cache::config('long', array(
'engine' => 'File',
'duration'=> '+1 week',
'probability'=> 100,
'path' => CACHE . 'long' . DS,
));
By placing the above code in your app/config/core.php you will have two additional Cache configurations.
The name of these configurations 'short' or 'long' is used as the $config parameter for Cache::write() and
Cache::read().
8.6.5 Cache::set()
• Edit
• Comments (0)
• History
Cache::set() allows you to temporarily override a cache configs settings for one operation (usually a read
or write). If you use Cache::set() to change the settings for a write, you should also use Cache::set()
before reading the data back in. If you fail to do so, the default settings will be used when the cache key is
read.
Code View
Cache::set(array('duration' => '+30 days'));
Cache::write('results', $data);
// Later on
8.7 HttpSocket
• Edit
• Comments (0)
• History
CakePHP includes an HttpSocket class which can be used easily for making requests, such as those to web
services.
8.7.1 get
• Edit
• Comments (0)
• History
The get method makes a simple HTTP GET request returning the results.
8.7.2 post
• Edit
• Comments (0)
• History
The post method makes a simple HTTP POST request returning the results.
8.7.3 request
• Edit
• Comments (0)
• History
The base request method, which is called from all the wrappers (get, post, put, delete). Returns the
results of the request.
This section explains how to use the core console applications packaged with CakePHP.
Before you dive in here, you may want to check out the CakePHP Console section covered earlier. Console
setup isn't covered here, so if you've never used the console before, check it out.
« request | Code Generation with Bake »
Those new to Bake (especially Windows users) may find the Bake screencast helpful in setting things up
before continuing.
Depending on the configuration of your setup, you may have to set execute rights on the cake bash script
or call it using ./cake bake. The cake console is run using the PHP CLI (command line interface). If you
have problems running the script, ensure that you have the PHP CLI installed and that it has the proper
modules enabled (eg: MySQL).
When running Bake for the first time, you'll be prompted to create a Database Configuration file, if you
haven't created one already.
After you've created a Database Configuration file, running Bake will present you with the following
options:
---------------------------------------------------------------
App : app
Path: /path-to/project/app
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q)
>
Alternatively, you can run any of these commands directly from the command line:
• Two new tasks (FixtureTask and TestTask) are accessible from the main bake menu
• A third task (TemplateTask) has been added for use in your shells.
• All the different bake tasks now allow you to use connections other than default for baking. Using
the -connection parameter.
• Plugin support has been greatly improved. You can use either -plugin PluginName or Plugin.class.
• Questions have been clarified, and made easier to understand.
• Multiple validations on models has been added.
• Self Associated models using parent_id are now detected. For example if your model is named
Thread, a ParentThread and ChildThread association will be created.
• Fixtures and Tests can be baked separately.
• Baked Tests include as many fixtures as they know about, including plugin detection (plugin
detection does not work on PHP4).
So with the laundry list of features, we'll take some time to look at some of the new commands, new
parameters and updated features.
In addition to being rebuildable at any time, baked tests are now attempt to find as many fixtures as
possible. In the past getting into testing often involved fighting through numerous 'Missing Table' errors.
With more advanced fixture detection we hope to make testing easier and more accessible.
Test cases also generate skeleton test methods for every non-inherited public method in your classes.
Saving you one extra step.
TemplateTask is a behind the scenes task, and it handles file generation from templates. In previous
versions of CakePHP baked views were template based, but all other code was not. With 1.3 almost all the
content in the files generated by bake are controlled by templates and the TemplateTask.
The FixtureTask not only generates fixtures with dummy data, but using the interactive options or the
-records option you can enable fixture generation using live data.
New bake command
New commands have been added to make baking easier and faster. Controller, Model, View baking all
feature an all subcommand, that builds everything at once and makes speedy rebuilds easy.
Code View
cake bake model all
Would bake all the models for an application in one shot. Similarly cake bake controller all would bake all
controllers and cake bake view all would generate all view files. Parameters on the ControllerTask have
changed as well. cake bake controller scaffold is now cake bake controller public. ViewTaskhas had an
-admin flag added, using -admin will allow you to bake views for actions that begin with Routing.admin
As mentioned before cake bake fixture and cake bake test are new, and have several subcommands each.
cake bake fixture all will regenerate all the basic fixtures for your application. The -count parameter allows
you to set the number of fake records that are created. By running fixture task interactively you can
generate fixtures using the data in your live tables. You can use cake bake test <type> <class> to create
test cases for already created objects in your app. Type should be one of the standard CakePHP types
('component', 'controller', 'model', 'helper', 'behavior') but doesn't have to be. Class should be an existing
object of the chosen type.
Templates Galore
New in bake for 1.3 is the addition of more templates. In 1.2 baked views used templates that could be
changed to modify the view files bake generated. In 1.3 templates are used to generate all output from
bake. There are separate templates for controllers, controller action sets, fixtures, models, test cases, and
the view files from 1.2. As well as more templates, you can also have multiple template sets or, bake
themes. Bake themes can be provided in your app, or as part of plugins. An example plugin path for bake
theme would be app/plugins/bake_theme/vendors/shells/templates/dark_red/. An app bake theme called
blue_bunny would be placed inapp/vendors/shells/templates/blue_bunny. You can look at
cake/console/templates/default/ to see what directories and files are required of a bake theme. However,
like view files, if your bake theme doesn't implement a template, other installed themes will be checked
until the correct template is found.
Additional plugin support.
New in 1.3 are additional ways to specify plugin names when using bake. In addition to cake bake plugin
Todo controller Posts, there are two new forms. cake bake controller Todo.Posts and cake bake controller
Posts -plugin Todo. The plugin parameter can be while using interactive bake as well. cake bake controller
-plugin Todo, for example will allow you to use interactive bake to add controllers to your Todo plugin.
Additional / multiple plugin paths are supported as well. In the past bake required your plugin to be in
app/plugins. In 1.3 bake will find which of the pluginPaths the named plugin is located on, and add the
files there.
Schema files can also be used to generate sql dump files. To generate a sql file containing the CREATE
TABLE statements, run:
$ cake schema dump filename.sql
Where filename.sql is the desired filename for the sql dump. If you omit filename.sql the sql dump will be
output to the console but not written to a file.
Generating Schema...
Schema file exists.
[O]verwrite
[S]napshot
[Q]uit
Would you like to do? (o/s/q)
Choosing [s] (snapshot) will create an incremented schema.php. So if you have schema.php, it will create
schema_2.php and so on. You can then restore to any of these schema files at any time by running:
5. Pass the skeleton path parameter to the project task Code View
cake bake project -skel vendors/shells/templates/skel
Notes
• You must run the specific project task cake bake project so that the path parameter can be
passed.
• The template path is relative to the current path of the Command Line Interface.
• Since the full path to the skeleton needs to be manually entered, you can specify any directory
holding your template build you want, including using multiple templates. (Unless Cake starts
supporting overriding the skel folder like it does for views)
« Migrations with CakePHP schema shell | Tutorials & Examples »
Alternatively, you can refer to CakeForge and the Bakery for existing applications and components. Don't
forget that you can also view the source code of this cook book.
10.1 Blog
• Edit
• Comments (1)
• History
Welcome to CakePHP. You're probably checking out this tutorial because you want to learn more about
how CakePHP works. It's our aim to increase productivity and make coding more enjoyable: we hope you'll
see this as you dive into the code.
This tutorial will walk you through the creation of a simple blog application. We'll be getting and installing
Cake, creating and configuring a database, and creating enough application logic to list, add, edit, and
delete blog posts.
1. A running web server. We're going to assume you're using Apache, though the instructions for
using other servers should be very similar. We might have to play a little with the server
configuration, but most folks can get Cake up and running without any configuration at all.
2. A database server. We're going to be using mySQL in this tutorial. You'll need to know enough
about SQL in order to create a database: Cake will be taking the reins from there.
3. Basic PHP knowledge. The more object-oriented programming you've done, the better: but fear
not if you're a procedural fan.
4.
Finally, you'll need a basic knowledge of the MVC programming pattern. A quick overview can be
found in Chapter "Beginning With CakePHP", Section :Understanding Model-View-Controller. Don't
worry: its only a half a page or so.
Let's get started!
The choices on table and column names are not arbitrary. If you follow Cake's database naming
conventions, and Cake's class naming conventions (both outlined in"CakePHP Conventions"), you'll be
able to take advantage of a lot of free functionality and avoid configuration. Cake is flexible enough to
accomodate even the worst legacy database schema, but adhering to convention will save you time.
Check out "CakePHP Conventions" for more information, but suffice it to say that naming our table 'posts'
automatically hooks it to our Post model, and having fields called 'modified' and 'created' will be
automagically managed by Cake.
1. Make sure that an .htaccess override is allowed: in your httpd.conf, you should have a section that
defines a section for each Directory on your server. Make sure the AllowOverride is set to All for
the correct Directory. For security and performance reasons, do not set AllowOverride to All in
<Directory />. Instead, look for the <Directory> block that refers to your actual website directory.
2. Make sure you are editing the correct httpd.conf rather than a user- or site-specific httpd.conf.
3. For some reason or another, you might have obtained a copy of CakePHP without the needed
.htaccess files. This sometimes happens because some operating systems treat files that start
with '.' as hidden, and don't copy them. Make sure your copy of CakePHP is from the downloads
section of the site or our git repository.
4. Make sure Apache is loading up mod_rewrite correctly! You should see something like LoadModule
rewrite_module libexec/httpd/mod_rewrite.so or (for Apache 1.3) AddModule mod_rewrite.c in your
httpd.conf.
If you don't want or can't get mod_rewrite (or some other compatible module) up and running on your
server, you'll need to use Cake's built in pretty URLs. In/app/config/core.php, uncomment the line that
looks like:
Code View
Configure::write('App.baseUrl', env('SCRIPT_NAME'));
Also remove these .htaccess files:
Code View
/.htaccess
/app/.htaccess
/app/webroot/.htaccess
1. Use Microsoft's Web Platform Installer to install the URL Rewrite Module 2.0.
</rule>
CakePHP's model class files go in /app/models, and the file we'll be creating will be saved to
/app/models/post.php. The completed file should look like this:
Code View
<?php
?>
Naming convention is very important in CakePHP. By naming our model Post, CakePHP can automatically
infer that this model will be used in the PostsController, and will be tied to a database table called posts.
CakePHP will dynamically create a model object for you, if it cannot find a corresponding file in
/app/models. This also means, that if you accidentally name your file wrong (i.e. Post.php or posts.php)
CakePHP will not recognize any of your settings and will use the defaults instead.
The $name variable is always a good idea to add, and is used to overcome some class name oddness in
PHP4.
For more on models, such as table prefixes, callbacks, and validation, check out the Models chapter of the
Manual.
function index() {
$this->set('posts', $this->Post->find('all'));
}
}
?>
Let me explain the action a bit. By defining function index() in our PostsController, users can now access
the logic there by requestingwww.example.com/posts/index. Similarly, if we were to define a function
called foobar(), users would be able to access that at www.example.com/posts/foobar.
You may be tempted to name your controllers and actions a certain way to obtain a certain URL. Resist
that temptation. Follow CakePHP conventions (plural controller names, etc.) and create readable,
understandable action names. You can map URLs to your code using "routes" covered later on.
The single instruction in the action uses set() to pass data from the controller to the view (which we'll
create next). The line sets the view variable called 'posts' equal to the return value of the find('all')
method of the Post model. Our Post model is automatically available at $this->Post because we've
followed Cake's naming conventions.
To learn more about Cake's controllers, check out Chapter "Developing with CakePHP" section:
"Controllers".
10.1.8 Creating Post Views
• Edit
• Comments (1)
• History
Now that we have our data flowing to our model, and our application logic and flow defined by our
controller, let's create a view for the index action we created above.
Cake views are just presentation-flavored fragments that fit inside an application's layout. For most
applications they're HTML mixed with PHP, but they may end up as XML, CSV, or even binary data.
Layouts are presentation code that is wrapped around a view, and can be defined and switched between,
but for now, let's just use the default.
Remember in the last section how we assigned the 'posts' variable to the view using the set() method?
That would hand down data to the view that would look something like this:
// print_r($posts) output:
Array
(
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => The title
[body] => This is the post body.
[created] => 2008-02-13 18:34:55
[modified] =>
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[title] => A title once again
[body] => And the post body follows.
[created] => 2008-02-13 18:34:56
[modified] =>
)
)
[2] => Array
(
[Post] => Array
(
[id] => 3
[title] => Title strikes back
[body] => This is really exciting! Not.
[created] => 2008-02-13 18:34:57
[modified] =>
)
)
)
Cake's view files are stored in /app/views inside a folder named after the controller they correspond to
(we'll have to create a folder named 'posts' in this case). To format this post data in a nice table, our view
code might look something like this:
Code View
<!-- File: /app/views/posts/index.ctp -->
<h1>Blog posts</h1>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
</tr>
<!-- Here is where we loop through our $posts array, printing out post info -->
</table>
Hopefully this should look somewhat simple.
You might have noticed the use of an object called $html. This is an instance of the CakePHP HtmlHelper
class. CakePHP comes with a set of view helpers that make things like linking, form output, JavaScript and
Ajax a snap. You can learn more about how to use them in Chapter "Built-in Helpers", but what's
important to note here is that the link() method will generate an HTML link with the given title (the first
parameter) and URL (the second parameter).
When specifying URLs in Cake, you simply give a path relative to the base of the application, and Cake
fills in the rest. As such, your URLs will typically take the form of/controller/action/param1/param2.
At this point, you should be able to point your browser to http://www.example.com/posts/index. You should
see your view, correctly formatted with the title and table listing of the posts.
If you happened to have clicked on one of the links we created in this view (that link a post's title to a URL
/posts/view/some_id), you were probably informed by CakePHP that the action hasn't yet been defined. If
you were not so informed, either something has gone wrong, or you actually did define it already, in which
case you are very sneaky. Otherwise, we'll create it in the PostsController now:
Code View
<?php
class PostsController extends AppController {
function index() {
$this->set('posts', $this->Post->find('all'));
}
function index() {
$this->set('posts', $this->Post->find('all'));
}
function view($id) {
$this->Post->id = $id;
$this->set('post', $this->Post->read());
function add() {
if (!empty($this->data)) {
if ($this->Post->save($this->data)) {
$this->Session->setFlash('Your post has been saved.');
$this->redirect(array('action' => 'index'));
}
}
}
}
?>
Here's what the add() action does: if the submitted form data isn't empty, try to save the data using the
Post model. If for some reason it doesn't save, just render the view. This gives us a chance to show the
user validation errors or other warnings.
When a user uses a form to POST data to your application, that information is available in $this->data.
You can use the pr() or debug functions to print it out if you want to see what it looks like.
We use the Session component's setFlash() function to set a message to a session variable to be
displayed on the page after redirection. In the layout we have$session->flash() which displays the
message and clears the corresponding session variable. The controller's redirect function redirects to
another URL. The param array('action'=>'index) translates to URL /posts i.e the index action of posts
controller. You can refer to Router::url function on the api to see the formats in which you can specify a
URL for various cake functions.
Calling the save() method will check for validation errors and abort the save if any occur. We'll discuss
how those errors are handled in the following sections.
10.1.10 Data Validation
• Edit
• Comments (0)
• History
Cake goes a long way in taking the monotony out of form input validation. Everyone hates coding up
endless forms and their validation routines. CakePHP makes it easier and faster.
To take advantage of the validation features, you'll need to use Cake's FormHelper in your views. The
FormHelper is available by default to all views at $form.
Here's our add view:
Code View
<!-- File: /app/views/posts/add.ctp -->
<h1>Add Post</h1>
<?php
echo $form->create('Post');
echo $form->input('title');
echo $form->input('body', array('rows' => '3'));
echo $form->end('Save Post');
?>
Here, we use the FormHelper to generate the opening tag for an HTML form. Here's the HTML that $form-
>create() generates:
Code View
<form id="PostAddForm" method="post" action="/posts/add">
If create() is called with no parameters supplied, it assumes you are building a form that submits to the
current controller's add() action (or edit() action when id is included in the form data), via POST.
The $form->input() method is used to create form elements of the same name. The first parameter tells
CakePHP which field they correspond to, and the second parameter allows you to specify a wide array of
options - in this case, the number of rows for the textarea. There's a bit of introspection and automagic
here: input() will output different form elements based on the model field specified.
The $form->end() call generates a submit button and ends the form. If a string is supplied as the first
parameter to end(), the FormHelper outputs a submit button named accordingly along with the closing
form tag. Again, refer to Chapter "Built-in Helpers" for more on helpers.
Now let's go back and update our /app/views/posts/index.ctp view to include a new "Add Post" link. Before
the <table>, add the following line:
Code View
<?php echo $html->link('Add Post',array('controller' => 'posts', 'action' => 'add'))?>
You may be wondering: how do I tell CakePHP about my validation requirements? Validation rules are
defined in the model. Let's look back at our Post model and make a few adjustments:
Code View
<?php
class Post extends AppModel
{
var $name = 'Post';
<h1>Blog posts</h1>
<p><?php echo $html->link('Add Post', array('action' => 'add')); ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Actions</th>
<th>Created</th>
</tr>
<!-- Here's where we loop through our $posts array, printing out post info -->
</table>
10.1.12 Editing Posts
• Edit
• Comments (0)
• History
Post editing: here we go. You're a CakePHP pro by now, so you should have picked up a pattern. Make the
action, then the view. Here's what the edit() action of the PostsController would look like:
Code View
function edit($id = null) {
$this->Post->id = $id;
if (empty($this->data)) {
$this->data = $this->Post->read();
} else {
if ($this->Post->save($this->data)) {
$this->Session->setFlash('Your post has been updated.');
$this->redirect(array('action' => 'index'));
}
}
}
This action first checks for submitted form data. If nothing was submitted, it finds the Post and hands it to
the view. If some data has been submitted, try to save the data using Post model (or kick back and show
the user the validation errors).
The edit view might look something like this:
Code View
<!-- File: /app/views/posts/edit.ctp -->
<h1>Edit Post</h1>
<?php
echo $form->create('Post', array('action' => 'edit'));
echo $form->input('title');
echo $form->input('body', array('rows' => '3'));
echo $form->input('id', array('type'=>'hidden'));
echo $form->end('Save Post');
?>
This view outputs the edit form (with the values populated), along with any necessary validation error
messages.
One thing to note here: CakePHP will assume that you are editing a model if the 'id' field is present in the
data array. If no 'id' is present (look back at our add view), Cake will assume that you are inserting a new
model when save() is called.
You can now update your index view with links to edit specific posts:
Code View
<!-- File: /app/views/posts/index.ctp (edit links added) -->
<h1>Blog posts</h1>
<p><?php echo $html->link("Add Post", array('action'=>'add')); ?>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Action</th>
<th>Created</th>
</tr>
<!-- Here's where we loop through our $posts array, printing out post info -->
</table>
10.1.13 Routes
• Edit
• Comments (0)
• History
For some, CakePHP's default routing works well enough. Developers who are sensitive to user-friendliness
and general search engine compatibility will appreciate the way that CakePHP's URLs map to specific
actions. So we'll just make a quick change to routes in this tutorial.
Cake's routing is found in /app/config/routes.php. You'll want to comment out or remove the line that
defines the default root route. It looks like this:
Code View
Router::connect ('/', array('controller'=>'pages', 'action'=>'display', 'home'));
This line connects the URL '/' with the default CakePHP home page. We want it to connect with our own
controller, so replace that line with this one:
Code View
Router::connect ('/', array('controller'=>'posts', 'action'=>'index'));
This should connect users requesting '/' to the index() action of our PostsController.
CakePHP also makes use of 'reverse routing' - if with the above route defined you pass
array('controller'=>'posts', 'action'=>'index') to a function expecting an array, the resultant url used will
be '/'. It's therefore a good idea to always use arrays for urls as this means your routes define where a url
goes, and also ensures that links point to the same place too.
« Editing Posts | Conclusion »
10.1.14 Conclusion
• Edit
• Comments (0)
• History
Creating applications this way will win you peace, honor, love, and money beyond even your wildest
fantasies. Simple, isn't it? Keep in mind that this tutorial was very basic. CakePHP has many more features
to offer, and is flexible in ways we didn't wish to cover here for simplicity's sake. Use the rest of this
manual as a guide for building more feature-rich applications.
Now that you've created a basic Cake application you're ready for the real thing. Start your own project,
read the rest of the Manual and API.
If you need help, come see us in #cakephp. Welcome to CakePHP!
1. A running web server. We're going to assume you're using Apache, though the instructions for
using other servers should be very similar. We might have to play a little with the server
configuration, but most folks can get Cake up and running without any configuration at all.
2. A database server. We're going to be using mySQL in this tutorial. You'll need to know enough
about SQL in order to create a database: Cake will be taking the reins from there.
3. Basic PHP knowledge. The more object-oriented programming you've done, the better: but fear
not if you're a procedural fan.
You can also checkout/export a fresh copy of our trunk code at: git://github.com/cakephp/cakephp.git
Once you've got a fresh copy of cake setup your database.php config file, and change the value of
Security.salt in your app/config/core.php. From there we will build a simple database schema to build our
application on. Execute the following SQL statements into your database.
While baking the Models cake will automagically detect the associations between your Models (or
relations between your tables). Let cake supply the correct hasMany and belongsTo associations. If you
are prompted to pick hasOne or hasMany, generally speaking you'll need a hasMany (only) relationships
for this tutorial.
Leave out admin routing for now, this is a complicated enough subject without them. Also be sure not to
add either the Acl or Auth Components to any of your controllers as you are baking them. We'll be doing
that soon enough. You should now have models, controllers, and baked views for your users, groups, posts
and widgets.
function logout() {
//Leave empty for now.
}
Then create the following view file for login at app/views/users/login.ctp:
Code View
<?php
$session->flash('auth');
echo $form->create('User', array('action' => 'login'));
echo $form->inputs(array(
'legend' => __('Login', true),
'username',
'password'
));
echo $form->end('Login');
?>
We don't need to worry about adding anything to hash passwords, as AuthComponent will do this for us
automatically when creating/editing users, and when they login, once configured properly. Furthermore, if
you hash incoming passwords manually AuthComponent will not be able to log you in at all. As it will hash
them again, and they will not match.
Next we need to make some modifications to AppController. If you don't have /app/app_controller.php,
create it. Note that this goes in /app/, not/app/controllers/. Since we want our entire site controlled with
Auth and Acl, we will set them up in AppController.
Code View
<?php
class AppController extends Controller {
var $components = array('Acl', 'Auth', 'Session');
function beforeFilter() {
//Configure AuthComponent
$this->Auth->authorize = 'actions';
$this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
$this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
$this->Auth->loginRedirect = array('controller' => 'posts', 'action' => 'add');
}
}
?>
Before we set up the ACL at all we will need to add some users and groups. With AuthComponent in use
we will not be able to access any of our actions, as we are not logged in. We will now add some
exceptions so AuthComponent will allow us to create some groups and users. In both your
GroupsController and yourUsersController Add the following.
Code View
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('*');
}
These statements tell AuthComponent to allow public access to all actions. This is only temporary and will
be removed once we get a few users and groups into our database. Don't add any users or groups just yet
though.
function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
$data = $this->data;
if (empty($this->data)) {
$data = $this->read();
}
if (empty($data['User']['group_id'])) {
return null;
} else {
return array('Group' => array('id' => $data['User']['group_id']));
}
}
Then in our Group Model Add the following:
Code View
var $actsAs = array('Acl' => array('type' => 'requester'));
function parentNode() {
return null;
}
What this does, is tie the Group and User models to the Acl, and tell CakePHP that every-time you make a
User or Group you want an entry on the aros table as well. This makes Acl management a piece of cake as
your AROs become transparently tied to your users and groups tables. So anytime you create or delete a
user/group the Aro table is updated.
Our controllers and models are now prepped for adding some initial data, and our Group and User models
are bound to the Acl table. So add some groups and users using the baked forms by browsing to
http://example.com/groups/add and http://example.com/users/add. I made the following groups:
• administrators
• managers
• users
I also created a user in each group so I had a user of each different access group to test with later. Write
everything down or use easy passwords so you don't forget. If you do a SELECT * FROM aros; from a
mysql prompt you should get something like the following:
+----+-----------+-------+-------------+-------+------+------+
| id | parent_id | model | foreign_key | alias | lft | rght |
+----+-----------+-------+-------------+-------+------+------+
| 1| NULL | Group | 1 | NULL | 1 | 4 |
| 2| NULL | Group | 2 | NULL | 5 | 8 |
| 3| NULL | Group | 3 | NULL | 9 | 12 |
| 4| 1 | User | 1 | NULL | 2 | 3 |
| 5| 2 | User | 2 | NULL | 6 | 7 |
| 6| 3 | User | 3 | NULL | 10 | 11 |
+----+-----------+-------+-------------+-------+------+------+
6 rows in set (0.00 sec)
This shows us that we have 3 groups and 3 users. The users are nested inside the groups, which means
we can set permissions on a per-group or per-user basis.
When modifying a user, you must manually update the ARO. This code should be executed wherever
you're updating the user information:
Code View
// Check if their permission group is changing
$oldgroupid = $this->User->field('group_id');
if ($oldgroupid !== $this->data['User']['group_id']) {
$aro =& $this->Acl->Aro;
$user = $aro->findByForeignKeyAndModel($this->data['User']['id'], 'User');
$group = $aro->findByForeignKeyAndModel($this->data['User']['group_id'], 'Group');
Our ARO are automatically creating themselves when new users and groups are created. What about a
way to auto-generate ACOs from our controllers and their actions? Well unfortunately there is no magic
way in CakePHP's core to accomplish this. The core classes offer a few ways to manually create ACO's
though. You can create ACO objects from the Acl shell or You can use the AclComponent. Creating Acos
from the shell looks like:
cake acl create aco root controllers
While using the AclComponent would look like:
Code View
$this->Acl->Aco->create(array('parent_id' => null, 'alias' => 'controllers'));
$this->Acl->Aco->save();
Both of these examples would create our 'root' or top level ACO which is going to be called 'controllers'.
The purpose of this root node is to make it easy to allow/deny access on a global application scope, and
allow the use of the Acl for purposes not related to controllers/actions such as checking model record
permissions. As we will be using a global root ACO we need to make a small modification to our
AuthComponent configuration. AuthComponent needs to know about the existence of this root node, so
that when making ACL checks it can use the correct node path when looking up controllers/actions. In
AppController add the following to the beforeFilter:
Code View
$this->Auth->actionPath = 'controllers/';
App::import('Core', 'File');
$Controllers = Configure::listObjects('controller');
$appIndex = array_search('App', $Controllers);
if ($appIndex !== false ) {
unset($Controllers[$appIndex]);
}
$baseMethods = get_class_methods('Controller');
$baseMethods[] = 'buildAcl';
$Plugins = $this->_getPluginControllerNames();
$Controllers = array_merge($Controllers, $Plugins);
/**
* Get the names of the plugin controllers ...
*
* This function will get an array of the plugin controller names, and
* also makes sure the controllers are available for us to get the
* method names by doing an App::import for each plugin controller.
*
* @return array of plugin names.
*
*/
function _getPluginControllerNames() {
App::import('Core', 'File', 'Folder');
$paths = Configure::getInstance();
$folder =& new Folder();
$folder->cd(APP . 'plugins');
In order to allow with the AclComponent we would use the following code syntax in a custom method:
Code View
$this->Acl->allow($aroAlias, $acoAlias);
We are going to add in a few allow/deny statements now. Add the following to a temporary function in
your UsersController and visit the address in your browser to run them (e.g.
http://localhost/cake/app/users/initdb). If you do a SELECT * FROM aros_acos you should see a whole pile
of 1's and 0's. Once you've confirmed your permissions are set remove the function.
Code View
function initDB() {
$group =& $this->User->Group;
//Allow admins to everything
$group->id = 1;
$this->Acl->allow($group, 'controllers');
10.2.8 Logging in
• Edit
• Comments (0)
• History
Our application is now under access control, and any attempt to view non-public pages will redirect you to
the login page. However, we will need to create a login view before anyone can login. Add the following to
app/views/users/login.ctp if you haven't done so already.
Code View
<h2>Login</h2>
<?php
echo $form->create('User', array('url' => array('controller' => 'users', 'action' =>'login')));
echo $form->input('User.username');
echo $form->input('User.password');
echo $form->end('Login');
?>
If a user is already logged in, redirect him:
Code View
function login() {
if ($this->Session->read('Auth.User')) {
$this->Session->setFlash('You are logged in!');
$this->redirect('/', null, false);
}
}
You may also want to add a flash() for Auth messages to your layout. Copy the default core layout - found
at cake/libs/view/layouts/default.ctp - to your app layouts folder if you haven't done so already. In
app/views/layouts/default.ctp add
Code View
echo $this->Session->flash('auth');
You should now be able to login and everything should work auto-magically. When access is denied Auth
messages will be displayed if you added the echo $session->flash('auth')
10.2.9 Logout
• Edit
• Comments (0)
• History
Now onto the logout. Earlier we left this function blank, now is the time to fill it. In
UsersController::logout() add the following:
Code View
$this->Session->setFlash('Good-Bye');
$this->redirect($this->Auth->logout());
This sets a Session flash message and logs out the User using Auth's logout method. Auth's logout
method basically deletes the Auth Session Key and returns a url that can be used in a redirect. If there is
other session data that needs to be deleted as well add that code here.
11 Appendices
• Edit
• Comments (0)
• History
Appendices contain information regarding the new features introduced in 1.3 and migration path from 1.2
to 1.3
# Removed Constants
• Edit
• View just this section
• Comments (0)
• History
The following constants have been removed from CakePHP. If your application depends on them you must
define them in app/config/bootstrap.php
• CIPHER_SEED - It has been replaced with Configure class var Security.cipherSeed which should be
changed in app/config/core.php
• PEAR
• INFLECTIONS
• VALID_NOT_EMPTY
• VALID_EMAIL
• VALID_NUMBER
• VALID_YEAR
• session.php ⇒ cake_session.php
• socket.php ⇒ cake_socket.php
• schema.php ⇒ cake_schema.php
• behavior.php ⇒ model_behavior.php
In most cases, the above renaming will not affect userland code.
• Router
• Set
• Inflector
• Cache
• CacheEngine
If you were using Object methods from these classes, you will need to not use those methods.
# Library Classes
• Edit
• View just this section
• Comments (0)
• History
CakeSession
• del is deprecated use delete
SessionComponent
• SessionComponent::setFlash() now uses an element instead of a layout as its second parameter.
Be sure to move any flash layouts from app/views/layouts to app/views/elements
Folder
• mkdir is deprecated use create
• mv is deprecated use move
• ls is deprecated use read
• cp is deprecated use copy
• rm is deprecated use delete
Set
• isEqual is deprecated. Use == or ===.
String
• getInstance is deprecated, call String methods statically.
Router
Routing.admin is deprecated. It provided an inconsistent behavior with other prefix style routes in that it
was treated differently. Instead you should useRouting.prefixes. Prefix routes in 1.3 do not require
additional routes to be declared manually. All prefix routes will be generated automatically. To update
simply change your core.php.
Code View
//from:
Configure::write('Routing.admin', 'admin');
//to:
Configure::write('Routing.prefixes', array('admin'));
See the New features guide for more information on using prefix routes. A small change has also been
done to routing params. Routed params should now only consist of alphanumeric chars, - and _ or /[A-Z0-
9-_+]+/.
Code View
Router::connect('/:$%@#param/:action/*', array(...)); // BAD
Router::connect('/:can/:anybody/:see/:m-3/*', array(...)); //Acceptable
For 1.3 the internals of the Router were heavily refactored to increase performance and reduce code
clutter. The side effect of this is two seldom used features were removed, as they were problematic and
buggy even with the existing code base. First path segments using full regular expressions was removed.
You can no longer create routes like
Code View
Router::connect('/([0-9]+)-p-(.*)/', array('controller' => 'products', 'action' => 'show'));
These routes complicated route compilation and impossible to reverse route. If you need routes like this, it
is recommended that you use route parameters with capture patterns. Next mid-route greedy star support
has been removed. It was previously possible to use a greedy star in the middle of a route.
Code View
Router::connect(
'/pages/*/:event',
array('controller' => 'pages', 'action' => 'display'),
array('event' => '[a-z0-9_-]+')
);
This is no longer supported as mid-route greedy stars behaved erratically, and complicated route
compiling. Outside of these two edge-case features and the above changes the router behaves exactly as
it did in 1.2
Also, people using the 'id' key in array-form URLs will notice that Router::url() now treats this as a named
parameter. If you previously used this approach for passing the ID parameter to actions, you will need to
rewrite all your $html->link() and $this->redirect() calls to reflect this change.
Code View
// old format:
$url = array('controller' => 'posts', 'action' => 'view', 'id' => $id);
// use cases:
Router::url($url);
$html->link($url);
$this->redirect($url);
// 1.2 result:
/posts/view/123
// 1.3 result:
/posts/view/id:123
// correct format:
$url = array('controller' => 'posts', 'action' => 'view', $id);
Dispatcher
Dispatcher is no longer capable of setting a controller's layout/viewPath with request parameters. Control
of these properties should be handled by the Controller, not the Dispatcher. This feature was also
undocumented, and untested.
Debugger
• Debugger::checkSessionKey() has been renamed to Debugger::checkSecurityKeys()
• Calling Debugger::output("text") no longer works. Use Debugger::output("txt").
Object
•Object::$_log has been removed. CakeLog::write is now called statically. See New Logging features
for more information on changes made to logging.
Sanitize
• Sanitize::html() now actually always returns escaped strings. In the past using the $remove
parameter would skip entity encoding, returning possibly dangerous content.
•
Sanitize::clean() now has a remove_html option. This will trigger the strip_tags feature of
Sanitize::html(), and must be used in conjunction with theencode parameter.
Configure and App
• Configure::listObjects() replaced by App::objects()
• Configure::corePaths() replaced by App::core()
• Configure::buildPaths() replaced by App::build()
• Configure no longer manages paths.
• Configure::write('modelPaths', array...) replaced by App::build(array('models' => array...))
• Configure::read('modelPaths') replaced by App::path('models')
• There is no longer a debug = 3. The controller dumps generated by this setting often caused
memory consumption issues making it an impractical and unusable setting. The $cakeDebug
variable has also been removed from View::renderLayout You should remove this variable
reference to avoid errors.
• Configure::load() can now load configuration files from plugins. Use Configure::load('plugin.file');
to load configuration files from plugins. Any configuration files in your application that use . in the
name should be updated to use _
Cache
In addition to being able to load CacheEngines from app/libs or plugins, Cache underwent some
refactoring for CakePHP1.3. These refactorings focused around reducing the number and frequency of
method calls. The end result was a significant performance improvement with only a few minor API
changes which are detailed below.
The changes in Cache removed the singletons used for each Engine type, and instead an engine instance
is made for each unique key created with Cache::config(). Since engines are not singletons anymore,
Cache::engine() was not needed and was removed. In addition Cache::isInitialized() now checks
cacheconfiguration names, not cache engine names. You can still use Cache::set() or Cache::engine() to
modify cache configurations. Also checkout the New features guide for more information on the additional
methods added to Cache.
It should be noted that using an app/libs or plugin cache engine for the default cache config can cause
performance issues as the import that loads these classes will always be uncached. It is recommended
that you either use one of the core cache engines for your default configuration, or manually include the
cache engine class before configuring it. Furthermore any non-core cache engine configurations should be
done in app/config/bootstrap.php for the same reasons detailed above.
sort(), prev(), next() now add additional class names to the generated html. prev() adds a class of prev.
next() adds a class of next. sort() will add the direction currently being sorted, either asc or desc.
FormHelper
• FormHelper::dateTime() no longer has a $showEmpty parameter. Use $attributes['empty'] instead.
• FormHelper::year() no longer has a $showEmpty parameter. Use $attributes['empty'] instead.
• FormHelper::month() no longer has a $showEmpty parameter. Use $attributes['empty'] instead.
• FormHelper::day() no longer has a $showEmpty parameter. Use $attributes['empty'] instead.
• FormHelper::minute() no longer has a $showEmpty parameter. Use $attributes['empty'] instead.
• FormHelper::meridian() no longer has a $showEmpty parameter. Use $attributes['empty'] instead.
• FormHelper::select() no longer has a $showEmpty parameter. Use $attributes['empty'] instead.
• Default urls generated by form helper no longer contain 'id' parameter. This makes default urls
more consistent with documented userland routes. Also enables reverse routing to work in a more
intuitive fashion with default FormHelper urls.
• FormHelper::submit() Can now create other types of inputs other than type=submit. Use the type
option to control the type of input generated.
• FormHelper::button() Now creates <button> elements instead of reset or clear inputs. If you want
to generate those types of inputs useFormHelper::submit() with a 'type' => 'reset' option for
example.
• FormHelper::secure() and FormHelper::create() no longer create hidden fieldset elements. Instead
they create hidden div elements. This improves validation with HTML4.
Also be sure to check the Form helper improvements for additional changes and new features in the
FormHelper.
HtmlHelper
• HtmlHelper::meta() no longer has an $inline parameter. It has been merged with the $options
array.
• HtmlHelper::link() no longer has an $escapeTitle parameter. Use $options['escape'] instead.
• HtmlHelper::para() no longer has an $escape parameter. Use $options['escape'] instead.
• HtmlHelper::div() no longer has an $escape parameter. Use $options['escape'] instead.
• HtmlHelper::tag() no longer has an $escape parameter. Use $options['escape'] instead.
• HtmlHelper::css() no longer has an $inline parameter. Use $options['inline'] instead.
SessionHelper
• flash() no longer auto echos. You must add an echo $session->flash(); to your session->flash()
calls. flash() was the only helper method that auto outputted, and was changed to create
consistency in helper methods.
CacheHelper
CacheHelper's interactions with Controller::$cacheAction has changed slightly. In the past if you used an
array for $cacheAction you were required to use the routed url as the keys, this caused caching to break
whenever routes were changed. You also could set different cache durations for different passed
argument values, but not different named parameters or query string parameters. Both of these
limitations/inconsistencies have been removed. You now use the controller's action names as the keys for
$cacheAction. This makes configuring $cacheAction easier as its no longer coupled to the routing, and
allows cacheAction to work with all custom routing. If you need to have custom cache durations for
specific argument sets you will need to detect and update cacheAction in your controller.
TimeHelper
TimeHelper has been refactored to make it more i18n friendly. Internally almost all calls to date() have
been replaced by strftime(). The new method TimeHelper::i18nFormat() has been added and will take
localization data from a LC_TIME locale definition file in app/locale following the POSIX standard. These are
the changes made in the TimeHelper API:
• TimeHelper::format() can now take a time string as first parameter and a format string as the
second one, the format must be using the strftime() style. When called with this parameter order
it will try to automatically convert the date format into the preferred one for the current locale. It
will also take parameters as in 1.2.x version to be backwards compatible, but in this case format
string must be compatible with date().
• TimeHelper::i18nFormat() has been added
Deprecated Helpers
Both the JavascriptHelper and the AjaxHelper are deprecated, and the JsHelper + HtmlHelper should be
used in their place.
• Model::find(first) will no longer use the id property for default conditions if no conditions are
supplied and id is not empty. Instead no conditions will be used
• For Model::saveAll() the default value for option 'validate' is now 'first' instead of true
Datasources
• DataSource::exists() has been refactored to be more consistent with non-database backed
datasources. Previously, if you set var $useTable = false; var $useDbConfig = 'custom';, it was
impossible for Model::exists() to return anything but false. This prevented custom datasources
from using create() orupdate() correctly without some ugly hacks. If you have custom datasources
that implement create(), update(), and read() (since Model::exists() will make a call to
Model::find('count'), which is passed to DataSource::read()), make sure to re-run your unit tests on
1.3.
Databases
Most database configurations no longer support the 'connect' key (which has been deprecated since pre-
1.2). Instead, set 'persistent' => true or false to determine whether or not a persistent database
connection should be used
SQL log dumping
A commonly asked question is how can one disable or remove the SQL log dump at the bottom of the
page?. In previous versions the HTML SQL log generation was buried inside DboSource. For 1.3 there is a
new core element called sql_dump. DboSource no longer automatically outputs SQL logs. If you want to
output SQL logs in 1.3, do the following:
Code View
<?php echo $this->element('sql_dump'); ?>
You can place this element anywhere in your layout or view. The sql_dump element will only generate
output when Configure::read('debug') is equal to 2. You can of course customize or override this element
in your app by creating app/views/elements/sql_dump.ctp.
11.1.3 Vendors, Test Suite & schema
• Edit
• Comments (0)
• History
vendors/css, vendors/js, and vendors/img
Support for these three directories, both in app/vendors as well as plugin/vendors has been removed.
They have been replaced with plugin and theme webroot directories.
Test Suite and Unit Tests
Group tests should now extend TestSuite instead of the deprecated GroupTest class. If your Group tests do
not run, you will need to update the base class.
Schema files used with the SchemaShell have been moved to app/config/schema instead of app/config/sql
Although config/sql will continue to work in 1.3, it will not in future versions, it is recommend that the new
path is used.
# Components
• Edit
• View just this section
• Comments (0)
• History
SecurityComponent
The various requireXX methods like requireGet and requirePost now accept a single array as their
argument as well as a collection of string names.
Code View
$this->Security->requirePost(array('edit', 'update'));
Component settings
Component settings for all core components can now be set from the $components array. Much like
behaviors, you can declare settings for components when you declare the component.
Code View
var $components = array(
'Cookie' => array(
'name' => 'MyCookie'
),
'Auth' => array(
'userModel' => 'MyUser',
'loginAction' => array('controller' => 'users', 'action' => 'login')
)
);
This should reduce clutter in your Controller's beforeFilter() methods.
EmailComponent
• You can now retrieve the rendered contents of sent Email messages, by reading $this->Email-
>htmlMessage and $this->Email->textMessage. These properties will contain the rendered email
content matching their name.
• Many of EmailComponent's private methods have been made protected for easier extension.
• EmailComponent::$to can now be an array. Allowing easier setting of multiple recipients, and
consistency with other properties.
• EmailComponent::$messageId has been added, it allows control over the Message-ID header for
email messages.
NumberHelper
A new method addFormat() has been added. This method allows you to set currency parameter sets, so
you don't have to retype them.
Code View
$this->Number->addFormat('NOK', array('before' => 'Kr. '));
$formatted = $this->Number->currency(1000, 'NOK');
FormHelper
The form helper has had a number of improvements and API modifications, see Form Helper
improvements for more information.
# Caching
• Edit
• View just this section
• Comments (0)
• History
Cache engines have been made more flexible in 1.3. You can now provide custom Cache adapters in
app/libs as well as in plugins using $plugin/libs. App/plugin cache engines can also override the core
engines. Cache adapters must be in a cache directory. If you had a cache engine named
MyCustomCacheEngine it would be placed in either app/libs/cache/my_custom_cache.php as an app/libs.
Or in $plugin/libs/cache/my_custom_cache.php as part of a plugin. Cache configs from plugins need to use
the plugin dot syntax.
Code View
Cache::config('custom', array(
'engine' => 'CachePack.MyCustomCacheEngine',
...
));
New Cache methods
Cache has a few new methods for 1.3 which make introspection and testing teardown easier.
tableParameters in Postgres
....
tableParameters in SQLite
....
# Console
• Edit
• View just this section
• Comments (0)
• History
Bake
Bake has had a number of significant changes made to it. Those changes are detailed in the bake updates
section
Subclassing
The ShellDispatcher has been modified to not require shells and tasks to have Shell as their immediate
parent anymore.
Output
Shell::nl() has been added. It returns a single or multiple linefeed sequences. Shell::out(), err() and hr()
now accept a $newlines parameter which is passed to nl() and allows for controlling how newlines are
appended to the output.
Shell::out() and Shell::err() have been modified, allowing a parameterless usage. This is especially useful
if you're often using $this->out('') for outputting just a single newline.
Acl Shell
All AclShell commands now take node parameters. node parameters can be either an alias path like
controllers/Posts/view or Model.foreign_key ie. User.1. You no longer need to know or use the aco/aro id for
commands.
The Acl shell dataSource switch has been removed. Use the Configure settings instead.
SchemaShell
The Schema shell can now read and write Schema files and SQL dumps to plugins. It expects and will
create schema files in $plugin/config/schema
....
# Router and Dispatcher
• Edit
• View just this section
• Comments (0)
• History
Router
Generating urls with new style prefixes works exactly the same as admin routing did in 1.2. They use the
same syntax and persist/behave in the same way. Assuming you have Configure::write('Routing.prefixes',
array('admin', 'member')); in your core.php you will be able to do the following from a non-prefixed url:
Code View
$this->Html->link('Go', array('controller' => 'posts', 'action' => 'index', 'member' => true));
$this->Html->link('Go', array('controller' => 'posts', 'action' => 'index', 'admin' => true));
Likewise, if you are in a prefixed url and want to go to a non-prefixed url, do the following:
Code View
$this->Html->link('Go', array('controller' => 'posts', 'action' => 'index', 'member' => false));
$this->Html->link('Go', array('controller' => 'posts', 'action' => 'index', 'admin' => false));
Route classes
For 1.3 the router has been internally rebuilt, and a new class CakeRoute has been created. This class
handles the parsing and reverse matching of an individual connected route. Also new in 1.3 is the ability
to create and use your own Route classes. You can implement any special routing features that may be
needed in application routing classes. Developer route classes must extend CakeRoute, if they do not an
error will be triggered. Commonly a custom route class will override theparse() and/or match() methods
found in CakeRoute to provide custom handling.
Dispatcher
• Accessing filtered asset paths, while having no defined asset filter will create 404 status code
responses.
# Library classes
• Edit
• View just this section
• Comments (0)
• History
Inflector
You can now globally customize the default transliteration map used in Inflector::slug using
Inflector::rules. eg. Inflector::rules('transliteration', array('/å/' => 'aa', '/ø/' => 'oe'))
The Inflector now also internally caches all data passed to it for inflection (except slug method).
Set
Set has a new method Set::apply(), which allows you to apply callbacks to the results of Set::extract and
do so in either a map or reduce fashion.
Code View
Set::apply('/Movie/rating', $data, 'array_sum');
Would return the sum of all Movie ratings in $data.
L10N
All languages in the catalog now have a direction key. This can be used to determine/define the text
direction of the locale being used.
File
• File now has a copy() method. It copies the file represented by the file instance, to a new location.
Configure
•Configure::load() can now load configuration files from plugins. Use Configure::load('plugin.file');
to load configuration files from plugins. Any configuration files in your application that use . in the
name should be updated to used _
App/libs
In addition to app/vendors a new app/libs directory has been added. This directory can also be part of
plugins, located at $plugin/libs. Libs directories are intended to contain 1st party libraries that do not
come from 3rd parties or external vendors. This allows you to separate your organization's internal
libraries from vendor libraries.App::import() has also been updated to import from libs directories.
Code View
App::import('Lib', 'ImageManipulation'); //imports app/libs/image_manipulation.php
You can also import libs files from plugins
Code View
App::import('Lib', 'Geocoding.Geocode'); //imports app/plugins/geocoding/libs/geocode.php
The remainder of lib importing syntax is identical to vendor files. So if you know how to import vendor
files with unique names, you know how to import libs files with unique names.
Configuration
• The default Security.level in 1.3 is medium instead of high
• There is a new configuration value Security.cipherSeed this value should be customized to ensure
more secure encrypted cookies, and a warning will be generated in development mode when the
value matches its default value.
i18n
Now you can use locale definition files for the LC_TIME category to retrieve date and time preferences for
a specific language. Just use any POSIX compliant locale definition file and store it at app/locale/language/
(do not create a folder for the category LC_TIME, just put the file in there).
For example, if you have access to a machine running debian or ubuntu you can find a french locale file
at: /usr/share/i18n/locales/fr_FR. Copy the part corresponding to LC_TIME into app/locale/fr_fr/LC_TIME file.
You can then access the time preferences for French language this way:
Code View
Configure::write('Config.language','fr-fr'); // set the current language
$monthNames = __c('mon',LC_TIME,true); // returns an array with the month names in French
$dateFormat = __c('d_fmt',LC_TIME,true); // return the preferred dates format for France
You can read a complete guide of possible values in LC_TIME definition file in this page
# Miscellaneous
• Edit
• View just this section
• Comments (0)
• History
Error Handling
Subclasses of ErrorHandler can more easily implement additional error methods. In the past you would
need to override __construct() and work around ErrorHandler's desire to convert all error methods into
error404 when debug = 0. In 1.3, error methods that are declared in subclasses are not converted to
error404. If you want your error methods converted into error404, then you will need to do it manually.
Scaffolding
With the addition of Routing.prefixes scaffolding has been updated to allow the scaffolding of any one
prefix.
Code View
Configure::write('Routing.prefixes', array('admin', 'member'));
Validation
After 1.2 was released, there were numerous requests to add additional localizations to the phone() and
postal() methods. Instead of trying to add every locale to Validation itself, which would result in large
bloated ugly methods, and still not afford the flexibility needed for all cases, an alternate path was taken.
In 1.3, phone() andpostal() will pass off any country prefix it does not know how to handle to another class
with the appropriate name. For example if you lived in the Netherlands you would create a class like
Code View
class NlValidation {
function phone($check) {
...
}
function postal($check) {
...
}
}
This file could be placed anywhere in your application, but must be imported before attempting to use it.
In your model validation you could use your NlValidation class by doing the following.
Code View
var $validate = array(
'phone_no' => array('rule' => array('phone', null, 'nl')),
'postal_code' => array('rule' => array('postal', null, 'nl'))
);
When your model data is validated, Validation will see that it cannot handle the 'nl' locale and will attempt
to delegate out to NlValidation::postal() and the return of that method will be used as the pass/fail for the
validation. This approach allows you to create classes that handle a subset or group of locales, something
that a large switch would not have. The usage of the individual validation methods has not changed, the
ability to pass off to another validator has been added.
IP Address Validation
Validation of IP Addresses has been extended to allow strict validation of a specific IP Version. It will also
make use of PHP native validation mechanisms if available.
Code View
Validation::ip($someAddress); // Validates both IPv4 and IPv6
Validation::ip($someAddress, 'IPv4'); // Validates IPv4 Addresses only
Validation::ip($someAddress, 'IPv6'); // Validates IPv6 Addresses only
Validation::uuid()
A uuid() pattern validation has been added to the Validation class. It will check that a given string
matches a uuid by pattern only. It does not ensure uniqueness of the given uuid.