CakePHPCookbook PDF
CakePHPCookbook PDF
CakePHPCookbook PDF
Release 2.x
Contents
Getting Started
Blog Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Blog Tutorial - Adding a layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Installation
Requirements . . . . . . . . . . . . . . .
License . . . . . . . . . . . . . . . . . .
Downloading CakePHP . . . . . . . . . .
Permissions . . . . . . . . . . . . . . . .
Setup . . . . . . . . . . . . . . . . . . .
Development . . . . . . . . . . . . . . .
Production . . . . . . . . . . . . . . . . .
Advanced Installation and URL Rewriting
Fire It Up . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1
1
9
29
29
29
30
30
30
31
32
32
40
CakePHP Overview
41
What is CakePHP? Why use it? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Understanding Model-View-Controller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Where to Get Help . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Controllers
The App Controller . . . . .
Request parameters . . . . .
Controller actions . . . . . .
Request Life-cycle callbacks
Controller Methods . . . . .
Controller Attributes . . . .
More on controllers . . . . .
Views
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
47
47
48
48
49
50
57
59
81
i
View Templates . . . . . . . .
Using view blocks . . . . . . .
Layouts . . . . . . . . . . . .
Elements . . . . . . . . . . . .
Creating your own view classes
View API . . . . . . . . . . .
More about Views . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
81
83
85
88
91
91
93
Models
201
Understanding Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
More on models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Core Libraries
General Purpose
Behaviors . . .
Components . .
Helpers . . . .
Utilities . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
335
335
496
524
575
672
Plugins
787
How To Install Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 787
How To Use Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 789
How To Create Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791
10 Development
Configuration . .
Routing . . . . .
Sessions . . . . .
Exceptions . . . .
Error Handling .
Debugging . . . .
Testing . . . . . .
REST . . . . . .
Dispatcher Filters
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
797
797
799
800
802
802
802
804
811
811
815
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
831
831
845
863
869
876
878
882
906
910
11 Deployment
915
Check your security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 915
ii
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
927
927
929
931
931
933
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
935
935
935
937
938
938
15 Contributing
Documentation . . . . . . . . .
Tickets . . . . . . . . . . . . . .
Code . . . . . . . . . . . . . . .
Coding Standards . . . . . . . .
Backwards Compatibility Guide
CakePHP Development Process
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
939
939
945
946
949
960
962
16 Appendices
2.6 Migration Guide . . .
2.5 Migration Guide . . .
2.4 Migration Guide . . .
2.3 Migration Guide . . .
2.2 Migration Guide . . .
2.1 Migration Guide . . .
2.0 Migration Guide . . .
Migration from 1.2 to 1.3
General Information . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
965
965
968
973
979
986
992
1003
1037
1057
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1059
Index
1061
iii
iv
CHAPTER 1
Getting Started
The CakePHP framework provides a robust base for your application. It can handle every aspect, from the
users initial request all the way to the final rendering of a web page. And since the framework follows the
principles of MVC, it allows you to easily customize and extend most aspects of your application.
The framework also provides a basic organizational structure, from filenames to database table names, keeping your entire application consistent and logical. This concept is simple but powerful. Follow the conventions and youll always know exactly where things are and how theyre organized.
The best way to experience and learn CakePHP is to sit down and build something. To start off well build
a simple blog application.
Blog Tutorial
Welcome to CakePHP. Youre probably checking out this tutorial because you want to learn more about how
CakePHP works. Its our aim to increase productivity and make coding more enjoyable: we hope youll see
this as you dive into the code.
This tutorial will walk you through the creation of a simple blog application. Well be getting and installing
CakePHP, creating and configuring a database, and creating enough application logic to list, add, edit, and
delete blog posts.
Heres what youll need:
1. A running web server. Were going to assume youre using Apache, though the instructions for using
other servers should be very similar. We might have to play a little with the server configuration, but
most folks can get CakePHP up and running without any configuration at all. Make sure you have
PHP 5.2.8 or greater.
2. A database server. Were going to be using MySQL server in this tutorial. Youll need to know enough
about SQL in order to create a database: CakePHP will be taking the reins from there. Since were
using MySQL, also make sure that you have pdo_mysql enabled in PHP.
3. Basic PHP knowledge. The more object-oriented programming youve done, the better: but fear not
if youre a procedural fan.
1
4. Finally, youll need a basic knowledge of the MVC programming pattern. A quick overview can be
found in Understanding Model-View-Controller. Dont worry, its only half a page or so.
Lets get started!
Getting CakePHP
First, lets get a copy of fresh CakePHP code.
To get a fresh download, visit the CakePHP project on GitHub: https://github.com/cakephp/cakephp/tags
and download the latest release of 2.0
You can also clone the repository using git1 . git clone git://github.com/cakephp/cakephp.git
Regardless of how you downloaded it, place the code inside of your DocumentRoot. Once finished, your
directory setup should look something like the following:
/path_to_document_root
/app
/lib
/plugins
/vendors
.htaccess
index.php
README
Now might be a good time to learn a bit about how CakePHPs directory structure works: check out the
CakePHP Folder Structure section.
Tmp directory permissions
Next well need to make the app/tmp directory writable by the webserver. The best way to do this is to
find out what user your webserver runs as. You can run <?php echo exec(whoami); ?> inside
any PHP file your webserver can execute. You should see a username printed. Change the ownership of the
app/tmp directory to that user. The final command you run (in *nix) might look something like this:
$ chown -R www-data app/tmp
If for some reason CakePHP cant write to that directory, youll see warnings and uncaught exceptions that
cache data cannot be written.
http://git-scm.com/
The choices on table and column names are not arbitrary. If you follow CakePHPs database naming conventions, and CakePHPs class naming conventions (both outlined in CakePHP Conventions), youll be able
to take advantage of a lot of free functionality and avoid configuration. CakePHP is flexible enough to
accommodate even the worst legacy database schema, but adhering to convention will save you time.
Check out CakePHP Conventions for more information, but 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 CakePHP.
Once youve saved your new database.php file, you should be able to open your browser and see the
Blog Tutorial
CakePHP welcome page. It should also tell you that your database connection file was found, and that
CakePHP can successfully connect to the database.
Note: Remember that youll need to have PDO, and pdo_mysql enabled in your php.ini.
Optional Configuration
There are a few other items that can be configured. Most developers complete these laundry-list items, but
theyre not required for this tutorial. One is defining a custom string (or salt) for use in security hashes.
The second is defining a custom number (or seed) for use in encryption.
The security salt is used for generating hashes. Change the default Security.salt value in
/app/Config/core.php. The replacement value should be long, hard to guess and be as random
as you can make it:
/**
* A random string used in security hashing methods.
*/
Configure::write(Security.salt, pl345e-P45s_7h3*S@l7!);
The cipher seed is used for encrypt/decrypt strings. Change the default Security.cipherSeed value
by editing /app/Config/core.php. The replacement value should be a large random integer:
/**
* A random numeric string (digits only) used to encrypt/decrypt strings.
*/
Configure::write(Security.cipherSeed, 7485712659625147843639846751);
A Note on mod_rewrite
Occasionally new users will run into mod_rewrite issues. For example if the CakePHP welcome page looks
a little funny (no images or CSS styles), it probably means mod_rewrite is not functioning on your system.
Please refer to one of the sections below about URL rewriting for your webserver to get you up and running:
URL Rewriting
Apache and mod_rewrite (and .htaccess)
While CakePHP is built to work with mod_rewrite out of the boxand usually doesweve noticed that a
few users struggle with getting everything to play nicely on their systems.
Here are a few things you might try to get it running correctly. First look at your httpd.conf. (Make sure you
are editing the system httpd.conf rather than a user- or site-specific httpd.conf.)
These files can vary between different distributions and Apache versions. You may also take a look at
http://wiki.apache.org/httpd/DistrosDefaultLayout for further information.
1. Make sure that an .htaccess override is allowed and that AllowOverride is set to All for the correct
DocumentRoot. You should see something similar to:
# Each directory to which Apache has access can be configured with respect
# to which services and features are allowed and/or disabled in that
# directory (and its subdirectories).
#
# First, we configure the "default" to be a very restrictive set of
# features.
#
<Directory />
Options FollowSymLinks
AllowOverride All
#
Order deny,allow
#
Deny from all
</Directory>
2. Make sure you are loading mod_rewrite correctly. You should see something like:
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
In many systems these will be commented out by default, so you may just need to remove the leading
# symbols.
After you make changes, restart Apache to make sure the settings are active.
Verify that your .htaccess files are actually in the right directories. Some operating systems treat files
that start with . as hidden and therefore wont copy them.
3. Make sure your copy of CakePHP comes from the downloads section of the site or our Git repository,
and has been unpacked correctly, by checking for .htaccess files.
CakePHP root directory (must be copied to your document; redirects everything to your CakePHP
app):
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule
^ $ app/webroot/
RewriteRule
</IfModule>
[L]
CakePHP app directory (will be copied to the top directory of your application by bake):
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule
^$
webroot/
RewriteRule
</IfModule>
(.*) webroot/ $ 1
[L]
[L]
CakePHP webroot directory (will be copied to your applications web root by bake):
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
Blog Tutorial
If your CakePHP site still has problems with mod_rewrite, you might want to try modifying settings
for Virtual Hosts. On Ubuntu, edit the file /etc/apache2/sites-available/default (location is distributiondependent). In this file, ensure that AllowOverride None is changed to AllowOverride
All, so you have:
<Directory />
Options FollowSymLinks
AllowOverride All
</Directory>
<Directory /var/www>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order Allow,Deny
Allow from all
</Directory>
On Mac OSX, another solution is to use the tool virtualhostx2 to make a Virtual Host to point to your
folder.
For many hosting services (GoDaddy, 1and1), your web server is actually being served from a
user directory that already uses mod_rewrite. If you are installing CakePHP into a user directory (http://example.com/~username/cakephp/), or any other URL structure that already utilizes
mod_rewrite, youll need to add RewriteBase statements to the .htaccess files CakePHP uses (/.htaccess, /app/.htaccess, /app/webroot/.htaccess).
This can be added to the same section with the RewriteEngine directive, so for example, your webroot
.htaccess file would look like:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /path/to/cake/app
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*) $ index.php [QSA,L]
</IfModule>
The details of those changes will depend on your setup, and can include additional things that are not
related to CakePHP. Please refer to Apaches online documentation for more information.
4. (Optional) To improve production setup, you should prevent invalid assets from being parsed by
CakePHP. Modify your webroot .htaccess to something like:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /path/to/cake/app
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^/(app/webroot/)?(img|css|js)/(.*) $
2
http://clickontyler.com/virtualhostx/
The above will simply prevent incorrect assets from being sent to index.php and instead display your
webservers 404 page.
Additionally you can create a matching HTML 404 page, or use the default built-in CakePHP 404 by
adding an ErrorDocument directive:
ErrorDocument 404 /404-not-found
nginx does not make use of .htaccess files like Apache, so it is necessary to create those rewritten URLs in
the site-available configuration. Depending upon your setup, you will have to modify this, but at the very
least, you will need PHP running as a FastCGI instance.
server {
listen
80;
server_name www.example.com;
rewrite ^(.*) http://example.com $ 1 permanent;
}
server {
listen
80;
server_name example.com;
# root directive should be global
root
/var/www/example.com/public/app/webroot/;
index index.php;
access_log /var/www/example.com/log/access.log;
error_log /var/www/example.com/log/error.log;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php $ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_pass
127.0.0.1:9000;
fastcgi_index
index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Blog Tutorial
IIS7 does not natively support .htaccess files. While there are add-ons that can add this support, you can
also import htaccess rules into IIS to use CakePHPs native rewrites. To do this, follow these steps:
1. Use Microsofts Web Platform Installer3 to install the URL Rewrite Module 2.04 or download it
directly (32-bit5 / 64-bit6 ).
2. Create a new file called web.config in your CakePHP root folder.
3. Using Notepad or any XML-safe editor, copy the following code into your new web.config file...
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Rewrite requests to test.php"
stopProcessing="true">
<match url="^test.php(.*)$" ignoreCase="false" />
<action type="Rewrite" url="app/webroot/test.php{R:1}" />
</rule>
<rule name="Exclude direct access to app/webroot/*"
stopProcessing="true">
<match url="^app/webroot/(.*)$" ignoreCase="false" />
<action type="None" />
</rule>
<rule name="Rewrite routed access to assets(img, css, files, js, favicon)"
stopProcessing="true">
<match url="^(img|css|files|js|favicon.ico)(.*)$" />
<action type="Rewrite" url="app/webroot/{R:1}{R:2}"
appendQueryString="false" />
</rule>
<rule name="Rewrite requested file/folder to index.php"
stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<action type="Rewrite" url="index.php"
appendQueryString="true" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Once the web.config file is created with the correct IIS-friendly rewrite rules, CakePHPs links, CSS, JavaScipt, and rerouting should work correctly.
3
http://www.microsoft.com/web/downloads/platform.aspx
http://www.iis.net/downloads/microsoft/url-rewrite
5
http://www.microsoft.com/en-us/download/details.aspx?id=5747
6
http://www.microsoft.com/en-us/download/details.aspx?id=7435
4
URL-Rewriting on lighttpd
Lighttpd does not support .htaccess functions, so you can remove all .htaccess files. In the lighttpd configuration, make sure youve activated mod_rewrite. Add a line:
url.rewrite-if-not-file =(
"^([^\?]*)(\?(.+))?$" => "/index.php?url=$1&$3"
)
The required UrlToolkit rule (for URL rewriting) to use CakePHP with Hiawatha is:
UrlToolkit {
ToolkitID = cakephp
RequestURI exists Return
Match .* Rewrite /index.php
}
If you dont want to or cant use URL rewriting on your webserver, refer to the core configuration.
Now continue to /tutorials-and-examples/blog/part-two to start building your first
CakePHP application.
Naming conventions are very important in CakePHP. By naming our model Post, CakePHP can automatically infer that this model will be used in the PostsController, and will be tied to a database table called
posts.
Note: CakePHP will dynamically create a model object for you if it cannot find a corresponding file
in /app/Model. This also means that if you accidentally name your file wrong (for example, post.php or
posts.php instead of Post.php), CakePHP will not recognize any of your settings and will use the defaults
instead.
For more on models, such as table prefixes, callbacks, and validation, check out the Models chapter of the
Manual.
Now, lets add an action to our controller. Actions often represent a single function or interface in
an application. For example, when users request www.example.com/posts/index (which is the same as
www.example.com/posts/), they might expect to see a listing of posts. The code for that action would look
something like this:
class PostsController extends AppController {
public $helpers = array(Html, Form);
public function index() {
$this->set(posts, $this->Post->find(all));
}
}
By defining function index() in our PostsController, users can access the logic there by requesting
www.example.com/posts/index. Similarly, if we were to define a function called foobar(), users would
be able to access that at www.example.com/posts/foobar.
Warning: You may be tempted to name your controllers and actions a certain way to obtain a certain
URL. Resist that temptation. Follow CakePHP conventions (capitalization, plural names, etc.) and create
readable, understandable action names. You can map URLs to your code using routes covered later
on.
The single instruction in the action uses set() to pass data from the controller to the view (which well
create next). The line sets the view variable called posts equal to the return value of the find(all)
method of the Post model. Our Post model is automatically available at $this->Post because weve
followed CakePHPs naming conventions.
To learn more about CakePHPs controllers, check out the Controllers chapter.
10
CakePHPs view files are stored in /app/View inside a folder named after the controller to which they
Blog Tutorial - Adding a layer
11
correspond. (Well have to create a folder named Posts in this case.) To format this post data into a nice
table, our view code might look something like this
<!-- File: /app/View/Posts/index.ctp -->
<h1>Blog posts</h1>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
</tr>
<!-- Here is where we loop through our $posts array, printing out post info -->
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post[Post][id]; ?></td>
<td>
<?php echo $this->Html->link($post[Post][title],
array(controller => posts, action => view, $post[Post][id])); ?>
</td>
<td><?php echo $post[Post][created]; ?></td>
</tr>
<?php endforeach; ?>
<?php unset($post); ?>
</table>
You might have noticed the use of an object called $this->Html. This is an instance of the CakePHP
HtmlHelper class. CakePHP comes with a set of view helpers that make things like linking, form output,
JavaScript and AJAX a snap. You can learn more about how to use them in Helpers, but whats important to
note here is that the link() method will generate an HTML link with the given title (the first parameter)
and URL (the second parameter).
When specifying URLs in CakePHP, it is recommended that you use the array format. This is explained
in more detail in the section on Routes. Using the array format for URLs allows you to take advantage of
CakePHPs reverse routing capabilities. You can also specify URLs relative to the base of the application in
the form of /controller/action/param1/param2.
At this point, you should be able to point your browser to http://www.example.com/posts/index. You should
see your view, correctly formatted with the title and table listing of the posts.
If you happened to have clicked on one of the links we created in this view (which link a posts title to a URL
/posts/view/some_id), you were probably informed by CakePHP that the action hadnt 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, well create it in the PostsController now:
// File: /app/Controller/PostsController.php
class PostsController extends AppController {
public $helpers = array(Html, Form);
public function index() {
$this->set(posts, $this->Post->find(all));
12
}
public function view($id = null) {
if (!$id) {
throw new NotFoundException(__(Invalid post));
}
$post = $this->Post->findById($id);
if (!$post) {
throw new NotFoundException(__(Invalid post));
}
$this->set(post, $post);
}
}
The set() call should look familiar. Notice were using findById() rather than find(all) because we only want a single posts information.
Notice that our view action takes a parameter: the ID of the post wed like to see. This parameter is handed
to the action through the requested URL. If a user requests /posts/view/3, then the value 3 is passed
as $id.
We also do a bit of error checking to ensure that a user is actually accessing a record. If a user requests
/posts/view, we will throw a NotFoundException and let the CakePHP ErrorHandler take over.
We also perform a similar check to make sure the user has accessed a record that exists.
Now lets create the view for our new view action and place it in /app/View/Posts/view.ctp
<!-- File: /app/View/Posts/view.ctp -->
<h1><?php echo h($post[Post][title]); ?></h1>
<p><small>Created: <?php echo $post[Post][created]; ?></small></p>
<p><?php echo h($post[Post][body]); ?></p>
Verify that this is working by trying the links at /posts/index or manually requesting a post by accessing
/posts/view/1.
Adding Posts
Reading from the database and showing us the posts is a great start, but lets allow for adding new posts.
First, start by creating an add() action in the PostsController:
class PostsController extends AppController {
public $helpers = array(Html, Form, Session);
public $components = array(Session);
public function index() {
$this->set(posts, $this->Post->find(all));
}
13
Note: $this->request->is() takes a single argument, which can be the request METHOD (get,
put, post, delete) or some request identifier (ajax). It is not a way to check for specific posted data.
For instance, $this->request->is(book) will not return true if book data was posted.
Note: You need to include the SessionComponent - and SessionHelper - in any controller where you will
use it. If necessary, include it in your AppController.
Heres what the add() action does: if the HTTP method of the request was POST, it tries to save the data
using the Post model. If for some reason it doesnt save, it just renders the view. This gives us a chance to
show the user validation errors or other warnings.
Every CakePHP request includes a CakeRequest object which is accessible using $this->request.
The request object contains useful information regarding the request that was just received, and can be used
to control the flow of your application. In this case, we use the CakeRequest::is() method to check
that the request is a HTTP POST request.
When a user uses a form to POST data to your application, that information is available in
$this->request->data. You can use the pr() or debug() functions to print it out if you want
to see what it looks like.
We use the SessionComponents SessionComponent::setFlash() method to set a message
to a session variable to be displayed on the page after redirection.
In the layout we have
SessionHelper::flash which displays the message and clears the corresponding session variable. The controllers Controller::redirect function redirects to another URL. The param
array(action => index) translates to URL /posts (that is, the index action of the posts con-
14
troller). You can refer to Router::url() function on the API7 to see the formats in which you can
specify a URL for various CakePHP functions.
Calling the save() method will check for validation errors and abort the save if any occur. Well discuss
how those errors are handled in the following sections.
We call the create() method first in order to reset the model state for saving new information. It does not
actually create a record in the database, but clears Model::$id and sets Model::$data based on your database
field defaults.
Data Validation
CakePHP goes a long way toward taking the monotony out of form input validation. Everyone hates coding
up endless forms and their validation routines. CakePHP makes it easier and faster.
To take advantage of the validation features, youll need to use CakePHPs FormHelper in your views. The
FormHelper is available by default to all views at $this->Form.
Heres our add view:
<!-- File: /app/View/Posts/add.ctp -->
<h1>Add Post</h1>
<?php
echo $this->Form->create(Post);
echo $this->Form->input(title);
echo $this->Form->input(body, array(rows => 3));
echo $this->Form->end(Save Post);
?>
We use the FormHelper to generate the opening tag for an HTML form.
$this->Form->create() generates:
If create() is called with no parameters supplied, it assumes you are building a form that submits via
POST to the current controllers add() action (or edit() action when id is included in the form data).
The $this->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. Theres a bit of introspection and
automagic here: input() will output different form elements based on the model field specified.
The $this->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 Helpers for more on helpers.
Now lets go back and update our /app/View/Posts/index.ctp view to include a new Add Post
link. Before the <table>, add the following line:
7
http://api.cakephp.org
15
You may be wondering: how do I tell CakePHP about my validation requirements? Validation rules are
defined in the model. Lets look back at our Post model and make a few adjustments:
class Post extends AppModel {
public $validate = array(
title => array(
rule => notEmpty
),
body => array(
rule => notEmpty
)
);
}
The $validate array tells CakePHP how to validate your data when the save() method is called. Here,
Ive specified that both the body and title fields must not be empty. CakePHPs validation engine is strong,
with a number of pre-built rules (credit card numbers, email addresses, etc.) and flexibility for adding your
own validation rules. For more information, check the Data Validation.
Now that you have your validation rules in place, use the app to try to add a post with an empty title or body
to see how it works. Since weve used the FormHelper::input() method of the FormHelper to create
our form elements, our validation error messages will be shown automatically.
Editing Posts
Post editing: here we go. Youre a CakePHP pro by now, so you should have picked up a pattern. Make the
action, then the view. Heres what the edit() action of the PostsController would look like:
public function edit($id = null) {
if (!$id) {
throw new NotFoundException(__(Invalid post));
}
$post = $this->Post->findById($id);
if (!$post) {
throw new NotFoundException(__(Invalid post));
}
if ($this->request->is(array(post, put))) {
$this->Post->id = $id;
if ($this->Post->save($this->request->data)) {
$this->Session->setFlash(__(Your post has been updated.));
return $this->redirect(array(action => index));
}
$this->Session->setFlash(__(Unable to update your post.));
}
16
if (!$this->request->data) {
$this->request->data = $post;
}
}
This action first ensures that the user has tried to access an existing record. If they havent passed in an $id
parameter, or the post does not exist, we throw a NotFoundException for the CakePHP ErrorHandler
to take care of.
Next the action checks whether the request is either a POST or a PUT request. If it is, then we use the POST
data to update our Post record, or kick back and show the user validation errors.
If there is no data set to $this->request->data, we simply set it to the previously retrieved post.
The edit view might look something like this:
<!-- File: /app/View/Posts/edit.ctp -->
<h1>Edit Post</h1>
<?php
echo $this->Form->create(Post);
echo $this->Form->input(title);
echo $this->Form->input(body, array(rows => 3));
echo $this->Form->input(id, array(type => hidden));
echo $this->Form->end(Save Post);
?>
This view outputs the edit form (with the values populated), along with any necessary validation error
messages.
One thing to note here: CakePHP will assume that you are editing a model if the id field is present in the
data array. If no id is present (look back at our add view), CakePHP 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:
<!-- File: /app/View/Posts/index.ctp
<h1>Blog posts</h1>
<p><?php echo $this->Html->link("Add Post", array(action => add)); ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Action</th>
<th>Created</th>
</tr>
<!-- Heres where we loop through our $posts array, printing out post info -->
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post[Post][id]; ?></td>
<td>
17
<?php
echo $this->Html->link(
$post[Post][title],
array(action => view, $post[Post][id])
);
?>
</td>
<td>
<?php
echo $this->Html->link(
Edit,
array(action => edit, $post[Post][id])
);
?>
</td>
<td>
<?php echo $post[Post][created]; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
Deleting Posts
Next, lets make a way for users to delete posts. Start with a delete() action in the PostsController:
public function delete($id) {
if ($this->request->is(get)) {
throw new MethodNotAllowedException();
}
if ($this->Post->delete($id)) {
$this->Session->setFlash(
__(The post with id: %s has been deleted., h($id))
);
} else {
$this->Session->setFlash(
__(The post with id: %s could not be deleted., h($id))
);
}
return $this->redirect(array(action => index));
}
This logic deletes the post specified by $id, and uses $this->Session->setFlash() to show the
user a confirmation message after redirecting them on to /posts. If the user attempts to do a delete using a
GET request, we throw an Exception. Uncaught exceptions are captured by CakePHPs exception handler,
and a nice error page is displayed. There are many built-in Exceptions that can be used to indicate the
various HTTP errors your application might need to generate.
Because were just executing some logic and redirecting, this action has no view. You might want to update
18
your index view with links that allow users to delete posts, however:
<!-- File: /app/View/Posts/index.ctp -->
<h1>Blog posts</h1>
<p><?php echo $this->Html->link(Add Post, array(action => add)); ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Actions</th>
<th>Created</th>
</tr>
<!-- Heres where we loop through our $posts array, printing out post info -->
<?php foreach ($posts as $post): ?>
<tr>
<td><?php echo $post[Post][id]; ?></td>
<td>
<?php
echo $this->Html->link(
$post[Post][title],
array(action => view, $post[Post][id])
);
?>
</td>
<td>
<?php
echo $this->Form->postLink(
Delete,
array(action => delete, $post[Post][id]),
array(confirm => Are you sure?)
);
?>
<?php
echo $this->Html->link(
Edit, array(action => edit, $post[Post][id])
);
?>
</td>
<td>
<?php echo $post[Post][created]; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
Using postLink() will create a link that uses JavaScript to do a POST request to delete our post. Allowing content to be deleted using GET requests is dangerous, as web crawlers could accidentally delete all
your content.
Note: This view code also uses the FormHelper to prompt the user with a JavaScript confirmation dialog
Blog Tutorial - Adding a layer
19
Routes
For some, CakePHPs default routing works well enough. Developers who are sensitive to user-friendliness
and general search engine compatibility will appreciate the way that CakePHPs URLs map to specific
actions. So well just make a quick change to routes in this tutorial.
For more information on advanced routing techniques, see Routes Configuration.
By default, CakePHP responds to a request for the root of your site (e.g., http://www.example.com) using
its PagesController, rendering a view called home. Instead, well replace this with our PostsController by
creating a routing rule.
CakePHPs routing is found in /app/Config/routes.php. Youll want to comment out or remove the
line that defines the default root route. It looks like this:
Router::connect(
/,
array(controller => pages, action => display, home)
);
This line connects the URL / with the default CakePHP home page. We want it to connect with our own
controller, so replace that line with this one:
Router::connect(/, array(controller => posts, action => index));
This should connect users requesting / to the index() action of our PostsController.
Note:
CakePHP also makes use of reverse routing. If, with the above route defined, you pass
array(controller => posts, action => index) to a function expecting an array, the resulting URL used will be /. Its 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.
Conclusion
Creating applications this way will win you peace, honor, love, and money beyond even your wildest fantasies. Simple, isnt it? Keep in mind that this tutorial was very basic. CakePHP has many more features to
offer, and is flexible in ways we didnt wish to cover here for simplicitys sake. Use the rest of this manual
as a guide for building more feature-rich applications.
Now that youve created a basic CakePHP application, youre ready for the real thing. Start your own project
and read the rest of the Cookbook and API8 .
If you need help, there are many ways to get the help you need - please see the Where to Get Help page.
Welcome to CakePHP!
8
20
http://api.cakephp.org
Additional Reading
A Typical CakePHP Request
Weve covered the basic ingredients in CakePHP, so lets look at how objects work together to complete
a basic request. Continuing with our original request example, lets imagine that our friend Ricardo just
clicked on the Buy A Custom Cake Now! link on a CakePHP applications landing page.
21
Controller class names are plural, CamelCased, and end in Controller. PeopleController and
LatestArticlesController are both examples of conventional controller names.
The first method you write for a controller might be the index() method. When a request specifies a
controller but not an action, the default CakePHP behavior is to execute the index() method of that
controller. For example, a request for http://www.example.com/apples/ maps to a call on the index()
method of the ApplesController, whereas http://www.example.com/apples/view/ maps to a call on
the view() method of the ApplesController.
22
You can also change the visibility of controller methods in CakePHP by prefixing controller method names
with underscores. If a controller method has been prefixed with an underscore, the method will not be
accessible directly from the web but is available for internal use. For example:
class NewsController extends AppController {
public function latest() {
$this->_findNewArticles();
}
protected function _findNewArticles() {
// Logic to find latest news articles
}
}
While the page http://www.example.com/news/latest/ would be accessible to the user as usual, someone
trying to get to the page http://www.example.com/news/_findNewArticles/ would get an error, because the
method is preceded with an underscore. You can also use PHPs visibility keywords to indicate whether or
not a method can be accessed from a URL. Non-public methods cannot be accessed.
URL Considerations for Controller Names As youve just seen, single word controllers map easily to
a simple lower case URL path. For example, ApplesController (which would be defined in the file
name ApplesController.php) is accessed from http://example.com/apples.
Multiple word controllers can be any inflected form which equals the controller name so:
/redApples
/RedApples
/Red_apples
/red_apples
will all resolve to the index of the RedApples controller. However, the convention is that your
URLs are lowercase and underscored, therefore /red_apples/go_pick is the correct form to access the
RedApplesController::go_pick action.
For more information on CakePHP URLs and parameter handling, see Routes Configuration. If you have
files/directories in your /webroot directory that share a name with one of your routes/controllers, you will
be directed to the file/directory and, not to your controller.
File and Class Name Conventions
In general, filenames match the class names, which are CamelCased. So if you have a class MyNiftyClass,
then in CakePHP, the file should be named MyNiftyClass.php. Below are examples of how to name the file
for each of the different types of classes you would typically use in a CakePHP application:
The Controller class KissesAndHugsController would be found in a file named KissesAndHugsController.php
The Component class MyHandyComponent would be found in a file named MyHandyComponent.php
Blog Tutorial - Adding a layer
23
Model class names are singular and CamelCased. Person, BigPerson, and ReallyBigPerson are all examples
of conventional model names.
Table names corresponding to CakePHP models are plural and underscored. The underlying tables for the
above mentioned models would be people, big_people, and really_big_people, respectively.
You can use the utility library Inflector to check the singular/plural of words. See the Inflector for more
information.
Field names with two or more words are underscored: 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 table like category_types whose name contains multiple words,
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, arranged 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 that does not already have a single-field
primary key, CakePHPs convention is that a single-field primary key is added to the table. You must add a
single-field primary key if you want to use that tables model.
CakePHP does not support composite primary keys. If you want to directly manipulate your join table data,
use direct query calls or add a primary key to act on it as a normal model. For example:
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). CakePHP will
then use a unique 36 character UUID (String::uuid) whenever you save a new record using the Model::save
method.
24
View Conventions
View template files are named after the controller functions they display, in an underscored
form. The getReady() function of the PeopleController class will look for a view template in
/app/View/People/get_ready.ctp.
The basic pattern is /app/View/Controller/underscored_function_name.ctp.
By naming the pieces of your application using CakePHP conventions, you gain functionality without the
hassle and maintenance tethers of configuration. Heres a final example that ties the conventions together:
Database table: people
Model class: Person, found at /app/Model/Person.php
Controller class: PeopleController, found at /app/Controller/PeopleController.php
View template, found at /app/View/People/index.ctp
Using these conventions, CakePHP knows that a request to http://example.com/people/ maps to a call on the
index() function of the PeopleController, where the Person model is automatically available (and automatically tied to the people table in the database), and renders to a file. None of these relationships have been
configured by any means other than by creating classes and files that youd need to create anyway.
Now that youve been introduced to CakePHPs fundamentals, you might try a run through the
/tutorials-and-examples/blog/blog to see how things fit together.
CakePHP Folder Structure
After youve downloaded and extracted CakePHP, these are the files and folders you should see:
app
lib
vendors
plugins
.htaccess
index.php
README
Youll notice three main folders:
The app folder will be where you work your magic: its where your applications files will be placed.
The lib folder is where weve worked our magic. Make a personal commitment not to edit files in
this folder. We cant help you if youve modified the core. Instead, look into modifying Application
Extensions.
Finally, the vendors folder is where youll place third-party PHP libraries you need to use with your
CakePHP applications.
25
CakePHPs app folder is where you will do most of your application development. Lets look a little closer
at the folders inside app.
Config Holds the (few) configuration files CakePHP uses. Database connection details, bootstrapping, core
configuration files and more should be stored here.
Console Contains the console commands and console tasks for your application. This directory can also
contain a Templates directory to customize the output of bake. For more information see Console
and Shells.
Controller Contains your applications controllers and their components.
Lib Contains libraries that do not come from 3rd parties or external vendors. This allows you to separate
your organizations internal libraries from vendor libraries.
Locale Stores string files for internationalization.
Model Contains your applications models, behaviors, and datasources.
Plugin Contains plugin packages.
Test This directory contains all the test cases and test fixtures for your application. The Test/Case directory should mirror your application and contain one or more test cases per class in your application.
For more information on test cases and test fixtures, refer to the Testing documentation.
tmp This is where CakePHP stores temporary data. The actual data it stores depends on how you have
CakePHP configured, but this folder is usually used to store model descriptions, logs, and sometimes
session information.
Make sure that this folder exists and is writable, or the performance of your application will be
severely impacted. In debug mode, CakePHP will warn you if the folder is absent or not writable.
Vendor Any third-party classes or libraries should be placed here. Doing so makes them easy to access
using the App::import(vendor, name) function. Keen observers will note that this seems redundant,
as there is also a vendors folder at the top level of our directory structure. Well get into the differences
between the two when we discuss managing multiple applications and more complex system setups.
View Presentational files are placed here: elements, error pages, helpers, layouts, and view files.
webroot In a production setup, this folder should serve as the document root for your application. Folders
here also serve as holding places for CSS stylesheets, images, and JavaScript files.
CakePHP Structure
CakePHP features Controller, Model, and View classes, but it also features some additional classes and
objects that make development in MVC a little quicker and more enjoyable. Components, Behaviors, and
Helpers are classes that provide extensibility and reusability to quickly add functionality to the base MVC
classes in your applications. Right now well stay at a higher level, so look for the details on how to use
these tools later on.
26
Application Extensions
Controllers, helpers and models each have a parent class you can use to define applicationwide changes.
AppController (located at /app/Controller/AppController.php), AppHelper (located at /app/View/Helper/AppHelper.php) and AppModel (located at
/app/Model/AppModel.php) are great places to put methods you want to share between all
controllers, helpers or models.
Although routes arent classes or files, they 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.
Controller Extensions (Components)
A Component is a class that aids in controller logic. If you have some logic you want to share between
controllers (or applications), a component is usually a good fit. As an example, the core EmailComponent
class makes creating and sending emails a snap. Rather than writing a controller method in a single controller
that performs this logic, you can package the logic so it can be shared.
Controllers are also fitted with callbacks. These callbacks are available for your use, just in case you need
to insert some logic between CakePHPs core operations. Callbacks available include:
afterFilter(), executed after all controller logic, including the rendering of the view
beforeFilter(), executed before any controller action logic
beforeRender(), executed after controller logic, but before the view is rendered
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 are also 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 with many iCal events.
Like controllers, models have callbacks:
beforeFind()
afterFind()
Blog Tutorial - Adding a layer
27
beforeValidate()
afterValidate()
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.
View Extensions (Helpers)
A Helper is a class that aids in view logic. Much like a component used among controllers, helpers allow presentational logic to be accessed and shared between views. One of the core helpers, JsHelper, makes AJAX
requests within views much easier and comes with support for jQuery (default), Prototype and Mootools.
Most applications have pieces of view code that are used repeatedly. CakePHP facilitates view code reuse
with layouts and elements. By default, every view rendered by a controller is placed inside a layout. Elements are used when small snippets of content need to be reused in multiple views.
28
CHAPTER 2
Installation
CakePHP is fast and easy to install. The minimum requirements are a webserver and a copy of CakePHP,
thats it! While this manual focuses primarily on setting up on Apache (because its the most commonly
used), you can configure CakePHP to run on a variety of web servers such as LightHTTPD or Microsoft IIS.
Requirements
HTTP Server. For example: Apache. mod_rewrite is preferred, but by no means required.
PHP 5.2.8 or greater.
Technically a database engine isnt required, but we imagine that most applications will utilize one.
CakePHP supports a variety of database storage engines:
MySQL (4 or greater)
PostgreSQL
Microsoft SQL Server
SQLite
Note: All built-in drivers require PDO. You should make sure you have the correct PDO extensions installed.
License
CakePHP is licensed under the MIT license. This means that you are free to modify, distribute and republish
the source code on the condition that the copyright notices are left intact. You are also free to incorporate
CakePHP into any commercial or closed source application.
29
Downloading CakePHP
There are two main ways to get a fresh copy of CakePHP. You can either download an archived copy
(zip/tar.gz/tar.bz2) from the main website, or check out the code from the git repository.
To download the latest major release of CakePHP, visit the main website http://cakephp.org and follow the
Download link.
All current releases of CakePHP are hosted on GitHub1 . GitHub houses both CakePHP itself as well as
many other plugins for CakePHP. The CakePHP releases are available at GitHub tags2 .
Alternatively you can get fresh off the press code, with all the bug-fixes and up to the minute enhancements.
These can be accessed from GitHub by cloning the GitHub3 repository:
git clone git://github.com/cakephp/cakephp.git
Permissions
CakePHP uses the app/tmp directory for a number of different operations. A few examples would be
Model descriptions, cached views and session information.
As such, make sure the directory app/tmp and all its subdirectories in your CakePHP installation are
writable by the web server user.
One common issue is that the app/tmp directories and subdirectories must be writable both by the web server
and the command line user. On a UNIX system, if your web server user is different from your command
line user, you can run the following commands just once in your project to ensure that permissions will be
setup properly:
Setup
Setting up CakePHP can be as simple as slapping it in your web servers 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 servers document root, clean URLs, very secure.
1
http://github.com/cakephp/cakephp
https://github.com/cakephp/cakephp/tags
3
http://github.com/cakephp/cakephp
2
30
Chapter 2. Installation
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.
Development
A development installation is the fastest method to setup CakePHP. This example will help you install
a CakePHP application and make it available at http://www.example.com/cake_2_0/. We assume for the
purposes of this example that your document root is set to /var/www/html.
Unpack the contents of the CakePHP archive into /var/www/html. You now have a folder in your document root named after the release youve downloaded (e.g. cake_2.0.0). Rename this folder to cake_2_0.
Your development setup will look like this on the file system:
/var/www/html/
cake_2_0/
app/
lib/
plugins/
vendors/
.htaccess
index.php
README
If your web server is configured correctly, you should now find your CakePHP application accessible at
http://www.example.com/cake_2_0/.
This will clone CakePHP into your /home/mark/projects directory. If you dont want to use git, you
can download a zipball and the remaining steps will be the same. Next youll have to locate and modify
your php.ini. On *nix systems this is often in /etc/php.ini, but using php -i and looking for
Loaded Configuration File, you can find the actual location. Once youve found the correct ini file, modify
the include_path configuration to include /home/mark/projects/cakephp/lib. An example
would look like:
include_path = .:/home/mark/projects/cakephp/lib:/usr/local/php/lib/php
After restarting your webserver, you should see the changes reflected in phpinfo().
Note: If you are on windows, separate include paths with ; instead of :
Development
31
Having finished setting up your include_path your applications should be able to find CakePHP automatically.
Production
A production installation is a more flexible way to setup CakePHP. Using this method allows an entire
domain to act as a single CakePHP application. This example will help you install CakePHP anywhere on
your filesystem and make it available at http://www.example.com. Note that this installation may require
the rights to change the DocumentRoot on Apache webservers.
Unpack the contents of the CakePHP archive into a directory of your choice. For the purposes of this
example, we assume you chose to install CakePHP 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)
lib/
plugins/
vendors/
.htaccess
index.php
README
Developers using Apache should set the DocumentRoot directive for the domain to:
DocumentRoot /cake_install/app/webroot
If your web server is configured correctly, you should now find your CakePHP application accessible at
http://www.example.com.
Note: On some systems installing libraries with PEAR will require sudo.
32
Chapter 2. Installation
After installing CakePHP with PEAR, if PEAR is configured correctly you should be able to use the cake
command to create a new application. Since CakePHP will be located on PHPs include_path you
wont need to make any other changes.
Installing CakePHP with Composer
Composer is a dependency management tool for PHP 5.3+. It solves many of the problems the PEAR
installer has, and simplifies managing multiple versions of libraries. Since CakePHP publishes a PEAR
package you can install CakePHP using composer4 . Before installing CakePHP youll need to setup a
composer.json file. A composer.json file for a CakePHP application would look like the following:
{
"name": "example-app",
"require": {
"cakephp/cakephp": "2.6.*"
},
"config": {
"vendor-dir": "Vendor/"
}
}
Save this JSON into composer.json in the APP directory of your project. Next download the composer.phar file into your project. After youve downloaded Composer, install CakePHP. In the same directory
as your composer.json run the following:
$ php composer.phar install
Once Composer has finished running you should have a directory structure that looks like:
example-app/
composer.phar
composer.json
Vendor/
bin/
autoload.php
composer/
cakephp/
You are now ready to generate the rest of your application skeleton:
$ Vendor/bin/cake bake project <path to project>
By default bake will hard-code CAKE_CORE_INCLUDE_PATH. To make your application more portable
you should modify webroot/index.php, changing CAKE_CORE_INCLUDE_PATH to be a relative
path:
define(
CAKE_CORE_INCLUDE_PATH,
ROOT . DS . APP_DIR . DS . Vendor . DS . cakephp . DS . cakephp . DS . lib
);
4
http://getcomposer.org
33
Note: If you are planning to create unit tests for your application youll also need to make the above change
to webroot/test.php
If youre installing any other libraries with Composer, youll need to setup the autoloader, and work around
an issue in Composers autoloader. In your Config/bootstrap.php file add the following:
// Load Composer autoload.
require APP . Vendor/autoload.php;
// Remove and re-prepend CakePHPs autoloader as Composer thinks it is the
// most important.
// See: http://goo.gl/kKVJO7
spl_autoload_unregister(array(App, load));
spl_autoload_register(array(App, load), true, true);
You should now have a functioning CakePHP application installed via Composer. Be sure to keep the
composer.json and composer.lock file with the rest of your source code.
Sharing CakePHP Libraries with multiple Applications
There may be some situations where you wish to place CakePHPs directories on different places on the
filesystem. This may be due to a shared host restriction, or maybe you just want a few of your apps to
share the same CakePHP libraries. This section describes how to spread your CakePHP directories across a
filesystem.
First, realize that there are three main parts to a CakePHP application:
1. The core CakePHP libraries, in /lib/Cake.
2. Your application code, in /app.
3. The applications webroot, usually in /app/webroot.
Each of these directories can be located anywhere on your file system, with the exception of the webroot,
which needs to be accessible by your web server. You can even move the webroot folder out of the app
folder as long as you tell CakePHP where youve put it.
To configure your CakePHP installation, youll need to make some changes to the following files.
/app/webroot/index.php
/app/webroot/test.php (if you use the Testing feature.)
There are three constants that youll need to edit: ROOT, APP_DIR, and CAKE_CORE_INCLUDE_PATH.
ROOT should be set to the path of the directory that contains your app folder.
APP_DIR should be set to the (base)name of your app folder.
CAKE_CORE_INCLUDE_PATH should be set to the path of your CakePHP libraries folder.
Lets run through an example so you can see what an advanced installation might look like in practice.
Imagine that I wanted to set up CakePHP to work as follows:
34
Chapter 2. Installation
It is recommended to use the DS constant rather than slashes to delimit file paths. This prevents any missing
file errors you might get as a result of using the wrong delimiter, and it makes your code more portable.
Apache and mod_rewrite (and .htaccess)
This section was moved to URL rewriting.
URL Rewriting
Apache and mod_rewrite (and .htaccess)
While CakePHP is built to work with mod_rewrite out of the boxand usually doesweve noticed that a
few users struggle with getting everything to play nicely on their systems.
Here are a few things you might try to get it running correctly. First look at your httpd.conf. (Make sure you
are editing the system httpd.conf rather than a user- or site-specific httpd.conf.)
These files can vary between different distributions and Apache versions. You may also take a look at
http://wiki.apache.org/httpd/DistrosDefaultLayout for further information.
1. Make sure that an .htaccess override is allowed and that AllowOverride is set to All for the correct
DocumentRoot. You should see something similar to:
#
#
#
#
#
#
Each directory to which Apache has access can be configured with respect
to which services and features are allowed and/or disabled in that
directory (and its subdirectories).
First, we configure the "default" to be a very restrictive set of
features.
35
#
<Directory />
Options FollowSymLinks
AllowOverride All
#
Order deny,allow
#
Deny from all
</Directory>
2. Make sure you are loading mod_rewrite correctly. You should see something like:
LoadModule rewrite_module libexec/apache2/mod_rewrite.so
In many systems these will be commented out by default, so you may just need to remove the leading
# symbols.
After you make changes, restart Apache to make sure the settings are active.
Verify that your .htaccess files are actually in the right directories. Some operating systems treat files
that start with . as hidden and therefore wont copy them.
3. Make sure your copy of CakePHP comes from the downloads section of the site or our Git repository,
and has been unpacked correctly, by checking for .htaccess files.
CakePHP root directory (must be copied to your document; redirects everything to your CakePHP
app):
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule
^ $ app/webroot/
RewriteRule
</IfModule>
[L]
CakePHP app directory (will be copied to the top directory of your application by bake):
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteRule
^$
webroot/
RewriteRule
</IfModule>
(.*) webroot/ $ 1
[L]
[L]
CakePHP webroot directory (will be copied to your applications web root by bake):
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*) $ index.php [QSA,L]
</IfModule>
If your CakePHP site still has problems with mod_rewrite, you might want to try modifying settings
for Virtual Hosts. On Ubuntu, edit the file /etc/apache2/sites-available/default (location is distributiondependent). In this file, ensure that AllowOverride None is changed to AllowOverride
All, so you have:
36
Chapter 2. Installation
<Directory />
Options FollowSymLinks
AllowOverride All
</Directory>
<Directory /var/www>
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Order Allow,Deny
Allow from all
</Directory>
On Mac OSX, another solution is to use the tool virtualhostx5 to make a Virtual Host to point to your
folder.
For many hosting services (GoDaddy, 1and1), your web server is actually being served from a
user directory that already uses mod_rewrite. If you are installing CakePHP into a user directory (http://example.com/~username/cakephp/), or any other URL structure that already utilizes
mod_rewrite, youll need to add RewriteBase statements to the .htaccess files CakePHP uses (/.htaccess, /app/.htaccess, /app/webroot/.htaccess).
This can be added to the same section with the RewriteEngine directive, so for example, your webroot
.htaccess file would look like:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /path/to/cake/app
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*) $ index.php [QSA,L]
</IfModule>
The details of those changes will depend on your setup, and can include additional things that are not
related to CakePHP. Please refer to Apaches online documentation for more information.
4. (Optional) To improve production setup, you should prevent invalid assets from being parsed by
CakePHP. Modify your webroot .htaccess to something like:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /path/to/cake/app
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^/(app/webroot/)?(img|css|js)/(.*) $
RewriteRule ^(.*) $ index.php [QSA,L]
</IfModule>
The above will simply prevent incorrect assets from being sent to index.php and instead display your
webservers 404 page.
Additionally you can create a matching HTML 404 page, or use the default built-in CakePHP 404 by
adding an ErrorDocument directive:
5
http://clickontyler.com/virtualhostx/
37
http://www.microsoft.com/web/downloads/platform.aspx
http://www.iis.net/downloads/microsoft/url-rewrite
8
http://www.microsoft.com/en-us/download/details.aspx?id=5747
9
http://www.microsoft.com/en-us/download/details.aspx?id=7435
7
38
Chapter 2. Installation
Once the web.config file is created with the correct IIS-friendly rewrite rules, CakePHPs links, CSS, JavaScipt, and rerouting should work correctly.
URL-Rewriting on lighttpd
Lighttpd does not support .htaccess functions, so you can remove all .htaccess files. In the lighttpd configuration, make sure youve activated mod_rewrite. Add a line:
url.rewrite-if-not-file =(
"^([^\?]*)(\?(.+))?$" => "/index.php?url=$1&$3"
)
39
UrlToolkit {
ToolkitID = cakephp
RequestURI exists Return
Match .* Rewrite /index.php
}
Fire It Up
Alright, lets see CakePHP in action. Depending on which setup you used, you should point your browser
to http://example.com/ or http://www.example.com/cake_2_0/. At this point, youll be presented with
CakePHPs default home, and a message that tells you the status of your current database connection.
Congratulations! You are ready to create your first CakePHP application.
Not working?
If youre getting timezone related errors from PHP uncomment one line in
app/Config/core.php:
/**
* Uncomment this line and correct your server timezone to fix
* any date & time related errors.
*/
date_default_timezone_set(UTC);
40
Chapter 2. Installation
CHAPTER 3
CakePHP Overview
Welcome to the Cookbook, the manual for the CakePHP web application framework that makes developing
a piece of cake!
This manual assumes that you have a general understanding of PHP and a basic understanding of objectoriented programming (OOP). Different functionality within the framework makes use of different technologies such as SQL, JavaScript, and XML and this manual does not attempt to explain those technologies,
only how they are used in context.
http://www.cakephp.org/
http://en.wikipedia.org/wiki/MIT_License
3
http://en.wikipedia.org/wiki/Open_source
4
http://en.wikipedia.org/wiki/Rapid_application_development
5
http://en.wikipedia.org/wiki/Application_framework
6
http://www.php.net/
7
https://github.com/cakephp?tab=members
2
41
Flexible licensing8
Compatible with versions PHP 5.2.8 and greater
Integrated CRUD9 for database interaction
Application scaffolding10
Code generation
MVC11 architecture
Request dispatcher with clean, custom URLs and routes
Built-in validation12
Fast and flexible templating13 (PHP syntax, with helpers)
View helpers for AJAX, JavaScript, HTML forms and more
Email, cookie, security, session, and request handling Components
Flexible ACL14
Data sanitization
Flexible caching15
Localization
Works from any web site directory, with little to no Apache16 configuration involved
Understanding Model-View-Controller
CakePHP follows the MVC17 software design pattern. Programming using MVC separates your application
into three main parts:
http://en.wikipedia.org/wiki/MIT_License
http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
10
http://en.wikipedia.org/wiki/Scaffold_(programming)
11
http://en.wikipedia.org/wiki/Model-view-controller
12
http://en.wikipedia.org/wiki/Data_validation
13
http://en.wikipedia.org/wiki/Web_template_system
14
http://en.wikipedia.org/wiki/Access_control_list
15
http://en.wikipedia.org/wiki/Web_cache
16
http://httpd.apache.org/
17
http://en.wikipedia.org/wiki/Model-view-controller
9
42
At a first glance, Model objects can be looked at as the first layer of interaction with any database you might
be using for your application. But in general they stand for the major concepts around which you implement
your application.
In the case of a social network, the Model layer would take care of tasks such as saving the user data, saving
friends associations, storing and retrieving user photos, finding suggestions for new friends, etc. The model
objects can be thought as Friend, User, Comment, or Photo.
Understanding Model-View-Controller
43
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 prototype18 . Separation also allows developers to make changes in
one part of the application without affecting the others.
If youve never built an application this way, it takes some time getting used to, but were confident that
once youve built your first application using CakePHP, you wont want to do it any other way.
To get started on your first CakePHP application, try the blog tutorial now
18
44
http://en.wikipedia.org/wiki/Software_prototyping
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 youll also be lightening our support load. Both the manual
and the API have an online component.
The Bakery
http://bakery.cakephp.org
The CakePHP Bakery is a clearing house for all things regarding CakePHP. Check it out for tutorials, case
studies, and code examples. Once youre acquainted with CakePHP, log on and share your knowledge with
the community and gain instant fame and fortune.
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.
45
Stackoverflow
http://stackoverflow.com/21
Tag your questions with cakephp and the specific version you are using to enable existing users of stackoverflow to find your questions.
19
https://github.com/cakephp?tab=members
http://groups.google.com/group/cake-php
21
http://stackoverflow.com/questions/tagged/cakephp/
22
http://cakephp-fr.org
20
46
CHAPTER 4
Controllers
Controllers are the C in MVC. After routing has been applied and the correct controller has been found,
your controllers action is called. Your controller should handle interpreting the request data, making sure
the correct models are called, and the right response or view is rendered. Controllers can be thought of as
middle man between the Model and View. You want to keep your controllers thin, and your models fat. This
will help you more easily reuse your code and makes your code easier to test.
Commonly, a controller is used to manage the logic around a single model. For example, if you were
building a site for an online bakery, you might have a RecipesController managing your recipes and an
IngredientsController managing your ingredients. However, its also possible to have controllers work with
more than one model. In CakePHP, a controller is named after the primary model it handles.
Your applications controllers extend the AppController class, which in turn extends the core Controller class.
The AppController class can be defined in
/app/Controller/AppController.php and it should contain methods that are shared between all
of your applications controllers.
Controllers provide a number of methods that handle requests. These are called actions. By default, each
public method in a controller is an action, and is accessible from a URL. An action is responsible for
interpreting the request and creating the response. Usually responses are in the form of a rendered view, but
there are other ways to create responses as well.
Controller attributes and methods created in your AppController will be available to all of your applications controllers. Components (which youll learn about later) are best used for code that is used in many
(but not necessarily all) controllers.
47
While normal object-oriented inheritance rules apply, CakePHP does a bit of extra work when it comes to
special controller attributes. The components and helpers used by a controller are treated specially. In these
cases, AppController value arrays are merged with child controller class arrays. The values in the child
class will always override those in AppController.
Note: CakePHP merges the following variables from the AppController into your applications controllers:
$components
$helpers
$uses
Remember to add the default Html and Form helpers if you define the $helpers property in your
AppController.
Also remember to call AppControllers callbacks within child controller callbacks for best results:
public function beforeFilter() {
parent::beforeFilter();
}
Request parameters
When a request is made to a CakePHP application, CakePHPs Router and Dispatcher classes use
Routes Configuration to find and create the correct controller. The request data is encapsulated in a request
object. CakePHP puts all of the important request information into the $this->request property. See
the section on CakeRequest for more information on the CakePHP request object.
Controller actions
Controller actions are responsible for converting the request parameters into a response for the browser/user
making the request. CakePHP uses conventions to automate this process and remove some boilerplate code
you would otherwise need to write.
By convention, CakePHP renders a view with an inflected version of the action name. Returning to our online bakery example, our RecipesController might contain the view(), share(), and search() actions.
The controller would be found in /app/Controller/RecipesController.php and contain:
# /app/Controller/RecipesController.php
class RecipesController extends AppController {
public function view($id) {
//action logic goes here..
}
public function share($customerId, $recipeId) {
//action logic goes here..
48
Chapter 4. Controllers
}
public function search($query) {
//action logic goes here..
}
}
The
view
files
for
these
actions
would
be
app/View/Recipes/view.ctp,
app/View/Recipes/share.ctp, and app/View/Recipes/search.ctp. The conventional
view file name is the lowercased and underscored version of the action name.
Controller actions generally use set() to create a context that View uses to render the view. Because of
the conventions that CakePHP uses, you dont need to create and render the view manually. Instead, once a
controller action has completed, CakePHP will handle rendering and delivering the View.
If for some reason youd like to skip the default behavior, both of the following techniques will bypass the
default view rendering behavior.
If you return a string, or an object that can be converted to a string from your controller action, it will
be used as the response body.
You can return a CakeResponse object with the completely created response.
When you use controller methods with requestAction(), you will often want to return data that isnt
a string. If you have controller methods that are used for normal web requests + requestAction, you should
check the request type before returning:
class RecipesController extends AppController {
public function popular() {
$popular = $this->Recipe->popular();
if (!empty($this->request->params[requested])) {
return $popular;
}
$this->set(popular, $popular);
}
}
The above controller action is an example of how a method can be used with requestAction() and normal requests. Returning array data to a non-requestAction request will cause errors and should be avoided.
See the section on requestAction() for more tips on using requestAction()
In order for you to use a controller effectively in your own application, well cover some of the core attributes
and methods provided by CakePHPs controllers.
49
Controller Methods
For a complete list of controller methods and their descriptions visit the CakePHP API1 .
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:
$data = array(
color => pink,
type => sugar,
base_price => 23.95
);
// make $color, $type, and $base_price
// available to the view:
1
50
http://api.cakephp.org/2.4/class-Controller.html
Chapter 4. Controllers
$this->set($data);
The attribute $pageTitle no longer exists. Use set() to set the title:
$this->set(title_for_layout, This is the page title);
Although CakePHP will automatically call it after every actions logic (unless youve set
$this->autoRender to false), you can use it to specify an alternate view file by specifying a
view name in the controller using $view.
If $view starts with /, it is assumed to be a view or element file relative to the /app/View folder.
This allows direct rendering of elements, very useful in AJAX calls.
// Render the element in /View/Elements/ajaxreturn.ctp
$this->render(/Elements/ajaxreturn);
The $layout parameter allows you to specify the layout with which the view is rendered.
Rendering a specific view
In your controller, you may want to render a different view than the conventional one. You can do this by
calling render() directly. Once you have called render(), CakePHP will not try to re-render the view:
class PostsController extends AppController {
public function my_action() {
$this->render(custom_file);
}
}
This
would
render
app/View/Posts/custom_file.ctp
app/View/Posts/my_action.ctp
instead
of
You
can
also
render
views
inside
plugins
using
the
following
syntax:
$this->render(PluginName.PluginController/custom_file). For example:
Controller Methods
51
Flow Control
Controller::redirect(mixed $url, integer $status, boolean $exit)
The flow control method youll use most often is redirect(). This method takes its first parameter
in the form of a CakePHP-relative URL. When a user has successfully placed an order, you might wish
to redirect them to a receipt screen.
public function place_order() {
// Logic for finalizing order goes here
if ($success) {
return $this->redirect(
array(controller => orders, action => thanks)
);
}
return $this->redirect(
array(controller => orders, action => confirm)
);
}
You can also use a relative or absolute URL as the $url argument:
$this->redirect(/orders/thanks);
$this->redirect(http://www.example.com);
The second parameter of redirect() allows you to define an HTTP status code to accompany the
redirect. You may want to use 301 (moved permanently) or 303 (see other), depending on the nature
of the redirect.
The method will issue an exit() after the redirect unless you set the third parameter to false.
If you need to redirect to the referer page you can use:
$this->redirect($this->referer());
52
Chapter 4. Controllers
Callbacks
In addition to the Request Life-cycle callbacks, CakePHP also supports callbacks related to scaffolding.
Controller::beforeScaffold($method)
$method name of method called example index, edit, etc.
Controller::afterScaffoldSave($method)
$method name of method called either edit or update.
Controller::afterScaffoldSaveError($method)
$method name of method called either edit or update.
Controller::scaffoldError($method)
$method name of method called example index, edit, etc.
Controller Methods
53
If $default is not set, the function defaults to the root of your domain - /.
Parameter $local if set to true, restricts referring URLs to local server.
Controller::disableCache()
Used to tell the users browser not to cache the results of the current request. This is different than
view caching, covered in a later chapter.
The headers sent to this effect are:
Expires: Mon, 26 Jul 1997 05:00:00 GMT
Last-Modified: [current datetime] GMT
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Chapter 4. Controllers
based on the Order model. Then a controller action can use the data posted from that form to craft
find conditions:
public function index() {
$conditions = $this->postConditions($this->request->data);
$orders = $this->Order->find(all, compact(conditions));
$this->set(orders, $orders);
}
The third parameter allows you to tell CakePHP what SQL boolean operator to use between the find
conditions. Strings 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.
Controller::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.
Controller::requestAction(string $url, array $options)
This function calls a controllers action from any location and returns data from the action. The $url
passed is a CakePHP-relative URL (/controllername/actionname/params). To pass extra data to the
receiving controller action add to the $options array.
Note: You can use requestAction() to retrieve a fully rendered view by passing return in the
options: requestAction($url, array(return));. It is important to note that making
a requestAction() using return from a controller method can cause script and CSS tags to
not work correctly.
Controller Methods
55
You should always include checks to make sure your requestAction() methods are actually
originating from requestAction(). Failing to do so will allow requestAction() methods
to be directly accessible from a URL, which is generally undesirable.
If we now create a simple element to call that function:
// View/Elements/latest_comments.ctp
$comments = $this->requestAction(/comments/latest);
foreach ($comments as $comment) {
echo $comment[Comment][title];
}
We can then place that element anywhere to get the output using:
echo $this->element(latest_comments);
Written in this way, whenever the element is rendered, a request will be made to the controller to get
the data, the data will be processed, and returned. However in accordance with the warning above its
best to make use of element caching to prevent needless processing. By modifying the call to element
to look like this:
echo $this->element(latest_comments, array(), array(cache => true));
The requestAction() call will not be made while the cached element view file exists and is valid.
In addition, requestAction() now takes array based cake style URLs:
echo $this->requestAction(
array(controller => articles, action => featured),
array(return)
);
56
Chapter 4. Controllers
This allows the requestAction() call to bypass the usage of Router::url() which can increase performance. The url based arrays are the same as the ones that HtmlHelper::link()
uses with one difference - if you are using named or passed parameters, you must put them in a second array and wrap them with the correct key. This is because requestAction() merges the
named args array (requestActions 2nd parameter) with the Controller::params member array and does not explicitly place the named args array into the key named; Additional members in
the $option array will also be made available in the requested actions Controller::params
array:
echo $this->requestAction(/articles/featured/limit:3);
echo $this->requestAction(/articles/view/5);
Note: Unlike other places where array URLs are analogous to string URLs, requestAction()
treats them differently.
When using an array url in conjunction with requestAction() you must specify all
parameters that you will need in the requested action.
This includes parameters like
$this->request->data. In addition to passing all required parameters, named and pass parameters must be done in the second array as seen above.
Controller::loadModel(string $modelClass, mixed $id)
The loadModel() function comes handy when you need to use a model which is not the controllers
default model or its associated model:
$this->loadModel(Article);
$recentArticles = $this->Article->find(
all,
array(limit => 5, order => Article.created DESC)
);
$this->loadModel(User, 2);
$user = $this->User->read();
Controller Attributes
For a complete list of controller attributes and their descriptions visit the CakePHP API2 .
2
http://api.cakephp.org/2.4/class-Controller.html
Controller Attributes
57
property Controller::$name
The $name attribute should be set to the name of the controller. Usually this is just the plural form
of the primary model the controller uses. This property can be omitted, but saves CakePHP from
inflecting it:
// $name controller attribute usage example
class RecipesController extends AppController {
public $name = Recipes;
}
58
Chapter 4. Controllers
Each of these variables are merged with their inherited values, therefore it is not necessary (for example) to redeclare the FormHelper, or anything that is declared in your AppController.
property Controller::$components
The components array allows you to set which Components a controller will use. Like $helpers
and $uses components in your controllers are merged with those in AppController. As with
$helpers you can pass settings into $components. See Configuring Components for more information.
Other Attributes
While you can check out the details for all controller attributes in the API3 , there are other controller attributes that merit their own sections in the manual.
property Controller::$cacheAction
The cacheAction attribute is used to define the duration and other information about full page caching.
You can read more about full page caching in the CacheHelper documentation.
property Controller::$paginate
The paginate attribute is a deprecated compatibility property. Using it loads and configures the
PaginatorComponent. It is recommended that you update your code to use normal component
settings:
class ArticlesController extends AppController {
public $components = array(
Paginator => array(
Article => array(
conditions => array(published => 1)
)
)
);
}
More on controllers
Request and Response objects
New in CakePHP 2.0 are request and response objects. In previous versions, these objects were represented
through arrays, and the related methods were spread across RequestHandlerComponent, Router,
Dispatcher and Controller. There was no authoritative object on what information the request
contained. For 2.0, CakeRequest and CakeResponse are used for this purpose.
CakeRequest
CakeRequest is the default request object used in CakePHP. It centralizes a number of features for interrogating and interacting with request data. On each request, one CakeRequest is created and then passed
3
http://api.cakephp.org
More on controllers
59
by reference to the various layers of an application that use request data. By default, CakeRequest is
assigned to $this->request, and is available in Controllers, Views and Helpers. You can also access it
in Components by using the controller reference. Some of the duties CakeRequest performs include:
Process the GET, POST, and FILES arrays into the data structures you are familiar with.
Provide environment introspection pertaining to the request. Things like the headers sent, the clients
IP address, and the subdomain/domain information about the application the server is running on.
Provide access to request parameters both as array indexes and object properties.
Accessing request parameters
CakeRequest exposes several interfaces for accessing request parameters. The first uses object properties,
the second uses array indexes, and the third uses $this->request->params:
$this->request->controller;
$this->request[controller];
$this->request->params[controller];
All of the above will access the same value. Multiple ways of accessing the parameters have been provided
to ease migration for existing applications. All Route Elements are accessed through this interface.
In addition to Route Elements, you also often need access to Passed Arguments and Named Parameters.
These are both available on the request object as well:
// Passed arguments
$this->request->pass;
$this->request[pass];
$this->request->params[pass];
// named parameters
$this->request->named;
$this->request[named];
$this->request->params[named];
All of these will provide you access to the passed arguments and named parameters. There are several important/useful parameters that CakePHP uses internally. These are also all found in the request parameters:
plugin The plugin handling the request. Will be null when there is no plugin.
controller The controller handling the current request.
action The action handling the current request.
prefix The prefix for the current action. See Prefix Routing for more information.
bare Present when the request came from requestAction() and included the bare option. Bare
requests do not have layouts rendered.
requested Present and set to true when the action came from requestAction().
60
Chapter 4. Controllers
You can either directly access the $query property, or you can use CakeRequest::query() to read
the URL query array in an error-free manner. Any keys that do not exist will return null:
$foo = $this->request->query(value_that_does_not_exist);
// $foo === null
You can either directly access the $data property, or you can use CakeRequest::data() to read the
data array in an error-free manner. Any keys that do not exist will return null:
$foo = $this->request->data(Value.that.does.not.exist);
// $foo == null
More on controllers
61
Some deserializing methods require additional parameters when called, such as the as array parameter on
json_decode. If you want XML converted into a DOMDocument object, CakeRequest::input()
supports passing in additional parameters as well:
// Get Xml encoded data submitted to a PUT/POST action
$data = $this->request->input(Xml::build, array(return => domdocument));
Both method calls will return the same value. For the time being, the methods are still available on RequestHandlerComponent, but are deprecated and still might be removed before
the final release. You can also easily extend the request detectors that are available by using
CakeRequest::addDetector() to create new kinds of detectors. There are four different types of
detectors that you can create:
Environment value comparison - Compares a value fetched from env() for equality with the provided value.
Pattern value comparison - Pattern value comparison allows you to compare a value fetched from
env() to a regular expression.
Option based comparison - Option based comparisons use a list of options to create a regular expression. Subsequent calls to add an already defined options detector will merge the options.
Callback detectors - Callback detectors allow you to provide a callback type to handle the check.
The callback will receive the request object as its only parameter.
Some examples would be:
// Add an environment detector.
$this->request->addDetector(
post,
array(env => REQUEST_METHOD, value => POST)
);
// Add a pattern value detector.
$this->request->addDetector(
iphone,
62
Chapter 4. Controllers
CakeRequest
also
includes
methods
like
CakeRequest::domain(),
CakeRequest::subdomains() and CakeRequest::host() to help applications with subdomains.
There are several built-in detectors that you can use:
is(get) Check to see whether the current request is a GET.
is(put) Check to see whether the current request is a PUT.
is(post) Check to see whether the current request is a POST.
is(delete) Check to see whether the current request is a DELETE.
is(head) Check to see whether the current request is HEAD.
is(options) Check to see whether the current request is OPTIONS.
is(ajax) Check to see whether the current request came with X-Requested-With = XMLHttpRequest.
is(ssl) Check to see whether the request is via SSL
is(flash) Check to see whether the request has a User-Agent of Flash
is(mobile) Check to see whether the request came from a common list of mobile agents.
CakeRequest and RequestHandlerComponent
Since many of the features CakeRequest offers used to be the realm of
RequestHandlerComponent, some rethinking was required to figure out how it still fits into
the picture. For 2.0, RequestHandlerComponent provides a layer of sugar, such as switching layout
and views based on content, on top of the utility that CakeRequest affords. This separation of utility and
sugar between the two classes lets you more easily choose what you want.
More on controllers
63
Chapter 4. Controllers
CakeRequest::input($callback[, $options ])
Retrieve the input data for a request, and optionally pass it through a decoding function. Useful when
interacting with XML or JSON request body content. Additional parameters for the decoding function
can be passed as arguments to input():
$this->request->input(json_decode);
CakeRequest::data($name)
Provides dot notation access to request data. Allows request data to be read and modified. Calls can
be chained together as well:
// Modify some request data, so you can prepopulate some form fields.
$this->request->data(Post.title, New post)
->data(Comment.1.author, Mark);
// You can also read out data.
$value = $this->request->data(Post.title);
CakeRequest::query($name)
Provides dot notation access to URL query data:
// URL is /posts/index?page=1&sort=title
$value = $this->request->query(page);
More on controllers
65
CakeRequest::acceptLanguage(es-es);
CakeRequest::param($name)
Safely read values in $request->params. This removes the need to call isset() or empty()
before using param values.
New in version 2.4.
property CakeRequest::$data
An array of POST data. You can use CakeRequest::data() to read this property in a way that
suppresses notice errors.
property CakeRequest::$query
An array of query string parameters.
property CakeRequest::$params
An array of route elements and request parameters.
property CakeRequest::$here
Returns the current request uri.
property CakeRequest::$base
The base path to the application, usually / unless your application is in a subdirectory.
property CakeRequest::$webroot
The current webroot.
CakeResponse
CakeResponse is the default response class in CakePHP. It encapsulates a number of features and
functionality for generating HTTP responses in your application. It also assists in testing, as it can be
mocked/stubbed allowing you to inspect headers that will be sent. Like CakeRequest, CakeResponse
consolidates a number of methods previously found on Controller, RequestHandlerComponent
and Dispatcher. The old methods are deprecated in favour of using CakeResponse.
CakeResponse provides an interface to wrap the common response-related tasks such as:
Sending headers for redirects.
Sending content type headers.
Sending any header.
Sending the response body.
Changing the response class
CakePHP uses CakeResponse by default. CakeResponse is a flexible and transparent class. If
you need to override it with your own application-specific class, you can replace CakeResponse
in app/webroot/index.php.
This will make all the controllers in your application use
CustomResponse instead of CakeResponse. You can also replace the response instance by setting
$this->response in your controllers. Overriding the response object is handy during testing, as it
66
Chapter 4. Controllers
allows you to stub out the methods that interact with header(). See the section on CakeResponse and
testing for more information.
Dealing with content types
You can control the Content-Type of your applications responses with CakeResponse::type(). If
your application needs to deal with content types that are not built into CakeResponse, you can map
them with CakeResponse::type() as well:
// Add a vCard type
$this->response->type(array(vcf => text/v-card));
// Set the response Content-Type to vcard.
$this->response->type(vcf);
Usually, youll want to map additional content types in your controllers beforeFilter() callback,
so you can leverage the automatic view switching features of RequestHandlerComponent if you are
using it.
Sending files
There are times when you want to send files as responses for your requests. Prior to version 2.3, you could
use MediaView. As of 2.3, MediaView is deprecated and you can use CakeResponse::file() to
send a file as response:
public function sendFile($id) {
$file = $this->Attachment->getFile($id);
$this->response->file($file[path]);
// Return response object to prevent controller from trying to render
// a view
return $this->response;
}
As shown in the above example, you must pass the file path to the method. CakePHP will send a proper
content type header if its a known file type listed in CakeResponse::$_mimeTypes. You can add new
types prior to calling CakeResponse::file() by using the CakeResponse::type() method.
If you want, you can also force a file to be downloaded instead of displayed in the browser by specifying the
options:
$this->response->file(
$file[path],
array(download => true, name => foo)
);
67
Setting headers
Setting headers is done with the CakeResponse::header() method. It can be called with a few
different parameter configurations:
// Set a single header
$this->response->header(Location, http://example.com);
// Set multiple headers
$this->response->header(array(
Location => http://example.com,
X-Extra => My header
));
$this->response->header(array(
WWW-Authenticate: Negotiate,
Content-type: application/pdf
));
Setting the same header() multiple times will result in overwriting the previous values, just as regular
header calls do. Headers are not sent when CakeResponse::header() is called; instead they are
buffered until the response is actually sent.
New in version 2.4.
You can now use the convenience method CakeResponse::location() to directly set or get the
redirect location header.
Interacting with browser caching
You sometimes need to force browsers not to cache the results of a controller action.
CakeResponse::disableCache() is intended for just that:
public function index() {
// do something.
$this->response->disableCache();
}
68
Chapter 4. Controllers
Warning: Using disableCache() with downloads from SSL domains while trying to send files to Internet
Explorer can result in errors.
You can also tell clients that you want them to cache responses. By using CakeResponse::cache():
public function index() {
//do something
$this->response->cache(-1 minute, +5 days);
}
The above would tell clients to cache the resulting response for 5 days, hopefully speeding up your visitors experience. CakeResponse::cache() sets the Last-Modified value to the first argument.
Expires header and the max-age directive are set based on the second parameter. Cache-Controls
public directive is set as well.
Fine tuning HTTP cache
One of the best and easiest ways of speeding up your application is to use HTTP cache. Under this caching
model, you are only required to help clients decide if they should use a cached copy of the response by
setting a few headers such as modified time and response entity tag.
Rather than forcing you to code the logic for caching and for invalidating (refreshing) it once the data has
changed, HTTP uses two models, expiration and validation, which usually are much simpler to use.
Apart from using CakeResponse::cache(), you can also use many other methods to fine-tune HTTP
cache headers to take advantage of browser or reverse proxy caching.
The Cache Control header
CakeResponse class helps you set this header with some utility methods that will produce a final valid
Cache-Control header. The first is the CakeResponse::sharable() method, which indicates
whether a response is to be considered sharable across different users or clients. This method actually
controls the public or private part of this header. Setting a response as private indicates that all or part
of it is intended for a single user. To take advantage of shared caches, the control directive must be set as
public.
The second parameter of this method is used to specify a max-age for the cache, which is the number of
seconds after which the response is no longer considered fresh:
public function view() {
...
// set the Cache-Control as public for 3600 seconds
$this->response->sharable(true, 3600);
More on controllers
69
}
public function my_data() {
...
// set the Cache-Control as private for 3600 seconds
$this->response->sharable(false, 3600);
}
CakeResponse exposes separate methods for setting each of the directives in the Cache-Control
header.
The Expiration header
This method also accepts a DateTime instance or any string that can be parsed by the DateTime class.
The Etag header
70
Chapter 4. Controllers
In some cases, you might want to serve different content using the same URL. This is often the case if
you have a multilingual page or respond with different HTML depending on the browser. Under such
circumstances you can use the Vary header:
$this->response->vary(User-Agent);
$this->response->vary(Accept-Encoding, User-Agent);
$this->response->vary(Accept-Language);
Additionally, you can run tests from the command line more easily, as you can use mocks to avoid the
headers sent errors that can occur when trying to set headers in CLI.
More on controllers
71
CakeResponse API
class CakeResponse
CakeResponse provides a number of useful methods for interacting with the response you are sending
to a client.
CakeResponse::header($header = null, $value = null)
Allows you to directly set one or more headers to be sent with the response.
CakeResponse::location($url = null)
Allows you to directly set the redirect location header to be sent with the response:
// Set the redirect location
$this->response->location(http://example.com);
// Get the current redirect location header
$location = $this->response->location();
72
Chapter 4. Controllers
mines whether it can still be considered fresh. If so, deletes the response content, and sends the 304
Not Modified header.
New in version 2.1.
CakeResponse::compress()
Turns on gzip compression for the request.
CakeResponse::download($filename)
Allows you to send a response as an attachment, and to set its filename.
CakeResponse::statusCode($code = null)
Allows you to set the status code of the response.
CakeResponse::body($content = null)
Sets the content body of the response.
CakeResponse::send()
Once you are done creating a response, calling send() will send all the set headers as well as the
body. This is done automatically at the end of each request by Dispatcher.
CakeResponse::file($path, $options = array())
Allows you to set the Content-Disposition header of a file either to display or to download.
New in version 2.3.
Scaffolding
Deprecated since version 2.5: Dynamic scaffolding will be removed and replaced in 3.0
Application scaffolding is a technique that allows a developer to define and create a basic application that
can create, retrieve, update and delete objects. Scaffolding in CakePHP also allows developers to define how
objects are related to each other, and to create and break those links.
All thats needed to create a scaffold is a model and its controller. Once you set the $scaffold variable in the
controller, youre up and running.
CakePHPs scaffolding is pretty cool. It allows you to get a basic CRUD application up and going in minutes.
Its so cool that youll want to use it in production apps. Now, we think its cool too, but please realize that
scaffolding is... well... just scaffolding. Its a loose structure you throw up real quick during the beginning
of a project in order to get started. It isnt meant to be completely flexible, its 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. CakePHPs bake console, covered in the next
section, is a great next step: it generates all the code that would produce the same result as the most current
scaffold.
Scaffolding is a great way of getting the early parts of developing a web application started. Early database
schemas are subject to change, which is perfectly normal in the early part of the design process. This has
a downside: a web developer hates creating forms that never will see real use. To reduce the strain on the
developer, scaffolding has been included in CakePHP. Scaffolding analyzes your database tables and creates
standard lists with add, delete and edit buttons, standard forms for editing and standard views for inspecting
a single item in the database.
More on controllers
73
To add scaffolding to your application, in the controller, add the $scaffold variable:
class CategoriesController extends AppController {
public $scaffold;
}
Assuming youve created even the most basic Category model class file (in
app/Model/Category.php), youre ready to go.
Visit http://example.com/categories to see
your new scaffold.
Note: Creating methods in controllers that are scaffolded can cause unwanted results. For example, if you
create an index() method in a scaffolded controller, your index method will be rendered rather than the
scaffolding functionality.
Scaffolding is aware of models associations; so, if your Category model belongsTo User, youll see related User IDs in the Category listings. While scaffolding knows about models associations, you will not
see any related records in the scaffold views until you manually add the association code to the model. For
example, if Group hasMany User and User belongsTo Group, you have to manually add the following
code to your User and Group models. Before you do it, the view displays an empty select input for Group
in the New User form; after populated with IDs or names from the Group table in the New User form:
// In Group.php
public $hasMany = User;
// In User.php
public $belongsTo = Group;
If youd rather see something besides an ID (like the users first name), you can set the $displayField
variable in the model. Lets set the $displayField variable in our User class so that users related to
categories will be shown by first name rather than just by ID in scaffolding. This feature makes scaffolding
more readable in many instances:
class User extends AppModel {
public $displayField = first_name;
}
74
Chapter 4. Controllers
http://example.com/admin/controller/add
http://example.com/admin/controller/delete
This is an easy way to create a simple backend interface quickly. Keep in mind that you cannot have both
admin and non-admin methods scaffolded at the same time. As with normal scaffolding, you can override
individual methods and replace them with your own:
public function admin_view($id = null) {
// custom code here
}
Once you have replaced a scaffolded action, you will need to create a view file for the action as well.
Customizing Scaffold Views
If youre looking for something a little different in your scaffolded views, you can create templates. We still
dont recommend using this technique for production applications, but such a customization may be useful
during prototyping iterations.
Custom scaffolding views for a specific controller (PostsController in this example) should be placed like
so:
app/View/Posts/scaffold.index.ctp
app/View/Posts/scaffold.form.ctp
app/View/Posts/scaffold.view.ctp
Custom scaffolding views for all controllers should be placed like so:
app/View/Scaffolds/index.ctp
app/View/Scaffolds/form.ctp
app/View/Scaffolds/view.ctp
More on controllers
75
Components
Components are packages of logic that are shared between controllers. CakePHP comes with a fantastic set
of core components you can use to aid in various common tasks. You can also create your own components.
If you find yourself wanting to copy and paste things between controllers, you should consider creating your
own component to contain the functionality. Creating components keeps controller code clean and allows
you to reuse code between projects.
Each of the core components is detailed in its own chapter. See Components. This section describes how to
configure and use components, and how to create your own components.
Configuring Components
Many of the core components require configuration. Some examples of components requiring configuration
are Authentication and Cookie. Configuration for these components, and for components in general, is
usually done in the $components array or your controllers beforeFilter() method:
class PostsController extends AppController {
public $components = array(
Auth => array(
authorize => array(controller),
loginAction => array(
controller => users,
action => login
)
),
Cookie => array(name => CookieMonster)
);
The previous fragment of code would be an example of configuring a component with the $components
array. All core components allow their configuration settings to be set in this way. In addition, you can
configure components in your controllers beforeFilter() method. This is useful when you need to
assign the results of a function to a component property. The above could also be expressed as:
public function beforeFilter() {
$this->Auth->authorize = array(controller);
$this->Auth->loginAction = array(
controller => users,
action => login
);
$this->Cookie->name = CookieMonster;
}
Its possible, however, that a component requires certain configuration options to be set before the controllers beforeFilter() is run. To this end, some components allow configuration options be set in the
$components array:
public $components = array(
DebugKit.Toolbar => array(panels => array(history, session))
);
76
Chapter 4. Controllers
Consult the relevant documentation to determine what configuration options each component provides.
One common setting to use is the className option, which allows you to alias components. This feature
is useful when you want to replace $this->Auth or another common Component reference with a custom
implementation:
// app/Controller/PostsController.php
class PostsController extends AppController {
public $components = array(
Auth => array(
className => MyAuth
)
);
}
// app/Controller/Component/MyAuthComponent.php
App::uses(AuthComponent, Controller/Component);
class MyAuthComponent extends AuthComponent {
// Add your code to override the core AuthComponent
}
Using Components
Once youve included some components in your controller, using them is pretty simple. Each component
you use is exposed as a property on your controller. If you had loaded up the SessionComponent and
the CookieComponent in your controller, you could access them like so:
class PostsController extends AppController {
public $components = array(Session, Cookie);
public function delete() {
if ($this->Post->delete($this->request->data(Post.id)) {
$this->Session->setFlash(Post deleted.);
return $this->redirect(array(action => index));
}
}
Note: Since both Models and Components are added to Controllers as properties they share the same
namespace. Be sure to not give a component and a model the same name.
You might not need all of your components available on every controller action. In situations like this you
can load a component at runtime using the Component Collection. From inside a controllers method you
More on controllers
77
Note: Keep in mind that loading a component on the fly will not call its initialize method. If the component
you are calling has this method you will need to call it manually after load.
Component Callbacks
Components also offer a few request life-cycle callbacks that allow them to augment the request cycle. See
the base Component API for more information on the callbacks components offer.
Creating a Component
Suppose our online application needs to perform a complex mathematical operation in many different parts
of the application. We could create a component to house this shared logic for use in many different controllers.
The first step is to create a new component file and class.
Create the file in
app/Controller/Component/MathComponent.php. The basic structure for the component
would look something like this:
App::uses(Component, Controller);
class MathComponent extends Component {
public function doComplexOperation($amount1, $amount2) {
return $amount1 + $amount2;
}
}
Note: All components must extend Component. Failing to do this will trigger an exception.
Once our component is finished, we can use it in the applications controllers by placing the components
name (without the Component part) in the controllers $components array. The controller will automatically be given a new attribute named after the component, through which we can access an instance of
it:
/* Make the new component available at $this->Math,
as well as the standard $this->Session */
public $components = array(Math, Session);
Components declared in AppController will be merged with those in your other controllers. So there is
no need to re-declare the same component twice.
78
Chapter 4. Controllers
When including Components in a Controller you can also declare a set of parameters that will be passed on
to the Components constructor. These parameters can then be handled by the Component:
public $components = array(
Math => array(
precision => 2,
randomGenerator => srand
),
Session, Auth
);
The above would pass the array containing precision and randomGenerator to
MathComponent::__construct() as the second parameter.
By convention, if array keys
match components public properties, the properties will be set to the values of these keys.
Using other Components in your Component
Sometimes one of your components may need to use another component. In this case you can include
other components in your component the exact same way you include them in controllers - using the
$components var:
// app/Controller/Component/CustomComponent.php
App::uses(Component, Controller);
class CustomComponent extends Component {
// the other component your component uses
public $components = array(Existing);
public function initialize(Controller $controller) {
$this->Existing->foo();
}
public function bar() {
// ...
}
}
// app/Controller/Component/ExistingComponent.php
App::uses(Component, Controller);
class ExistingComponent extends Component {
public function foo() {
// ...
}
}
More on controllers
79
Component API
class Component
The base Component class offers a few methods for lazily loading other Components through
ComponentCollection as well as dealing with common handling of settings. It also provides
prototypes for all the component callbacks.
Component::__construct(ComponentCollection $collection, $settings = array())
Constructor for the base component class. All $settings that are also public properties will have
their values changed to the matching value in $settings.
Callbacks
Component::initialize(Controller $controller)
Is called before the controllers beforeFilter method.
Component::startup(Controller $controller)
Is called after the controllers beforeFilter method but before the controller executes the current action
handler.
Component::beforeRender(Controller $controller)
Is called after the controller executes the requested actions logic, but before the controllers renders
views and layout.
Component::shutdown(Controller $controller)
Is called before output is sent to the browser.
Component::beforeRedirect(Controller $controller, $url, $status=null, $exit=true)
Is invoked when the controllers redirect method is called but before any further action. If this method
returns false the controller will not continue on to redirect the request. The $url, $status and $exit
variables have same meaning as for the controllers method. You can also return a string which will
be interpreted as the URL to redirect to or return an associative array with the key url and optionally
status and exit.
80
Chapter 4. Controllers
CHAPTER 5
Views
Views are the V in MVC. Views are responsible for generating the specific output required for the request.
Often this is in the form of HTML, XML, or JSON, but streaming files and creating PDFs that users can
download are also responsibilities of the View Layer.
CakePHP comes with a few built-in View classes for handling the most common rendering scenarios:
To create XML or JSON webservices you can use the JSON and XML views.
To serve protected files, or dynamically generated files, you can use Sending files.
To create multiple themed views, you can use Themes.
View Templates
The view layer of CakePHP is how you speak to your users. Most of the time your views will be showing
(X)HTML documents to browsers, but you might also need to serve AMF data to a Flash object, reply to a
remote application via SOAP, or output a CSV file for a user.
By default 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 youre serving to. If youd prefer using a templating language like
Twig, or Smarty, a subclass of View will bridge your templating language and CakePHP.
A view file is stored in /app/View/, in a subfolder named after the controller that uses the file. It has a
filename corresponding to its action. For example, the view file for the Products controllers view() action
would normally be found in /app/View/Products/view.ctp.
The view layer in CakePHP can be made up of a number of different parts. Each part has different uses, and
will be covered in this chapter:
views: Views are the part of the page that is unique to the action being run. They form the meat of
your applications response.
elements: smaller, reusable bits of view code. Elements are usually rendered inside views.
81
layouts: view files that contain presentational code that wraps many interfaces in your application.
Most views are rendered inside a layout.
helpers: these classes encapsulate view logic that is needed in many places in the view layer. Among
other things, helpers in CakePHP can help you build forms, build AJAX functionality, paginate model
data, or serve RSS feeds.
Extending Views
New in version 2.1.
View extending allows you to wrap one view in another. Combining this with view blocks gives you a
powerful way to keep your views DRY. For example, your application has a sidebar that needs to change
depending on the specific view being rendered. By extending a common view file, you can avoid repeating
the common markup for your sidebar, and only define the parts that change:
// app/View/Common/view.ctp
<h1><?php echo $this->fetch(title); ?></h1>
<?php echo $this->fetch(content); ?>
<div class="actions">
<h3>Related actions</h3>
<ul>
<?php echo $this->fetch(sidebar); ?>
</ul>
</div>
The above view file could be used as a parent view. It expects that the view extending it will define the
sidebar and title blocks. The content block is a special block that CakePHP creates. It will contain
all the uncaptured content from the extending view. Assuming our view file has a $post variable with the
data about our post, the view could look like:
<?php
// app/View/Posts/view.ctp
$this->extend(/Common/view);
$this->assign(title, $post);
$this->start(sidebar);
?>
<li>
<?php
echo $this->Html->link(edit, array(
action => edit,
$post[Post][id]
)); ?>
</li>
<?php $this->end(); ?>
// The remaining content will be available as the content block
// in the parent view.
<?php echo h($post[Post][body]);
82
Chapter 5. Views
The post view above shows how you can extend a view, and populate a set of blocks. Any content not already
in a defined block will be captured and put into a special block named content. When a view contains
a call to extend(), execution continues to the bottom of the current view file. Once it is complete, the
extended view will be rendered. Calling extend() more than once in a view file will override the parent
view that will be processed next:
$this->extend(/Common/view);
$this->extend(/Common/index);
The above will result in /Common/index.ctp being rendered as the parent view to the current view.
You can nest extended views as many times as necessary. Each view can extend another view if desired.
Each parent view will get the previous views content as the content block.
Note: You should avoid using content as a block name in your application. CakePHP uses this for
uncaptured content in extended views.
You can also append into a block using start() multiple times. assign() can be used to clear or
overwrite a block at any time:
// Clear the previous content from the sidebar block.
$this->assign(sidebar, );
In 2.3, a few new methods were added for working with blocks. The prepend() method was added to
prepend content to an existing block:
// Prepend to sidebar
$this->prepend(sidebar, this content goes on top of sidebar);
83
The method startIfEmpty() can be used to start a block only if it is empty or undefined. If the block
already exists, the captured content will be discarded. This is useful when you want to conditionally define
default content for a block if it does not already exist:
// In a view file.
// Create a navbar block
$this->startIfEmpty(navbar);
echo $this->element(navbar);
echo $this->element(notifications);
$this->end();
// In
<?php
<p>If
<?php
a parent view/layout
$this->startIfEmpty(navbar); ?>
the block is not defined by now - show this instead</p>
$this->end(); ?>
In the above example, the navbar block will only contain the content added in the first section. Since the
block was defined in the child view, the default content with the <p> tag will be discarded.
Note: You should avoid using content as a block name. This is used by CakePHP internally for extended
views, and view content in the layout.
Displaying blocks
New in version 2.1.
You can display blocks using the fetch() method. fetch() will safely output a block, returning if a
block does not exist:
echo $this->fetch(sidebar);
You can also use fetch to conditionally show content that should surround a block should it exist. This is
helpful in layouts, or extended views where you want to conditionally show headings or other markup:
// in app/View/Layouts/default.ctp
<?php if ($this->fetch(menu)): ?>
<div class="menu">
<h3>Menu options</h3>
<?php echo $this->fetch(menu); ?>
</div>
<?php endif; ?>
As of 2.3.0, you can also provide a default value for a block should it not have any content. This allows
you to easily add placeholder content for empty states. You can provide a default value using the second
argument:
<div class="shopping-cart">
<h3>Your Cart</h3>
84
Chapter 5. Views
The HtmlHelper also allows you to control which block the scripts and CSS go to:
// in your view
$this->Html->script(carousel, array(block => scriptBottom));
// in your layout
echo $this->fetch(scriptBottom);
Layouts
A layout contains presentation code that wraps around a view. Anything you want to see in all of your views
should be placed in a layout.
CakePHPs default layout is located at /app/View/Layouts/default.ctp. If you want to change
the overall look of your application, then this is the right place to start, because controller-rendered view
code is placed inside of the default layout when the page is rendered.
Other layout files should be placed in /app/View/Layouts. When you create a layout, you need to tell
CakePHP where to place the output of your views. To do so, make sure your layout includes a place for
$this->fetch(content) Heres an example of what a default layout might look like:
Layouts
85
<!DOCTYPE html>
<html lang="en">
<head>
<title><?php echo $this->fetch(title); ?></title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<!-- Include external files and scripts here (See HTML helper for more info.) -->
<?php
echo $this->fetch(meta);
echo $this->fetch(css);
echo $this->fetch(script);
?>
</head>
<body>
<!-- If youd like some sort of menu to
show up on all of your views, include it here -->
<div id="header">
<div id="menu">...</div>
</div>
<!-- Heres where I want my views to be displayed -->
<?php echo $this->fetch(content); ?>
<!-- Add a footer to each displayed page -->
<div id="footer">...</div>
</body>
</html>
Note: Prior to version 2.1, method fetch() was not available, fetch(content) is a replacement for
$content_for_layout and lines fetch(meta), fetch(css) and fetch(script)
are contained in the $scripts_for_layout variable in version 2.0
The script, css and meta blocks contain any content defined in the views using the built-in HTML
helper. Useful for including JavaScript and CSS files from views.
Note: When using HtmlHelper::css() or HtmlHelper::script() in view files, specify false
for the inline option to place the HTML source in a block with the same name. (See API for more details
on usage).
The content block contains the contents of the rendered view.
$title_for_layout contains the page title. This variable is generated automatically, but you can
override it by setting it in your controller/view.
Note: The $title_for_layout is deprecated as of 2.5, use $this->fetch(title) in your
layout and $this->assign(title, page title) instead.
Setting the title for the layout is easiest to do in the controller, setting the $title_for_layout variable:
86
Chapter 5. Views
You can also set the title_for_layout variable from inside the view file:
$this->set(title_for_layout, $titleContent);
You can create as many layouts as you wish: just place them in the app/View/Layouts directory, and
switch between them inside of your controller actions using the controller or views $layout property:
// from a controller
public function admin_view() {
// stuff
$this->layout = admin;
}
// from a view file
$this->layout = loggedin;
For example, if a section of my site included a smaller ad banner space, I might create a new layout with the
smaller advertising space and specify it as the layout for all controllers actions using something like:
class UsersController extends AppController {
public function view_active() {
$this->set(title_for_layout, View Active Users);
$this->layout = default_small_ad;
}
public function view_image() {
$this->layout = image;
//output user image
}
}
CakePHP features two core layouts (besides CakePHPs default layout) you can use in your own application:
ajax and flash. The Ajax layout is handy for crafting AJAX responses - its an empty layout. (Most AJAX
calls only require a bit of markup in return, rather than a fully-rendered interface.) The flash layout is used
for messages shown by Controller::flash() method.
Three other layouts, xml, js, and rss, exist in the core for a quick and easy way to serve up content that isnt
text/html.
Layouts
87
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 miniview that can be included in other views, in layouts, and even within other elements. Elements can be used
to make a view more readable, placing the rendering of repeating elements in its own file. They can also
help you re-use content fragments in your application.
Elements live in the /app/View/Elements/ folder, and have the .ctp filename extension. They are
output using the element method of the view:
echo $this->element(helpbox);
Inside the element file, all the passed variables are available as members of the parameter array (in the
same way that Controller::set() in the controller works with view files). In the above example, the
/app/View/Elements/helpbox.ctp file can use the $helptext variable:
// inside app/View/Elements/helpbox.ctp
echo $helptext; //outputs "Oh, this text is very helpful."
The View::element() method also supports options for the element. The options supported are cache
and callbacks. An example:
echo $this->element(helpbox, array(
"helptext" => "This is passed to the element as $helptext",
"foobar" => "This is passed to the element as $foobar",
),
array(
// uses the "long_view" cache configuration
"cache" => "long_view",
// set to true to have before/afterRender called for the element
"callbacks" => true
88
Chapter 5. Views
)
);
Element caching is facilitated through the Cache class. You can configure elements to be stored in any
Cache configuration youve set up. This gives you a great amount of flexibility to decide where and for
how long elements are stored. To cache different versions of the same element in an application, provide a
unique cache key value using the following format:
$this->element(helpbox, array(), array(
"cache" => array(config => short, key => unique value)
)
);
You can take full advantage of elements by using requestAction(), which fetches view variables from
a controller action and returns them as an array. This enables your elements to perform in true MVC style.
Create a controller action that prepares the view variables for your elements, then call requestAction()
inside the second parameter of element() to feed the element the view variables from your controller.
To do this, in your controller add something like the following for the Post example:
class PostsController extends AppController {
// ...
public function index() {
$posts = $this->paginate();
if ($this->request->is(requested)) {
return $posts;
}
$this->set(posts, $posts);
}
}
And then in the element we can access the paginated posts model. To get the latest five posts in an ordered
list, we would do something like the following:
<h2>Latest Posts</h2>
<?php
$posts = $this->requestAction(
posts/index/sort:created/direction:asc/limit:5
);
?>
<ol>
<?php foreach ($posts as $post): ?>
<li><?php echo $post[Post][title]; ?></li>
<?php endforeach; ?>
</ol>
Caching Elements
You can take advantage of CakePHP view caching if you supply a cache parameter. If set to true, it will
cache the element in the default Cache configuration. Otherwise, you can set which cache configuration
should be used. See Caching for more information on configuring Cache. A simple example of caching an
element would be:
Elements
89
If you render the same element more than once in a view and have caching enabled, be sure to set the
key parameter to a different name each time. This will prevent each successive call from overwriting the
previous element() calls cached result. For example:
echo $this->element(
helpbox,
array(var => $var),
array(cache => array(key => first_use, config => view_long)
);
echo $this->element(
helpbox,
array(var => $differenVar),
array(cache => array(key => second_use, config => view_long)
);
The above will ensure that both element results are cached separately. If you want all element caching to
use the same cache configuration, you can avoid some repetition by setting View::$elementCache to
the cache configuration you want to use. CakePHP will use this configuration when none is given.
2.1
If you are using a plugin and wish to use elements from within the plugin, just use the familiar plugin syntax.
If the view is being rendered for a plugin controller/action, the plugin name will automatically be prefixed
onto all elements used, unless another plugin name is present. If the element doesnt exist in the plugin, it
will look in the main APP folder.
echo $this->element(Contacts.helpbox);
If your view is a part of a plugin, you can omit the plugin name. For example, if you are in the
ContactsController of the Contacts plugin, the following:
echo $this->element(helpbox);
// and
echo $this->element(Contacts.helpbox);
are equivalent and will result in the same element being rendered.
Changed in version 2.1: The $options[plugin] option was deprecated and support for
Plugin.element was added.
90
Chapter 5. Views
For example:
Replacing the render method lets you take full control over how your content is rendered.
View API
class View
View methods are accessible in all view, element and layout files.
$this->method()
Then, in your layout, the $activeMenuButton variable will be available and contain the value
posts.
View::get(string $var, $default = null)
Get the value of a viewVar with the name $var.
As of 2.5, you can provide a default value in case the variable is not already set.
Changed in version 2.5: The $default argument was added in 2.5.
91
View::getVar(string $var)
Gets the value of the viewVar with the name $var.
Deprecated since version 2.3: Use View::get() instead.
View::getVars()
Gets a list of all the available view variables in the current rendering scope. Returns an array of
variable names.
View::element(string $elementPath, array $data, array $options = array())
Renders an element or view partial. See the section on Elements for more information and examples.
View::uuid(string $object, mixed $url)
Generates a unique non-random DOM ID for an object, based on the object type and URL. This
method is often used by helpers that need to generate unique DOM IDs for elements such as the
JsHelper:
$uuid = $this->uuid(
form,
array(controller => posts, action => index)
);
//$uuid contains form0425fe3bad
92
Chapter 5. Views
View::startIfEmpty($name)
Start a block if it is empty. All content in the block will be captured and discarded if the block is
already defined.
New in version 2.3.
View::assign($name, $content)
Assign the value of a block. This will overwrite any existing content. See the section on Using view
blocks for examples.
New in version 2.1.
View::fetch($name, $default = )
Fetch the value of a block. If a block is empty or undefined, will be returned. See the section on
Using view blocks for examples.
New in version 2.1.
View::extend($name)
Extend the current view/element/layout with the named one. See the section on Extending Views for
examples.
New in version 2.1.
property View::$layout
Set the layout the current view will be wrapped in.
property View::$elementCache
The cache configuration used to cache elements. Setting this property will change the default configuration used to cache elements. This default can be overridden using the cache option in the element
method.
property View::$request
An instance of CakeRequest. Use this instance to access information about the current request.
property View::$output
Contains the last rendered content from a view, either the view file, or the layout content.
Deprecated since version 2.1: Use $view->Blocks->get(content); instead.
property View::$Blocks
An instance of ViewBlock. Used to provide view block functionality in view rendering.
New in version 2.1.
93
Changed in version 2.1: Versions previous to 2.1 required setting the $this->viewClass =
Theme. 2.1 removes this requirement as the normal View class supports themes
You can also set or change the theme name within an action or within the beforeFilter or
beforeRender callback functions:
$this->theme = AnotherExample;
Theme view files need to be within the /app/View/Themed/ folder. Within the themed folder, create
a folder using the same name as your theme name. For example, the above theme would be found in
/app/View/Themed/AnotherExample.
Note: It is important to remember that CakePHP expects CamelCase theme names.
Beyond that, the folder structure within the /app/View/Themed/Example/ folder is exactly the same
as /app/View/.
For example, the view file for an edit action of a Posts controller would reside at
/app/View/Themed/Example/Posts/edit.ctp.
Layout files would reside in
/app/View/Themed/Example/Layouts/.
If a view file cant be found in the theme, CakePHP will try to locate the view file in the /app/View/
folder. This way, you can create master view files and simply override them on a case-by-case basis within
your theme folder.
Theme assets
Themes can contain static assets as well as view files. A theme can include any necessary assets in its webroot directory. This allows for easy packaging and distribution of themes. While in development, requests
for theme assets will be handled by Dispatcher. To improve performance for production environments,
its recommended that you either symlink or copy theme assets into the applications webroot. See below
for more information.
To use the new theme webroot create directories like:
app/View/Themed/<themeName>/webroot<path_to_file>
in your theme. The Dispatcher will handle finding the correct theme assets in your view paths.
All of CakePHPs built-in helpers are aware of themes and will create the correct paths automatically. Like
view files, if a file isnt in the theme folder, it will default to the main webroot folder:
//When in a theme with the name of purple_cupcake
$this->Html->css(main.css);
//creates a path like
/theme/purple_cupcake/css/main.css
94
Chapter 5. Views
//and links to
app/View/Themed/PurpleCupcake/webroot/css/main.css
becomes
app/View/Themed/Navy/webroot/css/navy.css
app/webroot/theme/Navy/css/navy.css
becomes
Media Views
class MediaView
Deprecated since version 2.3: Use Sending files instead.
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:
class ExampleController extends AppController {
public function download() {
$this->viewClass = Media;
// Download app/outside_webroot_dir/example.zip
$params = array(
id
=> example.zip,
name
=> example,
download => true,
extension => zip,
path
=> APP . outside_webroot_dir . DS
);
$this->set($params);
}
}
Heres an example of rendering a file whose mime type is not included in the MediaViews $mimeType
array. We are also using a relative path which will default to your app/webroot folder:
95
Settable Parameters
id The ID is the file name as it resides on the file server including the file extension.
name The name allows you to specify an alternate file name to be sent to the user. Specify the name without
the file extension.
download A boolean value indicating whether headers should be set to force download.
extension The file extension. This is matched against an internal list of acceptable mime types. If the
mime type specified is not in the list (or set in the mimeType parameter array), the file will not be
downloaded.
path The folder name, including the final directory separator. The path should be absolute but can be
relative to the app/webroot folder.
mimeType An array with additional mime types to be merged with MediaView internal list of acceptable
mime types.
cache A boolean or integer value - If set to true it will allow browsers to cache the file (defaults to false if
not set); otherwise set it to the number of seconds in the future for when the cache should expire.
96
Chapter 5. Views
Defining _serialize as an array has the added benefit of automatically appending a top-level
<response> element when using XmlView. If you use a string value for _serialize and XmlView,
make sure that your view variable has a single top-level element. Without a single top-level element the Xml
will fail to generate.
More about Views
97
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
98
Chapter 5. Views
create your own helpers, and outline the basic tasks CakePHPs core helpers can help you accomplish.
CakePHP features a number of helpers that aid in view creation. They assist in creating well-formed markup
(including forms), aid in formatting text, times and numbers, and can even speed up AJAX functionality.
For more information on the helpers included in CakePHP, check out the chapter for each helper:
CacheHelper
class CacheHelper(View $view, array $settings = array())
The Cache helper assists in caching entire layouts and views, saving time repetitively retrieving data. View
Caching in CakePHP temporarily stores parsed layouts and views as simple PHP + HTML files. 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. The CacheHelper then uses helper callbacks to process the file and output to generate the cache file.
When a URL is requested, CakePHP 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 CakePHP doesnt find a cached view, or the cache has expired for the requested URL it
continues to process the request normally.
Using the Helper
There are two steps you have to take before you can use the CacheHelper.
First in your
APP/Config/core.php uncomment the Configure write call for Cache.check. This will tell
CakePHP to check for, and generate view cache files when handling requests.
Once youve uncommented the Cache.check line you will need to add the helper to your controllers
$helpers array:
class PostsController extends AppController {
public $helpers = array(Cache);
}
You will also need to add the CacheDispatcher to your dispatcher filters in your bootstrap:
Configure::write(Dispatcher.filters, array(
CacheDispatcher
));
New in version 2.3: If you have a setup with multiple domains or languages you can use Configure::write(Cache.viewPrefix, YOURPREFIX); to store the view cache files prefixed.
Additional configuration options CacheHelper has a few additional configuration options you can use
to tune and tweak its behavior. This is done through the $cacheAction variable in your controllers.
$cacheAction should be set to an array which contains the actions you want cached, and the duration in
seconds you want those views cached. The time value can be expressed in a strtotime() format (e.g.
1 hour, or 3 minutes).
99
Using the example of an ArticlesController, that receives a lot of traffic that needs to be cached:
public $cacheAction = array(
view => 36000,
index => 48000
);
This will cache the view action 10 hours, and the index action 13 hours. By making $cacheAction a
strtotime() friendly value you can cache every action in the controller:
public $cacheAction = "1 hour";
You can also enable controller/component callbacks for cached views created with CacheHelper. To do
so you must use the array format for $cacheAction and create an array like the following:
public $cacheAction = array(
view => array(callbacks => true, duration => 21600),
add => array(callbacks => true, duration => 36000),
index => array(callbacks => true, duration => 48000)
);
By setting callbacks => true you tell CacheHelper that you want the generated files to create the
components and models for the controller. Additionally, fire the component initialize, controller beforeFilter,
and component startup callbacks.
Note: Setting callbacks => true partly defeats the purpose of caching. This is also the reason it is
disabled by default.
There will be times when you dont want an entire view cached. For example, certain parts of the page may
look different whether a user is currently logged in or browsing your site as a guest.
To indicate blocks of content that are not to be cached, wrap them in <!--nocache-->
<!--/nocache--> like so:
<!--nocache-->
<?php if ($this->Session->check(User.name)): ?>
Welcome, <?php echo h($this->Session->read(User.name)); ?>.
<?php else: ?>
<?php echo $this->Html->link(Login, users/login); ?>
<?php endif; ?>
<!--/nocache-->
Note: You cannot use nocache tags in elements. Since there are no callbacks around elements, they
cannot be cached.
It should be noted that once an action is cached, the controller method for the action will not be called. When
a cache file is created, the request object, and view variables are serialized with PHPs serialize().
100
Chapter 5. Views
Warning: If you have view variables that contain un-serializable content such as SimpleXML objects,
resource handles, or closures you might not be able to use view caching.
It is important to remember that CakePHP will clear a cached view if a model used in the cached view is
modified. For example, if a cached view uses data from the Post model, and there has been an INSERT,
UPDATE, or DELETE query made to a Post, the cache for that view is cleared, and new content is generated
on the next request.
Note: This automatic cache clearing requires the controller/model name to be part of the URL. If youve
used routing to change your URLs this feature will not work.
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().
FormHelper
class FormHelper(View $view, array $settings = array())
The FormHelper does most of the heavy lifting in form creation. 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 using conventions, or you can use specific methods to get only what
you need.
Creating Forms
The first method youll need to use in order to take advantage of the FormHelper is create(). This special
method outputs an opening form tag.
FormHelper::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 the current URL. The default method for
form submission is POST. The form element is also 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, Id see something like the following output in the rendered
view:
<form id="UserAddForm" method="post" action="/users/add">
Note:
You can also pass false for $model.
This will place your form
data into the array:
$this->request->data (instead of in the sub-array:
$this->request->data[Model]). This can be handy for short forms that may not
represent anything in your database.
101
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 forms 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:
// If you are on /recipes/add
echo $this->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->request->data property to automatically detect whether to create an add or edit form. If $this->request->data
contains an array element named after the forms model, and that array contains a non-empty value of
the models primary key, then the FormHelper will create an edit form for that record. For example,
if we browse to http://site.com/recipes/edit/5, we would get the following:
// Controller/RecipesController.php:
public function edit($id = null) {
if (empty($this->request->data)) {
$this->request->data = $this->Recipe->findById($id);
} else {
// Save logic goes here
}
}
// View/Recipes/edit.ctp:
// Since $this->request->data[Recipe][id] = 5,
// we will get an edit form
<?php echo $this->Form->create(Recipe); ?>
Output:
<form id="RecipeEditForm" method="post" action="/recipes/edit/5">
<input type="hidden" name="_method" value="PUT" />
Note: Since this is an edit form, a hidden input field is generated to override the default HTTP
method.
When creating forms for models in plugins, you should always use plugin syntax when creating a
form. This will ensure the form is correctly generated:
echo $this->Form->create(ContactManager.Contact);
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.
Changed in version 2.0: The default URL for all forms, is now the current URL including passed,
named, and querystring parameters. You can override this default by supplying $options[url]
in the second parameter of $this->Form->create().
102
Chapter 5. Views
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:
echo $this->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.
$options[action] The action key allows you to point the form to a specific action in your
current controller. For example, if youd like to point the form to the login() action of the current
controller, you would supply an $options array like the following:
echo $this->Form->create(User, array(action => login));
Output:
<form id="UserLoginForm" method="post" action="/users/login">
$options[url] If the desired form action isnt 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:
echo $this->Form->create(null, array(
url => array(controller => recipes, action => add)
));
Output:
<form method="post" action="/recipes/add">
103
Output:
<form method="get" action="http://www.google.com/search">
Also check HtmlHelper::url() method for more examples of different types of URLs.
$options[default] If default has been set to boolean false, the forms 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 forms default behavior so you can grab
the data and submit it via AJAX instead.
$options[inputDefaults] You can declare a set of default options for input() with the
inputDefaults key to customize your default input creation:
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:
echo $this->Form->input(password); // No div, no label
// has a label element
echo $this->Form->input(
username,
array(label => Username)
);
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:
<?php echo $this->Form->end(Finish); ?>
104
Chapter 5. Views
Will output:
<div class="submit">
<input type="submit" value="Finish" />
</div>
</form>
Will output:
<div class="glass-pill"><input type="submit" value="Update" name="Update">
</div>
There are a few ways to create form inputs with the FormHelper. Well start by looking at input(). This
method will automatically inspect the model field it has been supplied in order to create an appropriate input
for that field. Internally input() delegates to other methods in FormHelper.
FormHelper::input(string $fieldName, array $options = array())
Creates the following elements given a particular Model.field:
Wrapping div.
Label element
Input element(s)
Error element with message if applicable.
The type of input created depends on the column datatype:
Column Type Resulting Form Field
string (char, varchar, etc.) text
boolean, tinyint(1) checkbox
1
http://api.cakephp.org/2.4/class-FormHelper.html
105
text textarea
text, with name of password, passwd, or psword password
text, with name of email email
text, with name of tel, telephone, or phone tel
date day, month, and year selects
datetime, timestamp day, month, year, hour, minute, and meridian selects
time hour, minute, and meridian selects
binary file
The $options parameter allows you to customize how input() works, and finely control what is
generated.
The wrapping div will have a required class name appended if the validation rules for the Models
field do not specify allowEmpty => true. One limitation of this behavior is the fields model
must have been loaded during this request. Or be directly associated to the model supplied to
create().
New in version 2.5: The binary type now maps to a file input.
New in version 2.3. Since 2.3 the HTML5 required attribute will also be added to the input based
on validation rules. You can explicitly set required key in options array to override it for a field. To
skip browser validation triggering for the whole form you can set option formnovalidate =>
true for the input button you generate using FormHelper::submit() or set novalidate
=> true in options for FormHelper::create().
For example, lets assume that your User model includes fields for a username (varchar), password
(varchar), approved (datetime) and quote (text). You can use the input() method of the FormHelper to
create appropriate inputs for all of these form fields:
echo $this->Form->create();
echo $this->Form->input(username);
echo $this->Form->input(password);
echo $this->Form->input(approved);
echo $this->Form->input(quote);
//text
//password
//day, month, year, hour, minute,
//meridian
//textarea
echo $this->Form->end(Add);
106
Chapter 5. Views
Besides the specific options for input() found below, you can specify any option for the input type & any HTML attribute (for instance onfocus). For more information on $options and
$htmlAttributes see HtmlHelper.
Assuming that User hasAndBelongsToMany Group. In your controller, set a camelCase plural variable (group -> groups in this case, or ExtraFunkyModel -> extraFunkyModels) with the select options.
In the controller action you would put the following:
$this->set(groups, $this->User->Group->find(list));
And in the view a multiple select can be created with this simple code:
echo $this->Form->input(Group);
If you want to create a select field while using a belongsTo - or hasOne - Relation, you can add the
following to your Users-controller (assuming your User belongsTo Group):
$this->set(groups, $this->User->Group->find(list));
If your model name consists of two or more words, e.g., UserGroup, when passing the data using
set() you should name your data in a pluralised and camelCased format as follows:
$this->set(userGroups, $this->UserGroup->find(list));
// or
$this->set(
reallyInappropriateModelNames,
$this->ReallyInappropriateModelName->find(list)
);
Note:
Try to avoid using FormHelper::input() to generate submit buttons.
FormHelper::submit() instead.
Use
In addition to fields control, inputs() allows you to use a few additional options.
fieldset Set to false to disable the fieldset. If a string is supplied it will be used as the class
name for the fieldset element.
More about Views
107
legend Set to false to disable the legend for the generated input set. Or supply a string to
customize the legend text.
Field naming conventions The Form helper is pretty smart. Whenever you specify a field name with the
form helper methods, itll automatically use the current model name to build an input with a format like the
following:
<input type="text" id="ModelnameFieldname" name="data[Modelname][fieldname]">
This allows you to omit the model name when generating inputs for the model that the form was created for.
You can create inputs for associated models, or arbitrary models by passing in Modelname.fieldname as the
first parameter:
echo $this->Form->input(Modelname.fieldname);
If you need to specify multiple fields using the same field name, thus creating an array that can be saved in
one shot with saveAll(), use the following convention:
echo $this->Form->input(Modelname.0.fieldname);
echo $this->Form->input(Modelname.1.fieldname);
Output:
<input type="text" id="Modelname0Fieldname"
name="data[Modelname][0][fieldname]">
<input type="text" id="Modelname1Fieldname"
name="data[Modelname][1][fieldname]">
FormHelper uses several field-suffixes internally for datetime input creation. If you are using fields named
year, month, day, hour, minute, or meridian and having issues getting the correct input, you can
set the name attribute to override the default behavior:
echo $this->Form->input(Model.year, array(
type => text,
name => data[Model][year]
));
Options FormHelper::input() supports a large number of options. In addition to its own options
input() accepts options for the generated input types, as well as HTML attributes. The following will
cover the options specific to FormHelper::input().
$options[type] You can force the type of an input, overriding model introspection, by specifying a type. In addition to the field types found in the Creating form elements, you can also create
file, password, and any type supported by HTML5:
echo $this->Form->input(field, array(type => file));
echo $this->Form->input(email, array(type => email));
Output:
108
Chapter 5. Views
$options[div] Use this option to set attributes of the inputs containing div. Using a string
value will set the divs class name. An array will set the divs attributes to those specified by the
arrays keys/values. Alternatively, you can set this key to false to disable the output of the div.
Setting the class name:
echo $this->Form->input(User.name, array(
div => class_name
));
Output:
<div class="class_name">
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
Output:
<div class="input text" id="mainDiv" title="Div Title"
style="display:block">
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
Output:
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
$options[label] Set this key to the string you would like to be displayed within the label that
usually accompanies the input:
More about Views
109
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:
echo $this->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:
echo $this->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>
$options[error] 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.
To disable error message output & field classes set the error key to false:
$this->Form->input(Model.field, array(error => false));
To disable only the error message, but retain the field classes, set the errorMessage key to false:
$this->Form->input(Model.field, array(errorMessage => false));
To modify the wrapping element type and its class, use the following format:
110
Chapter 5. Views
$this->Form->input(Model.field, array(
error => array(
attributes => array(wrap => span, class => bzzz)
)
));
To prevent HTML being automatically escaped in the error message output, set the escape suboption
to false:
$this->Form->input(Model.field, array(
error => array(
attributes => array(escape => false)
)
));
To override the model error messages use an array with the keys matching the validation rule names:
$this->Form->input(Model.field, array(
error => array(tooShort => __(This is not long enough))
));
As seen above you can set the error message for each validation rule you have in your models. In
addition you can provide i18n messages for your forms.
New in version 2.3: Support for the errorMessage option was added in 2.3
$options[before],
$options[after]
$options[between],
$options[separator],
and
Use these keys if you need to inject some markup inside the output of the input() method:
echo $this->Form->input(field, array(
before => --before--,
after => --after--,
between => --between---
));
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 inputs the separator attribute can be used to inject markup to separate each input/label pair:
echo $this->Form->input(field, array(
before => --before--,
after => --after--,
between => --between---,
separator => --separator--,
111
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 -.
$options[format] The ordering of the HTML generated by FormHelper is controllable as
well. The format options supports an array of strings describing the template you would like said
element to follow. The supported array keys are: array(before, input, between,
label, after,error).
$options[inputDefaults] If you find yourself repeating the same options in multiple input() calls, you can use inputDefaults to keep your code dry:
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:
// No div, no label
echo $this->Form->input(password);
// has a label element
echo $this->Form->input(username, array(label => Username));
If you need to later change the defaults you can use FormHelper::inputDefaults().
Generating specific types of inputs
In addition to the generic input() method, FormHelper has specific methods for generating a number
of different types of inputs. These can be used to generate just the input widget itself, and combined with
other methods like label() and error() to generate fully custom form layouts.
112
Chapter 5. Views
Common options Many of the various input element methods support a common set of options. All of
these options are also supported by input(). To reduce repetition the common options shared by all input
methods are as follows:
$options[class] You can set the class name for an input:
echo $this->Form->input(title, array(class => custom-class));
$options[id] Set this key to force the value of the DOM id for the input.
$options[default] 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:
echo $this->Form->input(ingredient, array(default => Sugar));
Note:
You cannot use default to check a checkbox - instead you might set the value in
$this->request->data in your controller, or set the input option checked to true.
Date and datetime fields default values can be set by using the selected key.
Beware of using false to assign a default value. A false value is used to disable/exclude options of
an input field, so default => false would not set any value at all. Instead use default
=> 0.
In addition to the above options, you can mixin any HTML attribute you wish to use. Any non-special
option name will be treated as an HTML attribute, and applied to the generated HTML input element.
Options for select, checkbox and radio inputs
$options[selected] 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:
echo $this->Form->input(close_time, array(
type => time,
selected => 13:30:00
));
Note: The selected key for date and datetime inputs may also be a UNIX timestamp.
$options[empty] If set to true, forces the input to remain empty.
113
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:
echo $this->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>
Note: If you need to set the default value in a password field to blank, use value => instead.
Options can also supplied as key-value pairs.
$options[hiddenField] For certain input types (checkboxes, radios) a hidden input is created so that the key in $this->request->data will exist even without a value specified:
<input type="hidden" name="data[Post][Published]" id="PostPublished_"
value="0" />
<input type="checkbox" name="data[Post][Published]" value="1"
id="PostPublished" />
Which outputs:
<input type="checkbox" name="data[Post][Published]" value="1"
id="PostPublished" />
If you want to create multiple blocks of inputs on a form that are all grouped together, you should use
this parameter on all inputs except the first. If the hidden input is on the page in multiple places, only
the last group of inputs values will be saved
In this example, only the tertiary colors would be passed, and the primary colors would be overridden:
<h2>Primary Colors</h2>
<input type="hidden" name="data[Color][Color]" id="Colors_" value="0" />
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsRed" />
114
Chapter 5. Views
<label for="ColorsRed">Red</label>
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsBlue" />
<label for="ColorsBlue">Blue</label>
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsYellow" />
<label for="ColorsYellow">Yellow</label>
<h2>Tertiary Colors</h2>
<input type="hidden" name="data[Color][Color]" id="Colors_" value="0" />
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsGreen" />
<label for="ColorsGreen">Green</label>
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsPurple" />
<label for="ColorsPurple">Purple</label>
<input type="checkbox" name="data[Addon][Addon][]" value="5"
id="ColorsOrange" />
<label for="ColorsOrange">Orange</label>
Disabling the hiddenField on the second input group would prevent this behavior.
You can set a different hidden field value other than 0 such as N:
echo $this->Form->checkbox(published, array(
value => Y,
hiddenField => N,
));
Datetime options
$options[timeFormat] Used to specify the format of the select inputs for a time-related set
of inputs. Valid values include 12, 24, and null.
$options[dateFormat] Used to specify the format of the select inputs for a date-related set
of inputs. Valid values include any combination of D, M and Y or null. The inputs will be put
in the order defined by the dateFormat option.
$options[minYear], $options[maxYear] Used in combination with a
date/datetime input. Defines the lower and/or upper end of values shown in the years select
field.
$options[orderYear] Used in combination with a date/datetime input. Defines the order in
which the year values will be set. Valid values include asc, desc. The default value is desc.
$options[interval] This option specifies the number of minutes between each option in the
minutes select box:
echo $this->Form->input(Model.time, array(
type => time,
interval => 15
));
Would create 4 options in the minute select. One for each 15 minutes.
More about Views
115
All elements are created under a form for the User model as in the examples above. For this
reason, the HTML code generated will contain attributes that reference to the User model. Ex:
name=data[User][username], id=UserUsername
FormHelper::label(string $fieldName, string $text, array $options)
Create a label element. $fieldName is used for generating the DOM id. If $text is undefined,
$fieldName will be used to inflect the labels text:
echo $this->Form->label(User.name);
echo $this->Form->label(User.name, Your username);
Output:
<label for="UserName">Name</label>
<label for="UserName">Your username</label>
$options can either be an array of HTML attributes, or a string that will be used as a class name:
echo $this->Form->label(User.name, null, array(id => user-label));
echo $this->Form->label(User.name, Your username, highlight);
Output:
<label for="UserName" id="user-label">Name</label>
<label for="UserName" class="highlight">Your username</label>
Will output:
<input name="data[User][username]" type="text" class="users"
id="UserUsername" />
Will output:
116
Chapter 5. Views
Will output:
<input name="data[User][id]" id="UserId" type="hidden" />
If the form is edited (that is, the array $this->request->data will contain the information
saved for the User model), the value corresponding to id field will automatically be added to the
HTML generated. Example for data[User][id] = 10:
<input name="data[User][id]" id="UserId" type="hidden" />
Changed in version 2.0: Hidden fields no longer remove the class attribute. This means that if there
are validation errors on hidden fields, the error-field class name will be applied.
FormHelper::textarea(string $fieldName, array $options)
Creates a textarea input field.
echo $this->Form->textarea(notes);
Will output:
<textarea name="data[User][notes]" id="UserNotes"></textarea>
If the form is edited (that is, the array $this->request->data will contain the information
saved for the User model), the value corresponding to notes field will automatically be added to
the HTML generated. Example:
<textarea name="data[User][notes]" id="UserNotes">
This text is to be edited.
</textarea>
Note: The textarea input type allows for the $options attribute of escape which determines whether or not the contents of the textarea should be escaped. Defaults to true.
echo $this->Form->textarea(notes, array(escape => false);
// OR....
echo $this->Form->input(
notes,
array(type => textarea, escape => false)
);
Options
In addition to the Common options, textarea() supports a few specific options:
$options[rows], $options[cols] These two keys specify the number of rows
and columns:
More about Views
117
echo $this->Form->textarea(
textarea,
array(rows => 5, cols => 5)
);
Output:
<textarea name="data[Form][textarea]" cols="5" rows="5" id="FormTextarea">
</textarea>
Will output:
<input type="hidden" name="data[User][done]" value="0" id="UserDone_" />
<input type="checkbox" name="data[User][done]" value="1" id="UserDone" />
It is possible to specify the value of the checkbox by using the $options array:
echo $this->Form->checkbox(done, array(value => 555));
Will output:
<input type="hidden" name="data[User][done]" value="0" id="UserDone_" />
<input type="checkbox" name="data[User][done]" value="555" id="UserDone" />
Will output:
<input type="checkbox" name="data[User][done]" value="1" id="UserDone" />
118
Chapter 5. Views
Will output:
<input name="data[User][gender]" id="UserGender_" value=""
type="hidden" />
<input name="data[User][gender]" id="UserGenderM" value="M"
type="radio" />
<label for="UserGenderM">Male</label>
<input name="data[User][gender]" id="UserGenderF" value="F"
type="radio" />
<label for="UserGenderF">Female</label>
If for some reason you dont want the hidden input, setting $attributes[value] to a selected
value or boolean false will do just that.
Changed in version 2.1: The $attributes[disabled] option was added in 2.1.
FormHelper::select(string $fieldName, array $options, array $attributes)
Creates a select element, populated with the items in $options, with the option specified
by $attributes[value] shown as selected by default. Set the empty key in the
$attributes variable to false to turn off the default empty option:
$options = array(M => Male, F => Female);
echo $this->Form->select(gender, $options);
Will output:
<select name="data[User][gender]" id="UserGender">
<option value=""></option>
<option value="M">Male</option>
<option value="F">Female</option>
</select>
The select input type allows for a special $option attribute called escape which accepts a
bool and determines whether to HTML entity encode the contents of the select options. Defaults to
true:
$options = array(M => Male, F => Female);
echo $this->Form->select(gender, $options, array(escape => false));
$attributes[options] 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:
echo $this->Form->select(field, array(1,2,3,4,5));
Output:
<select name="data[User][field]" id="UserField">
<option value="0">1</option>
119
<option
<option
<option
<option
</select>
value="1">2</option>
value="2">3</option>
value="3">4</option>
value="4">5</option>
Output:
<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>
If you would like to generate a select with optgroups, just pass data in hierarchical format. This
works on multiple checkboxes and radio buttons too, but instead of optgroups wraps elements in
fieldsets:
$options = array(
Group 1 => array(
Value 1 => Label 1,
Value 2 => Label 2
),
Group 2 => array(
Value 3 => Label 3
)
);
echo $this->Form->select(field, $options);
Output:
<select name="data[User][field]" id="UserField">
<optgroup label="Group 1">
<option value="Value 1">Label 1</option>
<option value="Value 2">Label 2</option>
</optgroup>
<optgroup label="Group 2">
<option value="Value 3">Label 3</option>
</optgroup>
</select>
$attributes[multiple] If multiple has been set to true for an input that outputs a
select, the select will allow multiple selections:
echo $this->Form->select(
Model.field,
120
Chapter 5. Views
$options,
array(multiple => true)
);
Output:
<div class="input select">
<label for="ModelField">Field</label>
<input name="data[Model][field]" value="" id="ModelField"
type="hidden">
<div class="checkbox">
<input name="data[Model][field][]" value="Value 1"
id="ModelField1" type="checkbox">
<label for="ModelField1">Label 1</label>
</div>
<div class="checkbox">
<input name="data[Model][field][]" value="Value 2"
id="ModelField2" type="checkbox">
<label for="ModelField2">Label 2</label>
</div>
</div>
Output:
<div class="input select">
<label for="ModelField">Field</label>
<input name="data[Model][field]" value="" id="ModelField"
type="hidden">
<div class="checkbox">
<input name="data[Model][field][]" disabled="disabled"
value="Value 1" id="ModelField1" type="checkbox">
<label for="ModelField1">Label 1</label>
121
</div>
<div class="checkbox">
<input name="data[Model][field][]" value="Value 2"
id="ModelField2" type="checkbox">
<label for="ModelField2">Label 2</label>
</div>
</div>
Changed in version 2.3: Support for arrays in $attributes[disabled] was added in 2.3.
FormHelper::file(string $fieldName, array $options)
To add a file upload field to a form, you must first make sure that the form enctype is set to
multipart/form-data, so start off with a create function such as the following:
echo $this->Form->create(Document, array(
enctype => multipart/form-data
));
// OR
echo $this->Form->create(Document, array(type => file));
Next add either of the two lines to your form view file:
echo $this->Form->input(Document.submittedfile, array(
between => <br />,
type => file
));
// OR
echo $this->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:
$this->request->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 uploads2 .
2
http://php.net/features.file-upload
122
Chapter 5. Views
Validating Uploads Below is an example validation method you could define in your model to validate
whether a file has been successfully uploaded:
public 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;
}
Will output:
<form enctype="multipart/form-data" method="post" action="/users/add">
<input name="data[User][avatar]" value="" id="UserAvatar" type="file">
Note: When using $this->Form->file(), remember to set the form encoding-type, by setting the
type option to file in $this->Form->create()
Will output:
<div class="submit"><input value="Submit" type="submit"></div>
You can also pass a relative or absolute URL to an image for the caption parameter instead of caption
text.
echo $this->Form->submit(ok.png);
Will output:
<div class="submit"><input type="image" src="/img/ok.png"></div>
Setting
123
$this->Form->button(A Button);
$this->Form->button(Another Button, array(type => button));
$this->Form->button(Reset the Form, array(type => reset));
$this->Form->button(Submit Form, array(type => submit));
Will output:
<button
<button
<button
<button
type="submit">A Button</button>
type="button">Another Button</button>
type="reset">Reset the Form</button>
type="submit">Submit Form</button>
The button input type supports the escape option, which accepts a bool and determines whether
to HTML entity encode the $title of the button. Defaults to false:
echo $this->Form->button(Submit Form, array(
type => submit,
escape => true
));
124
Chapter 5. Views
Will output:
<select name="data[User][purchased][year]" id="UserPurchasedYear">
<option value=""></option>
<option value="2009">2009</option>
<option value="2008">2008</option>
<option value="2007">2007</option>
<option value="2006">2006</option>
<option value="2005">2005</option>
<option value="2004">2004</option>
<option value="2003">2003</option>
<option value="2002">2002</option>
<option value="2001">2001</option>
<option value="2000">2000</option>
</select>
Will output:
<select
<option
<option
<option
<option
<option
<option
<option
<option
<option
<option
<option
<option
name="data[User][mob][month]" id="UserMobMonth">
value=""></option>
value="01">January</option>
value="02">February</option>
value="03">March</option>
value="04">April</option>
value="05">May</option>
value="06">June</option>
value="07">July</option>
value="08">August</option>
value="09">September</option>
value="10">October</option>
value="11">November</option>
125
<option value="12">December</option>
</select>
You can pass in your own array of months to be used by setting the monthNames attribute, or have
months displayed as numbers by passing false. (Note: the default months are internationalized and
can be translated using localization.):
echo $this->Form->month(mob, array(monthNames => false));
Will output:
<select name="data[User][created][day]" id="UserCreatedDay">
<option value=""></option>
<option value="01">1</option>
<option value="02">2</option>
<option value="03">3</option>
...
<option value="31">31</option>
</select>
Chapter 5. Views
if ($this->Form->isFieldError(gender)) {
echo $this->Form->error(gender);
}
All inputs created from that point forward will inherit the options declared in inputDefaults. You can override
the default options by declaring the option in the input() call:
echo $this->Form->input(password); // No div, no label with class fancy
// has a label element same defaults
echo $this->Form->input(
username,
array(label => Username)
);
SecurityComponent offers several features that make your forms safer and more secure. By simply
including the SecurityComponent in your controller, youll automatically benefit from CSRF and form
tampering features.
As mentioned previously when using SecurityComponent, you should always close your forms using
FormHelper::end(). This will ensure that the special _Token inputs are generated.
FormHelper::unlockField($name)
Unlocks a field making it exempt from the SecurityComponent field hashing. This also allows
the fields to be manipulated by JavaScript. The $name parameter should be the entity name for the
input:
127
$this->Form->unlockField(User.id);
128
Chapter 5. Views
Note: The HtmlHelper is available in all views by default. If youre getting an error informing you that
it isnt there, its usually due to its name being missing from a manually configured $helpers controller
variable.
The most important task the HtmlHelper accomplishes is creating well formed markup. Dont be afraid to
use it often - you can cache views in CakePHP in order to save some CPU cycles when views are being
rendered and delivered. This section will cover some of the methods of the HtmlHelper and how to use
them.
HtmlHelper::charset($charset=null)
Parameters
$charset (string) Desired character set. If null, the value of App.encoding
will be used.
Used to create a meta tag specifying the documents character. Defaults to UTF-8
Example use:
echo $this->Html->charset();
Will output:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
Alternatively,
echo $this->Html->charset(ISO-8859-1);
Will output:
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
129
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 css block which you can print inside the head tag of the document.
You can use the block option to control which block the link element will be appended to. By default
it will append to the css block.
If key rel in $options array is set to import the stylesheet will be imported.
This method of CSS inclusion assumes that the CSS file specified resides inside the /app/webroot/css
directory if path doesnt start with a /.
echo $this->Html->css(forms);
Will output:
<link rel="stylesheet" type="text/css" href="/css/forms.css" />
Will output:
<link rel="stylesheet" type="text/css" href="/css/forms.css" />
<link rel="stylesheet" type="text/css" href="/css/tables.css" />
<link rel="stylesheet" type="text/css" href="/css/menu.css" />
You can include CSS files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/css/toolbar.css You could use the following:
echo $this->Html->css(DebugKit.toolbar.css);
If you want to include a CSS file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/css/Blog.common.css, you would:
.. versionchanged:: 2.4
130
Chapter 5. Views
If you set the type attribute using the $attributes parameter, CakePHP contains a few shortcuts:
type
html
rss
atom
icon
translated value
text/html
application/rss+xml
application/atom+xml
image/x-icon
<?php
echo $this->Html->meta(
favicon.ico,
/favicon.ico,
array(type => icon)
);
?>
// Output (line breaks added)
<link
href="http://example.com/favicon.ico"
title="favicon.ico" type="image/x-icon"
rel="alternate"
/>
<?php
echo $this->Html->meta(
Comments,
/comments/index.rss,
array(type => rss)
);
?>
// Output (line breaks added)
<link
href="http://example.com/comments/index.rss"
title="Comments"
type="application/rss+xml"
rel="alternate"
/>
This method can also be used to add the meta keywords and descriptions. Example:
<?php
echo $this->Html->meta(
keywords,
enter any meta keyword here
);
?>
// Output
<meta name="keywords" content="enter any meta keyword here" />
<?php
echo $this->Html->meta(
description,
enter any meta description here
);
?>
// Output
131
If you want to add a custom meta tag then the first parameter should be set to an array. To output a
robots noindex tag use the following code:
echo $this->Html->meta(array(name => robots, content => noindex));
translated value
HTML4 Strict
HTML4 Transitional
HTML4 Frameset
HTML5
XHTML1 Strict
XHTML1 Transitional
XHTML1 Frameset
XHTML1.1
echo $this->Html->docType();
// Outputs:
// <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
//
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
echo $this->Html->docType(html5);
// Outputs: <!DOCTYPE html>
echo $this->Html->docType(html4-trans);
// Outputs:
// <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
//
"http://www.w3.org/TR/html4/loose.dtd">
132
Chapter 5. Views
Will output:
background:#633; border-bottom:1px solid #000; padding:10px;
Will output:
<img src="/img/cake_logo.png" alt="CakePHP" />
To create an image link specify the link destination using the url option in $htmlAttributes.
echo $this->Html->image("recipes/6.jpg", array(
"alt" => "Brownies",
url => array(controller => recipes, action => view, 6)
));
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
If you are creating images in emails, or want absolute paths to images you can use the fullBase
option:
echo $this->Html->image("logo.png", array(fullBase => true));
Will output:
<img src="http://example.com/img/logo.jpg" alt="" />
You can include image files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/img/icon.png You could use the following:
echo $this->Html->image(DebugKit.icon.png);
If you want to include a image file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/js/Blog.icon.png, you would:
133
Changed in version 2.1: The fullBase option was added. Support for plugin syntax was added.
HtmlHelper::link(string $title, mixed $url = null, array $options = array(), string $confirmMessage = false)
param string $title The text to display as the body of the link.
param mixed $url Either the string location, or a routing array.
param array $options An array of html attributes.
General purpose method for creating HTML links. Use $options to specify attributes
for the element and whether or not the $title should be escaped.
echo $this->Html->link(
Enter,
/pages/home,
array(class => button, target => _blank)
);
Will output:
<a href="/pages/home" class="button" target="_blank">Enter</a>
Will output:
<a href="http://www.yourdomain.com/dashboards/index">Dashboard</a>
Will output:
<a href="/recipes/delete/6"
onclick="return confirm(
Are you sure you wish to delete this recipe?
);">
134
Chapter 5. Views
Delete
</a>
Will output:
<a href="/images/view/1?height=400&width=500">View image</a>
When using named parameters, use the array syntax and include names for ALL parameters
in the URL. Using the string syntax for paramters (i.e. recipes/view/6/comments:false
will result in the colon characters being HTML escaped and the link will not work as
desired.
<?php
echo $this->Html->link(
$this->Html->image("recipes/6.jpg", array("alt" => "Brownies")),
array(
controller => recipes,
action => view,
id => 6,
comments => false
)
);
Will output:
<a href="/recipes/view/id:6/comments:false">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
HTML special characters in $title will be converted to HTML entities. To disable this
conversion, set the escape option to false in the $options array.
<?php
echo $this->Html->link(
$this->Html->image("recipes/6.jpg", array("alt" => "Brownies")),
"recipes/view/6",
array(escape => false)
);
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
135
Setting escape to false will also disable escaping of attributes of the link. As of 2.4 you
can use the option escapeTitle to disable just escaping of title and not the attributes.
<?php
echo $this->Html->link(
$this->Html->image(recipes/6.jpg, array(alt => Brownies)),
recipes/view/6,
array(escapeTitle => false, title => hi "howdy")
);
Will output:
<a href="/recipes/view/6" title="hi "howdy"">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
136
Chapter 5. Views
)); ?>
// Output
<video src="http://www.somehost.com/files/video.mp4">Fallback text</video>
<?php echo $this->Html->media(
array(
video.mp4,
array(
src => video.ogg,
type => "video/ogg; codecs=theora, vorbis"
)
),
array(autoplay)
); ?>
// Output
<video autoplay="autoplay">
<source src="/files/video.mp4" type="video/mp4"/>
<source src="/files/video.ogg" type="video/ogg;
codecs=theora, vorbis"/>
</video>
Note: Text is not escaped by default but you may use $htmlOptions[escape] = true
to escape your text. This replaces a fourth parameter boolean $escape = false that was
available in previous versions.
137
Chapter 5. Views
$options[once] controls whether or not you want to include this script once per request or
more than once. This defaults to true.
You can 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:
echo $this->Html->script(scripts);
Will output:
<script type="text/javascript" href="/js/scripts.js"></script>
You can link to files with absolute paths as well to link files that are not in app/webroot/js:
echo $this->Html->script(/otherdir/script_file);
Will output:
<script type="text/javascript" href="http://code.jquery.com/jquery.min.js">
</script>
Will output:
<script type="text/javascript" href="/js/jquery.js"></script>
<script type="text/javascript" href="/js/wysiwyg.js"></script>
<script type="text/javascript" href="/js/scripts.js"></script>
You can append the script tag to a specific block using the block option:
echo $this->Html->script(wysiwyg, array(block => scriptBottom));
In your layout you can output all the script tags added to scriptBottom:
echo $this->fetch(scriptBottom);
You can include script files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/js/toolbar.js You could use the following:
echo $this->Html->script(DebugKit.toolbar.js);
If you want to include a script file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/js/Blog.plugins.js, you would:
139
Changed in version 2.1: The block option was added. Support for plugin syntax was added.
HtmlHelper::scriptBlock($code, $options = array())
Parameters
$code (string) The code to go in the script tag.
$options (array) An array of html attributes.
Generate a code block containing $code set $options[inline] to false to have the script
block appear in the script view block. Other options defined will be added as attributes to
script tags. $this->Html->scriptBlock(stuff, array(defer => true));
will create a script tag with defer="defer" attribute.
HtmlHelper::scriptStart($options = array())
Parameters
$options (array) An array of html attributes to be used when scriptEnd is called.
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 as scriptBlock()
HtmlHelper::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:
$this->Html->scriptStart(array(inline => false));
echo $this->Js->alert(I am in the javascript);
$this->Html->scriptEnd();
HtmlHelper::nestedList(array $list, array $options = array(), array $itemOptions = array(), string $tag = ul)
Parameters
$list (array) Set of elements to list.
$options (array) Additional HTML attributes of the list (ol/ul) tag or if ul/ol use
that as tag.
$itemOptions (array) Additional HTML attributes of the list item (LI) tag.
$tag (string) Type of list tag to use (ol/ul).
Build a nested list (UL/OL) out of an associative array:
$list = array(
Languages => array(
English => array(
American,
140
Chapter 5. Views
Canadian,
British,
),
Spanish,
German,
)
);
echo $this->Html->nestedList($list);
Output:
// Output (minus the whitespace)
<ul>
<li>Languages
<ul>
<li>English
<ul>
<li>American</li>
<li>Canadian</li>
<li>British</li>
</ul>
</li>
<li>Spanish</li>
<li>German</li>
</ul>
</li>
</ul>
Output:
<tr>
<th>Date</th>
<th>Title</th>
<th>Active</th>
</tr>
echo $this->Html->tableHeaders(
array(Date,Title,Active),
array(class => status),
array(class => product_table)
);
141
Output:
<tr class="status">
<th class="product_table">Date</th>
<th class="product_table">Title</th>
<th class="product_table">Active</th>
</tr>
Changed in version 2.2: tableHeaders() now accepts attributes per cell, see below.
As of 2.2 you can set attributes per column, these are used instead of the defaults provided in the
$thOptions:
echo $this->Html->tableHeaders(array(
id,
array(Name => array(class => highlight)),
array(Date => array(class => sortable))
));
Output:
<tr>
<th>id</th>
<th class="highlight">Name</th>
<th class="sortable">Date</th>
</tr>
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>
142
Chapter 5. Views
echo $this->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>
143
echo $this->Html->tableCells(
array(
array(Red, Apple),
array(Orange, Orange),
array(Yellow, Banana),
),
array(class => darker)
);
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>
144
Chapter 5. Views
Output:
<form action="http://example.com" method="post" class="myform">
http://api.cakephp.org/2.4/class-Router.html#_url
145
The $startText option can also accept an array. This gives more control over the generated first
link:
echo $this->Html->getCrumbs( > , array(
text => $this->Html->image(home.png),
url => array(controller => pages, action => display, home),
escape => false
));
Any keys that are not text or url will be passed to link() as the $options parameter.
Changed in version 2.1: The $startText parameter now accepts an array.
HtmlHelper::addCrumb(string $name, string $link = null, mixed $options = null)
Now, in your view youll want to add the following to start the breadcrumb trails on each of the pages:
$this->Html->addCrumb(Users, /users);
$this->Html->addCrumb(Add User, array(controller => users, action => add));
This will add the output of Home > Users > Add User in your layout where getCrumbs was added.
HtmlHelper::getCrumbList(array $options = array(), mixed $startText)
Parameters
$options (array) An array of html attributes for the containing <ul> element.
Can also contain the separator, firstClass, lastClass and escape options.
$startText (string|array) The text or element that precedes the ul.
Returns breadcrumbs as a (x)html list.
This method uses HtmlHelper::tag() to generate list and its elements. Works similar
to getCrumbs(), so it uses options which every crumb was added with. You can use the
$startText parameter to provide the first breadcrumb link/text. This is useful when you always want to include a root link. This option works the same as the $startText option for
getCrumbs().
146
Chapter 5. Views
First of all download your preferred JavaScript library and place it in app/webroot/js
Then you must include the library in your page. To include it in all pages, add this line to the <head> section
of app/View/Layouts/default.ctp:
echo $this->Html->script(jquery); // Include jQuery library
Replace jquery with the name of your library file (.js will be added to the name).
By default scripts are cached, and you must explicitly print out the cache. To do this at the end of each page,
include this line just before the ending </body> tag:
echo $this->Js->writeBuffer(); // Write cached scripts
Warning: You must include the library in your page and print the cache for the helper to function.
JavaScript engine selection is declared when you include the helper in your controller:
public $helpers = array(Js => array(Jquery));
The above would use the Jquery Engine in the instances of JsHelper in your views. If you do not declare a
specific engine, the jQuery engine will be used as the default. As mentioned before, there are three engines
implemented in the core, but we encourage the community to expand the library compatibility.
More about Views
147
Using jQuery with other libraries The jQuery library, and virtually all of its plugins are constrained
within the jQuery namespace. As a general rule, global objects are stored inside the jQuery namespace as
well, so you shouldnt get a clash between jQuery and any other library (like Prototype, MooTools, or YUI).
That said, there is one caveat: By default, jQuery uses $ as a shortcut for jQuery
To override the $ shortcut, use the jQueryObject variable:
$this->Js->JqueryEngine->jQueryObject = $j;
echo $this->Html->scriptBlock(
var $j = jQuery.noConflict();,
array(inline => false)
);
// Tell jQuery to go into noconflict mode
Using the JsHelper inside customHelpers Declare the JsHelper in the $helpers array in your customHelper:
public $helpers = array(Js);
Note: It is not possible to declare a JavaScript engine inside a custom helper. Doing that will have no effect.
If you are willing to use an other JavaScript engine than the default, do the helper setup in your controller as
follows:
public $helpers = array(
Js => array(Prototype),
CustomHelper
);
Warning:
controller.
Be sure to declare the JsHelper and its engine on top of the $helpers array in your
The selected JavaScript engine may disappear (replaced by the default) from the JsHelper object in your
helper, if you miss to do so and you will get code that does not fit your JavaScript library.
Creating a JavaScript Engine
JavaScript engine helpers follow normal helper conventions, with a few additional restrictions. They must
have the Engine suffix. DojoHelper is not good, DojoEngineHelper is correct. Furthermore, they
should extend JsBaseEngineHelper in order to leverage the most of the new API.
JavaScript engine usage
The JsHelper provides a few methods, and acts as a facade for the the Engine helper. You should not
directly access the Engine helper except in rare occasions. Using the facade features of the JsHelper
allows you to leverage the buffering and method chaining features built-in; (method chaining only works in
PHP5).
148
Chapter 5. Views
The JsHelper by default buffers almost all script code generated, allowing you to collect scripts throughout the view, elements and layout, and output it in one place. Outputting buffered scripts is done with
$this->Js->writeBuffer(); this will return the buffer contents in a script tag. You can disable
buffering wholesale with the $bufferScripts property or setting buffer => false in methods
taking $options.
Since most methods in JavaScript begin with a selection of elements in the DOM, $this->Js->get()
returns a $this, allowing you to chain the methods using the selection. Method chaining allows you to write
shorter, more expressive code:
$this->Js->get(#foo)->event(click, $eventCode);
Is an example of method chaining. Method chaining is not possible in PHP4 and the above sample would
be written like:
$this->Js->get(#foo);
$this->Js->event(click, $eventCode);
Common options In attempts to simplify development where JavaScript 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 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.
Working with buffered scripts One drawback to previous implementation of Ajax type features was
the scattering of script tags throughout your document, and the inability to buffer scripts added by elements
in the layout. The new JsHelper if used correctly avoids both of those issues. It is recommended that you
place $this->Js->writeBuffer() at the bottom of your layout file above the </body> tag. This
will allow all scripts generated in layout elements to be output in one place. It should be noted that buffered
scripts are handled separately from included script files.
JsHelper::writeBuffer($options = array())
Writes all JavaScript generated so far to a code block or caches them to a file and returns a linked script.
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)
149
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.
JsHelper::buffer($content)
Add $content to the internal script buffer.
JsHelper::getBuffer($clear = true)
Get the contents of the current buffer. Pass in false to not clear the buffer at the same time.
Buffering methods that are not normally buffered
Some methods in the helpers are buffered by default. The engines buffer the following methods by default:
event
sortable
drag
drop
slider
Additionally you can force any other method in JsHelper to use the buffering. By appending an boolean
to the end of the arguments you can force other methods to go into the buffer. For example the each()
method does not normally buffer:
$this->Js->each(alert("whoa!");, true);
The above would force the each() method to use the buffer. Conversely if you want a method that does
buffer to not buffer, you can pass a false in as the last argument:
$this->Js->event(click, alert("whoa!");, false);
This would force the event function which normally buffers to return its result.
Other Methods
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.
JsHelper::object($data, $options = array())
Serializes $data into JSON. This method is a proxy for json_encode() with a few extra features
added via the $options parameter.
Options:
prefix - String prepended to the returned data.
postfix - String appended to the returned data.
150
Chapter 5. Views
Example Use:
$json = $this->Js->object($data);
JsHelper::sortable($options = array())
Sortable generates a JavaScript snippet to make a set of elements (usually a list) drag and drop sortable.
The normalized options are:
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:
$this->Js->get(#my-list);
$this->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
$("#myList").sortable({
containment:"parent",
distance:5,
sort:onSort,
start:onStart,
stop:onStop
});
151
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 response.
type - Data type for response. json and html are supported. Default is html for most
libraries.
evalScripts - Whether or not <script> tags should be evaled.
dataExpression - Should the data key be treated as a callback. Useful for supplying
$options[data] as another JavaScript expression.
Example use:
$this->Js->event(
click,
$this->Js->request(
array(action => foo, param1),
array(async => true, update => #element)
)
);
JsHelper::get($selector)
Set the internal selection to a CSS selector. The active selection is used in subsequent operations
until a new selection is made:
$this->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.
JsHelper::set(mixed $one, mixed $two = null)
Pass variables into JavaScript. Allows you to set variables that will be output when the buffer is
fetched with JsHelper::getBuffer() or JsHelper::writeBuffer(). The JavaScript
variable used to output set variables can be controlled with JsHelper::$setVariable.
JsHelper::drag($options = array())
Make an element draggable.
Options
handle - selector to the handle element.
152
Chapter 5. Views
If you were using the jQuery engine the following code would be added to the buffer
$("#element").draggable({
containment:"#content",
drag:onDrag,
grid:[10,10],
start:onStart,
stop:onStop
});
JsHelper::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:
$this->Js->get(#element);
$this->Js->drop(array(
accept => .items,
hover => onHover,
leave => onExit,
153
If you were using the jQuery engine the following code would be added to the buffer
$("#element").droppable({
accept:".items",
drop:onDrop,
out:onExit,
over:onHover
});
Note: Droppables in Mootools function differently from other libraries. Droppables are implemented
as an extension of Drag. So in addition 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.
JsHelper::slider($options = array())
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 sliders value is updated
complete - Fired when the user stops sliding the handle
Example use:
$this->Js->get(#element);
$this->Js->slider(array(
complete => onComplete,
change => onChange,
min => 0,
max => 10,
value => 2,
direction => vertical,
wrapCallbacks => false
));
154
Chapter 5. Views
If you were using the jQuery engine the following code would be added to the buffer
$("#element").slider({
change:onChange,
max:10,
min:0,
orientation:"vertical",
stop:onComplete,
value:2
});
155
$this->Js->get(#some-link);
$this->Js->event(click, $this->Js->alert(hey you!));
If you were using the jQuery library you would get the following JavaScript code:
$(#some-link).bind(click, function (event) {
alert(hey you!);
return false;
});
You can remove the return false; by passing setting the stop option to false:
$this->Js->get(#some-link);
$this->Js->event(
click,
$this->Js->alert(hey you!),
array(stop => false)
);
If you were using the jQuery library you would the following JavaScript code would be added to the
buffer. Note that the default browser event is not cancelled:
$(#some-link).bind(click, function (event) {
alert(hey you!);
});
JsHelper::domReady($callback)
Creates the special DOM ready event. JsHelper::writeBuffer() automatically wraps the
buffered scripts in a domReady method.
JsHelper::each($callback)
Create a snippet that iterates over the currently selected elements, and inserts $callback.
Example:
$this->Js->get(div.message);
$this->Js->each($(this).css({color: "red"}););
JsHelper::alert($message)
Create a JavaScript snippet containing an alert() snippet. By default, alert does not buffer, and
returns the script snippet.
$alert = $this->Js->alert(Hey there);
JsHelper::confirm($message)
Create a JavaScript snippet containing a confirm() snippet. By default, confirm does not buffer,
and returns the script snippet.
$alert = $this->Js->confirm(Are you sure?);
156
Chapter 5. Views
JsHelper::prompt($message, $default)
Create a JavaScript snippet containing a prompt() snippet. By default, prompt does not buffer,
and returns the script snippet.
$prompt = $this->Js->prompt(What is your favorite color?, blue);
Will create a submit button with an attached onclick event. The click event will be buffered by default.
echo $this->Js->submit(Save, array(
update => #content,
div => false,
type => json,
async => false
));
both
FormHelper::submit()
and
157
Example use:
echo $this->Js->link(
Page 2,
array(page => 2),
array(update => #content)
);
Will create a link pointing to /page:2 and updating #content with the response.
You can use the htmlAttributes option to add in additional custom attributes.
echo $this->Js->link(Page 2, array(page => 2), array(
update => #content,
htmlAttributes => array(other => value)
));
JsHelper::serializeForm($options = array())
Serialize the form attached to $selector. Pass true for $isForm if the current selection is a form
element. Converts the form or the form element attached to the current selection into a string/json
object (depending on the library implementation) for use with XHR operations.
Options
isForm - is the current selection a form, or an input? (defaults to false)
inline - is the rendered statement going to be used inside another JS statement? (defaults to
false)
Setting inline == false allows you to remove the trailing ;. This is useful when you need to serialize a
form element as part of another JavaScript operation, or use the serialize method in an Object literal.
JsHelper::redirect($url)
Redirect the page to $url using window.location.
JsHelper::value($value)
Converts a PHP-native variable of any type to a JSON-equivalent representation. Escapes any string
values into JSON compatible strings. UTF-8 characters will be escaped.
AJAX Pagination
Much like AJAX Pagination in 1.2, you can use the JsHelper to handle the creation of AJAX pagination
links instead of plain HTML links.
Making AJAX Links Before you can create AJAX links you must include the JavaScript library
that matches the adapter you are using with JsHelper. By default the JsHelper uses jQuery.
So in your layout include jQuery (or whichever library you are using). Also make sure to include
RequestHandlerComponent in your components. Add the following to your controller:
158
Chapter 5. Views
Next link in the JavaScript library you want to use. For this example well be using jQuery:
echo $this->Html->script(jquery);
Similar to 1.2 you need to tell the PaginatorHelper that you want to make JavaScript enhanced links
instead of plain HTML ones. To do so, call the options() at the top of your view:
$this->Paginator->options(array(
update => #content,
evalScripts => true
));
The PaginatorHelper now knows to make JavaScript enhanced links, and that those links should
update the #content element. Of course this element must exist, and often times you want to wrap
$content_for_layout with a div matching the id used for the update option. You also should set
evalScripts to true if you are using the Mootools or Prototype adapters, without evalScripts these
libraries will not be able to chain requests together. The indicator option is not supported by JsHelper
and will be ignored.
You then create all the links as needed for your pagination features. Since the JsHelper automatically
buffers all generated script content to reduce the number of <script> tags in your source code you must
write the buffer out. At the bottom of your view file. Be sure to include:
echo $this->Js->writeBuffer();
If you omit this you will not be able to chain AJAX pagination links. When you write the buffer, it is also
cleared, so you dont have worry about the same JavaScript being output twice.
Adding effects and transitions Since indicator is no longer supported, you must add any indicator
effects yourself:
<!DOCTYPE html>
<html>
<head>
<?php echo $this->Html->script(jquery); ?>
//more stuff here.
</head>
<body>
<div id="content">
<?php echo $this->fetch(content); ?>
</div>
<?php
echo $this->Html->image(
indicator.gif,
array(id => busy-indicator)
);
?>
</body>
</html>
159
Remember to place the indicator.gif file inside app/webroot/img folder. You may see a situation where the
indicator.gif displays immediately upon the page load. You need to put in this CSS #busy-indicator
{ display:none; } in your main CSS file.
With the above layout, weve included an indicator image file, that will display a busy indicator animation
that we will show and hide with the JsHelper. To do that we need to update our options() function:
$this->Paginator->options(array(
update => #content,
evalScripts => true,
before => $this->Js->get(#busy-indicator)->effect(
fadeIn,
array(buffer => false)
),
complete => $this->Js->get(#busy-indicator)->effect(
fadeOut,
array(buffer => false)
),
));
This will show/hide the busy-indicator element before and after the #content div is updated. Although
indicator has been removed, the new features offered by JsHelper allow for more control and more
complex effects to be created.
NumberHelper
class NumberHelper(View $view, array $settings = array())
The NumberHelper contains convenient 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 formatting numbers.
Changed in version 2.1: NumberHelper have been refactored into CakeNumber class to allow easier
use outside of the View layer. Within a view, these methods are accessible via the NumberHelper class
and you can call it as you would call a normal helper method: $this->Number->method($args);.
All of these functions return the formatted number; They do not automatically echo the output into the view.
NumberHelper::currency(mixed $number, string $currency = USD, array $options = array())
Parameters
$number (float) The value to covert.
$currency (string) The known currency format to use.
$options (array) Options, see below.
This method is used to display a number in common currency formats (EUR,GBP,USD). Usage in a
view looks like:
160
Chapter 5. Views
// called as NumberHelper
echo $this->Number->currency($number, $currency);
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::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:
$currency
EUR
GBP
USD
The third parameter is an array of options for further defining the output. The following options are
available:
Option
before
after
zero
places
thousands
decimals
negative
escape
wholeSymbol
wholePosition
fractionSymbol
fractionPosition
fractionExponent
Description
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 no decimal symbol. eg. 0.35 => $0.35.
The text to use for zero values, can be a string or a number. ie. 0, Free!
Number of decimal places to use. ie. 2
Thousands separator ie. ,
Decimal separator symbol ie. .
Symbol for negative numbers. If equal to (), the number will be wrapped with (
and )
Should the output be htmlentity escaped? Defaults to true
String to use for whole numbers ie. dollars
Either before or after to place the whole symbol
String to use for fraction numbers ie. cents
Either before or after to place the fraction symbol
Fraction exponent of this specific currency. Defaults to 2.
161
Add a currency format to the Number helper. Makes reusing currency formats easier:
// called as NumberHelper
$this->Number->addFormat(BRL, array(before => R$, thousands => ., decimals
// called as CakeNumber
App::uses(CakeNumber, Utility);
CakeNumber::addFormat(BRL, array(before => R$, thousands => ., decimals =>
You can now use BRL as a short form when formatting currency amounts:
// called as NumberHelper
echo $this->Number->currency($value, BRL);
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::currency($value, BRL);
162
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
,
before,
false,
after,
0,
2,
,,
.,
(),
true,
2
Chapter 5. Views
Description
Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal
percentages.
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.
// Called as NumberHelper. Output: 45.69%
echo $this->Number->toPercentage(45.691873645);
// Called as CakeNumber. Output: 45.69%
App::uses(CakeNumber, Utility);
echo CakeNumber::toPercentage(45.691873645);
// Called with multiply. Output: 45.69%
echo CakeNumber::toPercentage(0.45691, 2, array(
multiply => true
));
New in version 2.4: The $options argument with the multiply option was added.
NumberHelper::fromReadableSize(string $size, $default)
Parameters
163
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
164
Chapter 5. Views
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 takes the same keys as CakeNumber::format() itself:
places (integer): the amount of desired precision
before (string): to be put before the outputted number
after (string): to be put after the outputted number
decimals (string): used to delimit the decimal places in a number
thousands (string): used to mark off thousand, millions, . . . places
Example:
// called as NumberHelper
echo $this->Number->formatDelta(123456.7890, array(
places => 2,
decimals => .,
165
thousands => ,
));
// output +123,456.79
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::formatDelta(123456.7890, array(
places => 2,
decimals => .,
thousands => ,
));
// output +123,456.79
Paginator
class PaginatorHelper(View $view, array $settings = array())
The Pagination helper is used to output pagination controls such as page numbers and next/previous links.
It works in tandem with PaginatorComponent.
See also Pagination for information on how to create paginated datasets and do paginated queries.
Creating sort links
166
Chapter 5. Views
lock Lock direction. Will only use the default direction then, defaults to false.
New in version 2.5: You can now set the lock option to true in order to lock the sorting direction into
the specified direction.
Assuming you are paginating some posts, and are on page one:
echo $this->Paginator->sort(user_id);
Output:
<a href="/posts/index/page:1/sort:user_id/direction:asc/">User Id</a>
You can use the title parameter to create custom text for your link:
echo $this->Paginator->sort(user_id, User account);
Output:
<a href="/posts/index/page:1/sort:user_id/direction:asc/">User account</a>
If you are using HTML like images in your links remember to set escaping off:
echo $this->Paginator->sort(
user_id,
<em>User account</em>,
array(escape => false)
);
Output:
<a href="/posts/index/page:1/sort:user_id/direction:asc/">
<em>User account</em>
</a>
The direction option can be used to set the default direction for a link. Once a link is active, it will automatically switch directions like normal:
echo $this->Paginator->sort(user_id, null, array(direction => desc));
Output:
<a href="/posts/index/page:1/sort:user_id/direction:desc/">User Id</a>
The lock option can be used to lock sorting into the specified direction:
echo $this->Paginator->sort(user_id, null, array(direction => asc, lock => true));
167
PaginatorHelper::numbers($options = array())
Returns a set of numbers for the paged result set. Uses a modulus to decide how many numbers to show on
each side of the current page By default 8 links on either side of the current page will be created if those
pages exist. Links will not be generated for pages that do not exist. The current page is also not a link.
Supported options are:
before Content to be inserted before the numbers.
after Content to be inserted after the numbers.
model Model to create numbers for, defaults to PaginatorHelper::defaultModel().
modulus how many numbers to include on either side of the current page, defaults to 8.
separator Separator content defaults to |
tag The tag to wrap links in, defaults to span.
first Whether you want first links generated, set to an integer to define the number of first links
to generate. Defaults to false. If a string is set a link to the first page will be generated with the value
as the title:
echo $this->Paginator->numbers(array(first => First page));
last Whether you want last links generated, set to an integer to define the number of last links
to generate. Defaults to false. Follows the same logic as the first option. There is a last()
method to be used separately as well if you wish.
ellipsis Ellipsis content, defaults to ...
class The class name used on the wrapping tag.
currentClass The class name to use on the current/active link. Defaults to current.
currentTag Tag to use for current page number, defaults to null. This allows you to generate for
example Twitter Bootstrap like links with the current page number wrapped in extra a or span tag.
While this method allows a lot of customization for its output. It is also ok to just call the method without
any params.
echo $this->Paginator->numbers();
Using the first and last options you can create links to the beginning and end of the page set. The following
would create a set of page links that include links to the first 2 and last 2 pages in the paged results:
echo $this->Paginator->numbers(array(first => 2, last => 2));
168
Chapter 5. Views
In addition to generating links that go directly to specific page numbers, youll often want links that go to
the previous and next links, first and last pages in the paged data set.
PaginatorHelper::prev($title = << Previous, $options = array(), $disabledTitle = null,
$disabledOptions = array())
Parameters
$title (string) Title for the link.
$options (mixed) Options for pagination link.
$disabledTitle (string) Title when the link is disabled, as when youre already
on the first page, no previous page to go.
$disabledOptions (mixed) Options for the disabled pagination link.
Generates a link to the previous page in a set of paged records.
$options and $disabledOptions supports the following keys:
tag The tag wrapping tag you want to use, defaults to span. Set this to false to disable this
option.
escape Whether you want the contents HTML entity encoded, defaults to true.
model The model to use, defaults to PaginatorHelper::defaultModel().
disabledTag Tag to use instead of A tag when there is no previous page
A simple example would be:
echo $this->Paginator->prev(
<< . __(previous),
array(),
null,
array(class => prev disabled)
);
If you were currently on the second page of posts, you would get the following:
<span class="prev">
<a rel="prev" href="/posts/index/page:1/sort:title/order:desc">
<< previous
</a>
</span>
You can change the wrapping tag using the tag option:
echo $this->Paginator->prev(__(previous), array(tag => li));
169
Output:
<li class="prev">
<a rel="prev" href="/posts/index/page:1/sort:title/order:desc">
previous
</a>
</li>
Output:
<a class="prev" rel="prev"
href="/posts/index/page:1/sort:title/order:desc">
previous
</a>
Changed
in
version
2.3:
For
methods:
PaginatorHelper::prev()
and
PaginatorHelper::next() it is now possible to set the tag option to false to disable the
wrapper. New options disabledTag has been added.
If you leave the $disabledOptions empty the $options parameter will be used. This can save some
additional typing if both sets of options are the same.
PaginatorHelper::next($title = Next >>, $options = array(), $disabledTitle = null, $disabledOptions = array())
This method is identical to prev() with a few exceptions. It creates links pointing to the next page
instead of the previous one. It also uses next as the rel attribute value instead of prev
PaginatorHelper::first($first = << first, $options = array())
Returns a first or set of numbers for the first pages. If a string is given, then only a link to the first
page with the provided text will be created:
echo $this->Paginator->first(< first);
The above creates a single link for the first page. Will output nothing if you are on the first page. You
can also use an integer to indicate how many first paging links you want generated:
echo $this->Paginator->first(3);
The above will create links for the first 3 pages, once you get to the third or greater page. Prior to that
nothing will be output.
The options parameter accepts the following:
tag The tag wrapping tag you want to use, defaults to span
after Content to insert after the link/tag
model The model to use defaults to PaginatorHelper::defaultModel()
separator Content between the generated links, defaults to |
ellipsis Content for ellipsis, defaults to ...
170
Chapter 5. Views
PaginatorHelper::counter($options = array())
Returns a counter string for the paged result set. Using a provided format string and a number of options
you can create localized and application specific indicators of where a user is in the paged data set.
There are a number of options for counter(). The supported ones are:
format Format of the counter. Supported formats are range, pages and custom. Defaults to pages
which would output like 1 of 10. In the custom mode the supplied string is parsed and tokens are
replaced with actual values. The available tokens are:
{:page} - the current page displayed.
{:pages} - total number of pages.
{:current} - current number of records being shown.
{:count} - the total number of records in the result set.
{:start} - number of the first record being displayed.
{:end} - number of the last record being displayed.
{:model} - The pluralized human form of the model name. If your model was RecipePage,
{:model} would be recipe pages. This option was added in 2.0.
You could also supply only a string to the counter method using the tokens available. For example:
echo $this->Paginator->counter(
Page {:page} of {:pages}, showing {:current} records out of
{:count} total, starting on record {:start}, ending on {:end}
);
171
separator The separator between the actual page and the number of pages. Defaults to of . This
is used in conjunction with format = pages which is format default value:
echo $this->Paginator->counter(array(
separator => of a total of
));
model
The
name
of
the
model
being
paginated,
defaults
to
PaginatorHelper::defaultModel(). This is used in conjunction with the custom
string on format option.
Modifying the options PaginatorHelper uses
PaginatorHelper::options($options = array())
Parameters
$options (mixed) Default options for pagination links. If a string is supplied - it
is used as the DOM id element to update.
Sets all the options for the Paginator Helper. Supported options are:
url The URL of the paginating action. url has a few sub options as well:
sort The key that the records are sorted by.
direction The direction of the sorting. Defaults to ASC.
page The page number to display.
The above mentioned options can be used to force particular pages/directions. You can also append
additional URL content into all URLs generated in the helper:
$this->Paginator->options(array(
url => array(
sort => email, direction => desc, page => 6,
lang => en
)
));
The above adds the en route parameter to all links the helper will generate. It will also create links
with specific sort, direction and page values. By default PaginatorHelper will merge in all of the
current pass and named parameters. So you dont have to do that in each view file.
escape Defines if the title field for links should be HTML escaped. Defaults to true.
update The CSS selector of the element to update with the results of AJAX pagination calls. If not
specified, regular links will be created:
172
Chapter 5. Views
This is useful when doing AJAX Pagination. Keep in mind that the value of update can be any valid
CSS selector, but most often is simpler to use an id selector.
model
The
name
of
the
model
PaginatorHelper::defaultModel().
being
paginated,
defaults
to
Using GET parameters for pagination Normally Pagination in CakePHP uses Named Parameters. There
are times you want to use GET parameters instead. While the main configuration option for this feature is
in PaginatorComponent, you have some additional control in the view. You can use options() to
indicate that you want other named parameters to be converted:
$this->Paginator->options(array(
convertKeys => array(your, keys, here)
));
Configuring the PaginatorHelper to use a JavaScript helper By default the PaginatorHelper uses
JsHelper to do AJAX features. However, if you dont want that and want to use a custom helper for AJAX
links, you can do so by changing the $helpers array in your controller. After running paginate() do
the following:
// In your controller action.
$this->set(posts, $this->paginate());
$this->helpers[Paginator] = array(ajax => CustomJs);
Will change the PaginatorHelper to use the CustomJs for AJAX operations. You could also set
the ajax key to be any helper, as long as that class implements a link() method that behaves like
HtmlHelper::link()
Pagination in Views
Its up to you to decide how to show records to the user, but most often this will be done inside HTML
tables. The examples below assume a tabular layout, but the PaginatorHelper available in views doesnt
always need to be restricted as such.
See the details on PaginatorHelper4 in the API. As mentioned, the PaginatorHelper also offers sorting features which can be easily integrated into your table column headers:
// app/View/Posts/index.ctp
<table>
<tr>
<th><?php echo $this->Paginator->sort(id, ID); ?></th>
<th><?php echo $this->Paginator->sort(title, Title); ?></th>
</tr>
<?php foreach ($data as $recipe): ?>
<tr>
4
http://api.cakephp.org/2.4/class-PaginatorHelper.html
173
The links output from the sort() method of the PaginatorHelper allow users to click on table headers
to toggle the sorting of the data by a given field.
It is also possible to sort a column based on associations:
<table>
<tr>
<th><?php echo $this->Paginator->sort(title, Title); ?></th>
<th><?php echo $this->Paginator->sort(Author.name, Author); ?></th>
</tr>
<?php foreach ($data as $recipe): ?>
<tr>
<td><?php echo h($recipe[Recipe][title]); ?> </td>
<td><?php echo h($recipe[Author][name]); ?> </td>
</tr>
<?php endforeach; ?>
</table>
The final ingredient to pagination display in views is the addition of page navigation, also supplied by the
PaginationHelper:
// Shows the page numbers
echo $this->Paginator->numbers();
// Shows the next and previous links
echo $this->Paginator->prev(
Previous,
null,
null,
array(class => disabled)
);
echo $this->Paginator->next(
Next ,
null,
null,
array(class => disabled)
);
// prints X of Y, where X is current page and Y is number of pages
echo $this->Paginator->counter();
The wording output by the counter() method can also be customized using special markers:
echo $this->Paginator->counter(array(
format => Page {:page} of {:pages}, showing {:current} records out of
{:count} total, starting on record {:start}, ending on {:end}
));
174
Chapter 5. Views
Other Methods
Would
create
link
pointing
at
PaginatorHelper::defaultModel()
Gets the default model of the paged sets or null if pagination is not initialized.
PaginatorHelper::params(string $model = null)
Gets the current paging parameters from the resultset for the given model:
debug($this->Paginator->params());
/*
Array
(
[page] => 2
[current] => 2
[count] => 43
[prevPage] => 1
175
[nextPage] => 3
[pageCount] => 3
[order] =>
[limit] => 20
[options] => Array
(
[page] => 2
[conditions] => Array
(
)
)
[paramType] => named
)
*/
You can also append the output of the meta function to the named block:
$this->Paginator->meta(array(block => true));
This example assumes you have a Posts Controller and Post Model already created and want to make an
alternative view for RSS.
176
Chapter 5. Views
Creating an xml/rss version of posts/index is a snap with CakePHP. After a few simple steps you can simply
append the desired extension .rss to posts/index making your URL posts/index.rss. Before
we jump too far ahead trying to get our webservice up and running we need to do a few things. First
parseExtensions needs to be activated, this is done in app/Config/routes.php:
Router::parseExtensions(rss);
In the call above weve activated the .rss extension. When using Router::parseExtensions() you
can pass as many arguments or extensions as you want. This will activate each extension/content-type for
use in your application. Now when the address posts/index.rss is requested you will get an xml
version of your posts/index. However, first we need to edit the controller to add in the rss-specific
code.
Controller Code It is a good idea to add RequestHandler to your PostsControllers $components array.
This will allow a lot of automagic to occur:
public $components = array(RequestHandler);
Our view will also use the TextHelper for formatting, so that should be added to the controller as well:
public $helpers = array(Text);
Before we can make an RSS version of our posts/index we need to get a few things in order. It may
be tempting to put the channel metadata in the controller action and pass it to your view using the
Controller::set() method but this is inappropriate. That information can also go in the view. That
will come later though, for now if you have a different set of logic for the data used to make the RSS feed
and the data for the HTML view you can use the RequestHandler::isRss() method, otherwise your
controller can stay the same:
// Modify the Posts Controller action that corresponds to
// the action which deliver the rss feed, which is the
// index action in our example
public function index() {
if ($this->RequestHandler->isRss() ) {
$posts = $this->Post->find(
all,
array(limit => 20, order => Post.created DESC)
);
return $this->set(compact(posts));
}
// this is not an Rss request, so deliver
// data used by websites interface
$this->paginate[Post] = array(
order => Post.created DESC,
limit => 10
);
$posts = $this->paginate();
$this->set(compact(posts));
}
177
With all the View variables set we need to create an rss layout.
Layout An
Rss
layout
is
very
simple,
app/View/Layouts/rss/default.ctp:
put
the
following
contents
in
if (!isset($documentData)) {
$documentData = array();
}
if (!isset($channelData)) {
$channelData = array();
}
if (!isset($channelData[title])) {
$channelData[title] = $this->fetch(title);
}
$channel = $this->Rss->channel(array(), $channelData, $this->fetch(content));
echo $this->Rss->document($documentData, $channel);
It doesnt look like much but thanks to the power in the RssHelper its doing a lot of lifting for us. We
havent set $documentData or $channelData in the controller, however in CakePHP 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
View/Posts/rss/ directory and create a new index.ctp inside that folder. The contents of the file
are below.
View Our view, located at app/View/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 channels metadata back to the layout:
$this->set(channelData, array(
title => __("Most Recent Posts"),
link => $this->Html->url(/, true),
description => __("Most recent posts."),
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.
Note: You will need to modify the $postLink variable as appropriate to your application.
178
Chapter 5. Views
$this->Rss->item(array(), array(
title => $post[Post][title],
link => $postLink,
guid => array(url => $postLink, isPermaLink => true),
description => $bodyText,
pubDate => $post[Post][created]
));
}
You can see above that we can use the loop to prepare the data to be transformed into XML elements. It
is important to filter out any non-plain text characters out of the description, especially if you are using
a rich text editor for the body of your blog. In the code above we used strip_tags() and h() to
remove/escape any XML special characaters from the content, as they could cause validation errors. Once
we have set up the data for the feed, we can then use the RssHelper::item() method to create the
XML in RSS format. Once you have all this setup, you can test your RSS feed by going to your site
/posts/index.rss and you will see your new feed. It is always important that you validate your RSS
feed before making it live. This can be done by visiting sites that validate the XML such as Feed Validator
or the w3c site at http://validator.w3.org/feed/.
Note: 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.
property RssHelper::$action
Current action
property RssHelper::$base
Base URL
More about Views
179
property RssHelper::$data
POSTed model data
property RssHelper::$field
Name of the current field
property RssHelper::$helpers
Helpers used by the RSS Helper
property RssHelper::$here
URL to current action
property RssHelper::$model
Name of current model
property RssHelper::$params
Parameter array
property RssHelper::$version
Default spec version of generated RSS.
RssHelper::channel(array $attrib = array (), array $elements = array (), mixed $content =
null)
Return type string
Returns an RSS <channel /> element.
RssHelper::document(array $attrib = array (), string $content = null)
Return type string
Returns an RSS document wrapped in <rss /> tags.
RssHelper::elem(string $name, array $attrib = array (), mixed $content = null, boolean $endTag = true)
Return type string
Generates an XML element.
RssHelper::item(array $att = array (), array $elements = array ())
Return type string
Converts an array into an <item /> element and its contents.
RssHelper::items(array $items, mixed $callback = null)
Return type string
Transforms an array of data using an optional callback, and maps it to a set of <item /> tags.
RssHelper::time(mixed $time)
Return type string
Converts a time in any format to an RSS time. See TimeHelper::toRSS().
180
Chapter 5. Views
SessionHelper
class SessionHelper(View $view, array $settings = array())
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 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 read by using dot notation array structures:
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.
SessionHelper::read(string $key)
Return type mixed
Read from the Session. Returns a string or array depending on the contents of the session.
SessionHelper::check(string $key)
Return type boolean
Check to see whether a key is in the Session. Returns a boolean representing the keys existence.
SessionHelper::error()
Return type string
Returns last error encountered in a session.
SessionHelper::valid()
Return type boolean
Used to check whether a session is valid in a view.
Displaying notifications or flash messages
181
The above will output a simple message with the following HTML:
<div id="flashMessage" class="message">
Your stuff has been saved.
</div>
As with the component method, you can set additional properties and customize which element is
used. In the controller, you might have code like:
// in a controller
$this->Session->setFlash(The user could not be deleted.);
When outputting this message, you can choose the element used to display the message:
// in a layout.
echo $this->Session->flash(flash, array(element => failure));
This would use View/Elements/failure.ctp to render the message. The message text would
be available as $message in the element.
The failure element would contain something like this:
<div class="flash flash-failure">
<?php echo h($message); ?>
</div>
You can also pass additional parameters into the flash() method, which allows you to generate
customized messages:
// In the controller
$this->Session->setFlash(Thanks for your payment.);
// In the layout.
echo $this->Session->flash(flash, array(
params => array(name => $user[User][name])
element => payment
));
// View/Elements/payment.ctp
<div class="flash payment">
<?php printf($message, h($name)); ?>
</div>
Note: By default, CakePHP does not escape the HTML in flash messages. If you are using any
request or user data in your flash messages, you should escape it with h when formatting your messages.
TextHelper
class TextHelper(View $view, array $settings = array())
182
Chapter 5. Views
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 gracefully truncating long stretches of text.
Changed in version 2.1: Several TextHelper methods have been moved into the String class to allow
easier use outside of the View layer. Within a view, these methods are accessible via the TextHelper class.
You can call one as you would call a normal helper method: $this->Text->method($args);.
TextHelper::autoLinkEmails(string $text, array $options=array())
Parameters
$text (string) The text to convert.
$options (array) An array of html attributes for the generated links.
Adds links to the well-formed email addresses in $text, according to any options defined in
$htmlOptions (see HtmlHelper::link()).
$myText = For more information regarding our world-famous .
pastries and desserts, contact [email protected];
$linkedText = $this->Text->autoLinkEmails($myText);
Output:
For more information regarding our world-famous pastries and desserts,
contact <a href="mailto:[email protected]">[email protected]</a>
Changed in version 2.1: In 2.1 this method automatically escapes its input. Use the escape option
to disable this if necessary.
TextHelper::autoLinkUrls(string $text, array $htmlOptions=array())
Parameters
$text (string) The text to convert.
$htmlOptions (array) An array html attributes for the generated links
Same as autoLinkEmails(), only this method searches for strings that start with https, http, ftp,
or nntp and links them appropriately.
Changed in version 2.1: In 2.1 this method automatically escapes its input. Use the escape option
to disable this if necessary.
TextHelper::autoLink(string $text, array $htmlOptions=array())
Parameters
$text (string) The text to autolink.
$htmlOptions (array) An array html attributes for the generated links
Performs the functionality in both autoLinkUrls() and autoLinkEmails() on the supplied
$text. All URLs and emails are linked appropriately given the supplied $htmlOptions.
Changed in version 2.1: As of 2.1, this method automatically escapes its input. Use the escape
option to disable this if necessary.
183
TextHelper::autoParagraph(string $text)
Parameters
$text (string) The text to convert.
Adds proper <p> around text where double-line returns are found, and <br> where single-line returns
are found.
$myText = For more information
regarding our world-famous pastries and desserts.
contact [email protected];
$formattedText = $this->Text->autoParagraph($myText);
Output:
<p>For more information<br />
regarding our world-famous pastries and desserts.<p>
<p>contact [email protected]</p>
184
Chapter 5. Views
Output:
Highlights $needle in $haystack <span class="highlight">using</span>
the $options[format] string specified or a default string.
TextHelper::stripLinks($text)
Strips the supplied $text of any HTML links.
TextHelper::truncate(string $text, int $length=100, array $options)
Parameters
$text (string) The text to truncate.
$length (int) The length, in characters, beyond which the text should be truncated.
$options (array) An array of options to use.
If $text is longer than $length characters, this method truncates it at $length and adds a prefix
consisting of ellipsis, if defined. If exact is passed as false, the truncation will occur
at the first whitespace after the point at which $length is exceeded. If html is passed as true,
HTML tags will be respected and will not be cut off.
$options is used to pass all extra parameters, and has the following possible keys by default, all of
which are optional:
array(
ellipsis => ...,
exact => true,
html => false
)
Example:
// called as TextHelper
echo $this->Text->truncate(
The killer crept forward and tripped on the rug.,
22,
array(
ellipsis => ...,
exact => false
)
);
// called as String
App::uses(String, Utility);
echo String::truncate(
The killer crept forward and tripped on the rug.,
22,
array(
ellipsis => ...,
exact => false
)
);
185
Output:
The killer crept...
Changed in version 2.3: ending has been replaced by ellipsis. ending is still used in 2.2.1
TextHelper::tail(string $text, int $length=100, array $options)
Parameters
$text (string) The text to truncate.
$length (int) The length, in characters, beyond which the text should be truncated.
$options (array) An array of options to use.
If $text is longer than $length characters, this method removes an initial substring with length
consisting of the difference and prepends a suffix consisting of ellipsis, if defined. If
exact is passed as false, the truncation will occur at the first whitespace prior to the point
at which truncation would otherwise take place.
$options is used to pass all extra parameters, and has the following possible keys by default, all of
which are optional:
array(
ellipsis => ...,
exact => true
)
186
Chapter 5. Views
)
);
Output:
...a TV, a C# program that can divide by zero, death metal t-shirts
Output:
... by $radius, and prefix/suffix with $ellipsis. This method is
especially handy for search results. The query...
Output:
187
TimeHelper
class TimeHelper(View $view, array $settings = array())
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:
1. It can format time strings.
2. It can test time (but cannot bend time, sorry).
Changed in version 2.1: TimeHelper has been refactored into the CakeTime class to allow easier use
outside of the View layer. Within a view, these methods are accessible via the TimeHelper class and you
can call it as you would call a normal helper method: $this->Time->method($args);.
Using the Helper
A common use of the Time Helper is to offset the date and time to match a users time zone. Lets use a
forum as an example. Your forum has many users who may post messages at any time from any part of the
world. An easy way to manage the time is to save all dates and times as GMT+0 or UTC. Uncomment the
line date_default_timezone_set(UTC); in app/Config/core.php to ensure your applications time zone is set to GMT+0.
Next add a time zone field to your users table and make the necessary modifications to allow your users to
set their time zone. Now that we know the time zone of the logged in user we can correct the date and time
on our posts using the Time Helper:
echo $this->Time->format(
F jS, Y h:i A,
$post[Post][created],
null,
$user[User][time_zone]
);
// Will display August 22nd, 2011 11:53 PM for a user in GMT+0
// August 22nd, 2011 03:53 PM for a user in GMT-8
// and August 23rd, 2011 09:53 AM GMT+10
Most of the Time Helper methods have a $timezone parameter. The $timezone parameter accepts a valid
timezone identifier string or an instance of DateTimeZone class.
Formatting
188
Chapter 5. Views
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
TimeHelper::convertSpecifiers($format, $time = NULL)
Return type string
Converts a string representing the format for the function strftime and returns a windows safe and
i18n aware format.
TimeHelper::dayAsSql($dateString, $field_name, $timezone = NULL)
Return type string
Creates a string in the same format as daysAsSql but only needs a single date object:
// called via TimeHelper
echo $this->Time->dayAsSql(Aug 22, 2011, modified);
// (modified >= 2011-08-22 00:00:00) AND
// (modified <= 2011-08-22 23:59:59)
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::dayAsSql(Aug 22, 2011, modified);
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::daysAsSql($begin, $end, $fieldName, $timezone = NULL)
Return type string
Returns a string in the format ($field_name >= 2008-01-21 00:00:00) AND ($field_name <= 200801-25 23:59:59). This is handy if you need to search for records between two dates inclusively:
// called via TimeHelper
echo $this->Time->daysAsSql(Aug 22, 2011, Aug 25, 2011, created);
// (created >= 2011-08-22 00:00:00) AND
// (created <= 2011-08-25 23:59:59)
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::daysAsSql(Aug 22, 2011, Aug 25, 2011, created);
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
More about Views
189
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::format($date, $format = NULL, $default = false, $timezone = NULL)
Return type string
Will return a string formatted to the given format using the PHP strftime() formatting options5 :
// called via TimeHelper
echo $this->Time->format(2011-08-22 11:53:00, %B %e, %Y %H:%M %p);
// August 22, 2011 11:53 AM
echo $this->Time->format(+2 days, %c);
// 2 days from now formatted as Sun, 13 Nov 2011 03:36:10 AM EET
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::format(2011-08-22 11:53:00, %B %e, %Y %H:%M %p);
echo CakeTime::format(+2 days, %c);
You can also provide the date/time as the first argument. When doing this you should use strftime
compatible formatting. This call signature allows you to leverage locale aware date formatting which
is not possible using date() compatible formatting:
// called via TimeHelper
echo $this->Time->format(2012-01-13, %d-%m-%Y, invalid);
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::format(2011-08-22, %d-%m-%Y);
Changed in version 2.2: $format and $date parameters are in opposite order as used in 2.1 and below. $timezone parameter replaces $userOffset parameter used in 2.1 and below. $default
parameter replaces $invalid parameter used in 2.1 and below.
New in version 2.2: $date parameter now also accepts a DateTime object.
TimeHelper::fromString($dateString, $timezone = NULL)
Return type string
Takes a string and uses strtotime6 to convert it into a date integer:
// called via TimeHelper
echo $this->Time->fromString(Aug 22, 2011);
// 1313971200
echo $this->Time->fromString(+1 days);
// 1321074066 (+1 day from current date)
// called as CakeTime
App::uses(CakeTime, Utility);
5
6
http://www.php.net/manual/en/function.strftime.php
http://us.php.net/manual/en/function.date.php
190
Chapter 5. Views
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::gmt($dateString = NULL)
Return type integer
Will return the date as an integer set to Greenwich Mean Time (GMT).
// called via TimeHelper
echo $this->Time->gmt(Aug 22, 2011);
// 1313971200
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::gmt(Aug 22, 2011);
191
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::serverOffset()
Return type integer
Returns servers offset from GMT in seconds.
TimeHelper::timeAgoInWords($dateString, $options = array())
Return type string
Will take a datetime string (anything that is parsable by PHPs strtotime() function or MySQLs datetime format) and convert it into a friendly word format like, 3 weeks, 3 days ago:
// called via TimeHelper
echo $this->Time->timeAgoInWords(Aug 22, 2011);
// on 22/8/11
// on August 22nd, 2011
echo $this->Time->timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y)
);
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::timeAgoInWords(Aug 22, 2011);
echo CakeTime::timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y)
);
Use the end option to determine the cutoff point to no longer will use words; default +1 month:
// called via TimeHelper
echo $this->Time->timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y, end => +1 year)
);
// On Nov 10th, 2011 it would display: 2 months, 2 weeks, 6 days ago
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::timeAgoInWords(
192
Chapter 5. Views
Use the accuracy option to determine how precise the output should be. You can use this to limit the
output:
// If $timestamp is 1 month, 1 week, 5 days and 6 hours ago
echo CakeTime::timeAgoInWords($timestamp, array(
accuracy => array(month => month),
end => 1 year
));
// Outputs 1 month ago
New in version 2.2: $dateString parameter now also accepts a DateTime object.
193
New in version 2.4: The new option parameters relativeString (defaults to %s ago) and
absoluteString (defaults to on %s) to allow customization of the resulting output string are
now available.
TimeHelper::toRSS($dateString, $timezone = NULL)
Return type string
Will return a date string in the RSS format Sat, 12 Jan 2008 00:00:00 -0500
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::toUnix($dateString, $timezone = NULL)
Return type integer
A wrapper for fromString.
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::toServer($dateString, $timezone = NULL, $format = Y-m-d H:i:s)
Return type mixed
New in version 2.2: Returns a formatted date in servers timezone.
TimeHelper::timezone($timezone = NULL)
Return type DateTimeZone
New in version 2.2: Returns a timezone object from a string or the users timezone object. If the
function is called without a parameter it tries to get timezone from Config.timezone configuration
variable.
TimeHelper::listTimezones($filter = null, $country = null, $group = true)
Return type array
New in version 2.2: Returns a list of timezone identifiers.
Testing Time
Chapter 5. Views
wasWithinLast takes a time interval which is a string in the format 3 months and accepts a time
interval of seconds, minutes, hours, days, weeks, months and years (plural and not). If a time interval
is not recognized (for example, if it is mistyped) then it will default to days.
Using and Configuring Helpers
You enable helpers in CakePHP by making a controller aware of them. Each controller has a $helpers
property that lists the helpers to be made available in the view. To enable a helper in your view, add the
name of the helper to the controllers $helpers array:
class BakeriesController extends AppController {
public $helpers = array(Form, Html, Js, Time);
}
Adding helpers from plugins uses the plugin syntax used elsewhere in CakePHP:
class BakeriesController extends AppController {
public $helpers = array(Blog.Comment);
}
You can also add helpers from within an action, so they will only be available to that action and not to the
other actions in the controller. This saves processing power for the other actions that do not use the helper
and helps keep the controller better organized:
class BakeriesController extends AppController {
public function bake() {
$this->helpers[] = Time;
}
public function mix() {
// The Time helper is not loaded here and thus not available
195
}
}
If you need to enable a helper for all controllers, add the name of the helper to the $helpers array in
/app/Controller/AppController.php (or create it if not present). Remember to include the
default Html and Form helpers:
class AppController extends Controller {
public $helpers = array(Form, Html, Js, Time);
}
You can pass options to helpers. These options can be used to set attribute values or modify behavior of a
helper:
class AwesomeHelper extends AppHelper {
public function __construct(View $view, $settings = array()) {
parent::__construct($view, $settings);
debug($settings);
}
}
class AwesomeController extends AppController {
public $helpers = array(Awesome => array(option1 => value1));
}
As of 2.3, the options are merged with the Helper::$settings property of the helper.
One common setting to use is the className option, which allows you to create aliased helpers in your
views. This feature is useful when you want to replace $this->Html or another common Helper reference
with a custom implementation:
// app/Controller/PostsController.php
class PostsController extends AppController {
public $helpers = array(
Html => array(
className => MyHtml
)
);
}
// app/View/Helper/MyHtmlHelper.php
App::uses(HtmlHelper, View/Helper);
class MyHtmlHelper extends HtmlHelper {
// Add your code to override the core HtmlHelper
}
Chapter 5. Views
Using Helpers
Once youve configured which helpers you want to use in your controller, each helper is exposed as a public
property in the view. For example, if you were using the HtmlHelper you would be able to access it by
doing the following:
echo $this->Html->css(styles);
The above would call the css method on the HtmlHelper. You can access any loaded helper using
$this->{$helperName}. There may come a time where you need to dynamically load a helper from
inside a view. You can use the views HelperCollection to do this:
$mediaHelper = $this->Helpers->load(Media, $mediaSettings);
The HelperCollection is a collection and supports the collection API used elsewhere in CakePHP.
Callback methods
Helpers feature several callbacks that allow you to augment the view rendering process. See the Helper API
and the Collections documentation for more information.
Creating Helpers
If a core helper (or one showcased on GitHub or in the Bakery) doesnt fit your needs, helpers are easy to
create.
Lets 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 into CakePHPs existing helper
structure, youll need to create a new class in /app/View/Helper. Lets call our helper LinkHelper. The
actual PHP class file would look something like this:
/* /app/View/Helper/LinkHelper.php */
App::uses(AppHelper, View/Helper);
class LinkHelper extends AppHelper {
public function makeEdit($title, $url) {
// Logic to create specially formatted link goes here...
}
}
Note: Helpers must extend either AppHelper or Helper or implement all the callbacks in the Helper
API.
More about Views
197
You may wish to use some functionality already existing in another helper. To do so, you can specify helpers
you wish to use with a $helpers array, formatted just as you would in a controller:
/* /app/View/Helper/LinkHelper.php (using other helpers) */
App::uses(AppHelper, View/Helper);
class LinkHelper extends AppHelper {
public $helpers = array(Html);
public function makeEdit($title, $url) {
// Use the HTML helper to output
// formatted data:
$link = $this->Html->link($title, $url, array(class => edit));
return <div class="editOuter"> . $link . </div>;
}
}
Once youve created your helper and placed it in /app/View/Helper/, youll be able to include it in
your controllers using the special variable $helpers:
class PostsController extends AppController {
public $helpers = array(Link);
}
Once your controller has been made aware of this new class, you can use it in your views by accessing an
object named after the helper:
<!-- make a link using the new helper -->
<?php echo $this->Link->makeEdit(Change this Recipe, /recipes/edit/5); ?>
198
Chapter 5. Views
Helper API
class Helper
The base class for Helpers. It provides a number of utility methods and features for loading other
helpers.
Helper::webroot($file)
Resolve a file name to the webroot of the application. If a theme is active and the file exists in the
current themes webroot, the path to the themed file will be returned.
Helper::url($url, $full = false)
Generates an HTML escaped URL, delegates to Router::url().
Helper::value($options = array(), $field = null, $key = value)
Get the value for a given input name.
Helper::domId($options = null, $id = id)
Generate a CamelCased id value for the currently selected field. Overriding this method in your
AppHelper will allow you to change how CakePHP generates ID attributes.
Callbacks
Helper::beforeRenderFile($viewFile)
Is called before each view file is rendered. This includes elements, views, parent views and layouts.
Helper::afterRenderFile($viewFile, $content)
Is called after each view file is rendered. This includes elements, views, parent views and layouts. A
callback can modify and return $content to change how the rendered content will be displayed in
the browser.
Helper::beforeRender($viewFile)
The beforeRender method is called after the controllers beforeRender method but before the controller renders view and layout. Receives the file being rendered as an argument.
Helper::afterRender($viewFile)
Is called after the view has been rendered but before layout rendering has started.
Helper::beforeLayout($layoutFile)
Is called before layout rendering starts. Receives the layout filename as an argument.
Helper::afterLayout($layoutFile)
Is called after layout rendering is complete. Receives the layout filename as an argument.
199
200
Chapter 5. Views
CHAPTER 6
Models
Models are the classes that form the business layer in your application. They should be responsible for
managing almost everything regarding your data, its validity, and its interactions, as well as the evolution of
the information workflow in your domain.
Usually, model classes represent data and are used in CakePHP applications for data access. They generally
represent a database table but can be used to access anything that manipulates data such as files, external
web services, or iCal events.
A model can be associated with other models. For example, a Recipe may be associated with an Author as
well as an Ingredient.
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 will explain the different ways to build associations for
your data. It will describe how to find, save, and delete data. Finally, it will look at Datasources.
Understanding Models
A Model represents your data model. In object-oriented programming, a data model is an object that represents a thing such as a car, a person, or a house. A blog, for example, may have many blog posts and
each blog post may have many comments. The Blog, Post, and Comment are all examples of models, each
associated with another.
Here is a simple example of a model definition in CakePHP:
App::uses(AppModel, Model);
class Ingredient extends AppModel {
public $name = Ingredient;
}
With just this simple declaration, the Ingredient model is endowed with all the functionality you need to
create queries and to save and delete data. These methods come from CakePHPs Model class by the magic
of inheritance. The Ingredient model extends the application model, AppModel, which in turn extends
201
CakePHPs internal Model class. It is this core Model class that bestows the functionality onto your Ingredient model. App::uses(AppModel, Model) ensures that the model is loaded when it is
needed.
The intermediate class, AppModel, is empty. If you havent created your own, it is taken from the CakePHP
core 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 AppModel.php file that resides in
the Model folder, as do all other models in your application. Creating a project using Bake will automatically
generate this file for you.
See also Behaviors for more information on how to apply similar logic to multiple models.
Back to our Ingredient model. In order to work on it, create the PHP file in the /app/Model/ directory. By
convention, it should have the same name as the class, which for this example will be Ingredient.php.
Note: CakePHP will dynamically create a model object for you if it cannot find a corresponding file in
/app/Model. This also means that if your model file isnt named correctly (for instance, if it is named
ingredient.php or Ingredients.php rather than Ingredient.php), CakePHP will use an instance of AppModel
rather than your model file (which CakePHP assumes is missing). If youre trying to use a method youve
defined in your model, or a behavior attached to your model, and youre getting SQL errors that are the name
of the method youre calling, its a sure sign that CakePHP cant find your model and you need to check the
file names, your application cache, or both.
Note: Some class names are not usable for model names. For instance, File cannot be used, since File
is a class that already exists in the CakePHP core.
When your model is 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:
class IngredientsController extends AppController {
public function index() {
//grab all ingredients and pass it to the view:
$ingredients = $this->Ingredient->find(all);
$this->set(ingredients, $ingredients);
}
}
Associated models are available through the main model. In the following example, Recipe has an association with the Ingredient model:
class Recipe extends AppModel {
public function steakRecipes() {
$ingredient = $this->Ingredient->findByName(Steak);
return $this->findAllByMainIngredient($ingredient[Ingredient][id]);
}
}
This shows how to use models that are already linked. To understand how associations are defined, take a
202
Chapter 6. Models
More on models
Associations: Linking Models Together
One of the most powerful features of CakePHP is the ability to link relational mapping provided by the
model. In CakePHP, the links between models are handled through associations.
Defining relations between different objects in your application should be a natural process. For example:
in a recipe database, a recipe may have many reviews, reviews have a single author, and authors may have
many recipes. Defining the way these relations work allows you to access your data in an intuitive and
powerful way.
The purpose of this section is to show you how to plan for, define, and utilize associations between models
in CakePHP.
While data can come from a variety of sources, the most common form of storage in web applications is a
relational database. Most of what this section covers will be in that context.
For information on associations with Plugin models, see Plugin Models.
Relationship Types
The four association types in CakePHP are: hasOne, hasMany, belongsTo, and hasAndBelongsToMany
(HABTM).
Relationship
one to one
one to many
many to one
many to many
Association Type
hasOne
hasMany
belongsTo
hasAndBelongsToMany
Example
A user has one profile.
A user can have multiple recipes.
Many recipes belong to a user.
Recipes have, and belong to, many ingredients.
To further clarify which way around the associations are defined in the models: If the table of the model
contains the foreign key (other_model_id), the relation type in this model is always a Model belongsTo
OtherModel relation!
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 complex as a multidimensional array used to
define association specifics.
class User extends AppModel {
public $hasOne = Profile;
public $hasMany = array(
Recipe => array(
className => Recipe,
conditions => array(Recipe.approved => 1),
order => Recipe.created DESC
)
More on models
203
);
}
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 for each model must be unique across the app. For example, it is
appropriate to have:
class User extends AppModel {
public $hasMany = array(
MyRecipe => array(
className => Recipe,
)
);
public $hasAndBelongsToMany = array(
MemberOf => array(
className => Group,
)
);
}
class Group extends AppModel {
public $hasMany = array(
MyRecipe => array(
className => Recipe,
)
);
public $hasAndBelongsToMany = array(
Member => array(
className => User,
)
);
}
204
Chapter 6. Models
)
);
public $hasAndBelongsToMany = array(
Member => array(
className => User,
)
);
}
because here we have the alias Member referring to both the User (in Group) and the Group (in User)
model in the HABTM associations. Choosing non-unique names for model aliases across models can cause
unexpected behavior.
CakePHP will automatically create links between associated model objects. So for example in your User
model you can access the Recipe model as:
$this->Recipe->someFunction();
Similarly in your controller you can access an associated model simply by following your model associations:
$this->User->Recipe->someFunction();
Note: Remember that associations are 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.
hasOne
Lets 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:
hasOne: the other model contains the foreign key.
Relation
Apple hasOne Banana
User hasOne Profile
Doctor hasOne Mentor
Schema
bananas.apple_id
profiles.user_id
mentors.doctor_id
Note: It is not mandatory to follow CakePHP conventions. You can easily override the use of any foreignKey in your associations definitions. Nevertheless, sticking to conventions will make your code less
repetitive and easier to read and maintain.
The User model file will be saved in /app/Model/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/Model/Profile.php,
or the association wont work:
More on models
205
There are two ways to describe this relationship in your model files. The simplest method is to set the
$hasOne attribute to a string containing the class name of the associated model, as weve done above.
If you need more control, you can define your associations using array syntax. For example, you might want
to limit the association to include only certain records.
class User extends AppModel {
public $hasOne = array(
Profile => array(
className => Profile,
conditions => array(Profile.published => 1),
dependent => true
)
);
}
206
Chapter 6. Models
belongsTo
Now that we have Profile data access from the User model, lets 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:
belongsTo: the current model contains the foreign key.
Relation
Banana belongsTo Apple
Profile belongsTo User
Mentor belongsTo Doctor
Schema
bananas.apple_id
profiles.user_id
mentors.doctor_id
More on models
207
conditions:
an array of find() compatible
array(User.active => true)
conditions
or
SQL
strings
such
as
type: the type of the join to use in the SQL query. The default is LEFT, which may not fit your
needs in all situations. The value INNER may be helpful (when used with some conditions) when
you want everything from your main and associated models or nothing at all.
fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by
default.
order:
an array of find() compatible
array(User.username => ASC)
order
clauses
or
SQL
strings
such
as
counterCache: If set to true, the associated Model will automatically increase or decrease the [singular_model_name]_count 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. You can also specify multiple counter caches by defining an array. See Multiple
counterCache.
counterScope: Optional conditions array to use for updating counter cache field.
Once this association has been defined, find operations on the Profile model will also fetch a related User
record if it exists:
//Sample results from a $this->Profile->find() call.
Array
(
[Profile] => Array
(
[id] => 12
[user_id] => 121
[skill] => Baking Cakes
[created] => 2007-05-01 10:31:01
)
[User] => Array
(
[id] => 121
[name] => Gwoo the Kungwoo
[created] => 2007-05-01 10:31:01
)
)
This feature helps you cache the count of related data. Instead of counting the records manually via
find(count), the model itself tracks any addition/deletion towards the associated $hasMany model
and increases/decreases a dedicated integer field within the parent model table.
The name of the field consists of the singular model name followed by a underscore and the word count:
my_model_count
208
Chapter 6. Models
Lets say you have a model called ImageComment and a model called Image. You would add a new
INT-field to the images table and name it image_comment_count.
Here are some more examples:
Model
User
Image
BlogEntry
Associated Model
Image
ImageComment
BlogEntryComment
Example
users.image_count
images.image_comment_count
blog_entries.blog_entry_comment_count
Once you have added the counter field, you are good to go. Activate counter-cache in your association by
adding a counterCache key and set the value to true:
class ImageComment extends AppModel {
public $belongsTo = array(
Image => array(
counterCache => true,
)
);
}
From now on, every time you add or remove a ImageComment associated to Image, the number within
image_comment_count is adjusted automatically.
counterScope
You can also specify counterScope. It allows you to specify a simple condition which tells the model
when to update (or when not to, depending on how you look at it) the counter value.
Using our Image model example, we can specify it like so:
class ImageComment extends AppModel {
public $belongsTo = array(
Image => array(
counterCache => active_comment_count, //custom field name
// only count if "ImageComment" is active = 1
counterScope => array(
ImageComment.active => 1
)
)
);
}
Multiple counterCache
Since 2.0, CakePHP has supported having multiple counterCache in a single model relation. It is also
possible to define a counterScope for each counterCache. Assuming you have a User model and a
Message model, and you want to be able to count the amount of read and unread messages for each user.
More on models
209
Model
User
User
Message
Field
users.messages_read
users.messages_unread
messages.is_read
Description
Count read Message
Count unread Message
Determines if a Message is read or not.
hasMany
Next step: defining a User hasMany Comment association. A hasMany association will allow us to fetch
a users comments when we fetch a User record.
When keying your database tables for a hasMany relationship, follow this convention:
hasMany: the other model contains the foreign key.
Relation
User hasMany Comment
Cake hasMany Virtue
Product hasMany Option
Schema
Comment.user_id
Virtue.cake_id
Option.product_id
We can define the hasMany association in our User model at /app/Model/User.php using the string syntax
as follows:
class User extends AppModel {
public $hasMany = Comment;
}
210
Chapter 6. Models
More on models
211
One thing to remember is that youll need a complementary Comment belongsTo User association in order
to get the data from both directions. What weve outlined in this section empowers you to get Comment
data from the User. Adding the Comment belongsTo User association in the Comment model enables you
to get User data from the Comment model, completing the connection and allowing the flow of information
from either models perspective.
hasAndBelongsToMany (HABTM)
All right. At this point, you can already call yourself a CakePHP model associations professional. Youre
already well versed in the three associations that take up the bulk of object relations.
Lets tackle the 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, were about to join up our Recipe model with an Ingredient model using HABTM. Using
tomatoes as an Ingredient for my grandmas spaghetti recipe doesnt use up the ingredient. I can also use
it for a salad Recipe.
Links between hasMany associated objects are exclusive. If my User hasMany Comments, a comment is
only linked to a specific user. Its not up for grabs.
Moving on. Well need to set up an extra table in the database to handle HABTM associations. This new join
tables name needs to include the names of both models involved, in alphabetical order, and separated with
an underscore ( _ ). The contents of the table should be two fields that are foreign keys (which should be
integers) pointing to the primary keys of the involved models. To avoid any issues, dont define a combined
primary key for these two fields. If your application requires a unique index, you can define one. If you plan
to add any extra information to this table, or use a with model, you should add an additional primary key
field (by convention id).
HABTM requires a separate join table that includes both model names.
Relationship
Recipe HABTM
Ingredient
Cake HABTM Fan
Foo HABTM Bar
Note: Table names are in alphabetical order by convention. It is possible to define a custom table name in
212
Chapter 6. Models
association definition.
Make sure primary keys in tables cakes and recipes have id fields as assumed by convention. If theyre
different than assumed, they must be changed in models primaryKey.
Once this new table has been created, we can define the HABTM association in the model files. Were going
to skip straight to the array syntax this time:
class Recipe extends AppModel {
public $hasAndBelongsToMany = array(
Ingredient =>
array(
className => Ingredient,
joinTable => ingredients_recipes,
foreignKey => recipe_id,
associationForeignKey => ingredient_id,
unique => true,
conditions => ,
fields => ,
order => ,
limit => ,
offset => ,
finderQuery => ,
with =>
)
);
}
213
updating.
When false, CakePHP will insert the specified new relationship records and leave any existing relationship records in place, possibly resulting in duplicate relationship records.
When set to keepExisting, the behavior is similar to true, but with an additional check
so that if any of the records to be added are duplicates of an existing relationship record, the
existing relationship record is not deleted, and the duplicate is ignored. This can be useful
if, for example, the join table has additional data in it that needs to be retained.
conditions: an array of find()-compatible conditions or SQL string. If you have conditions on an
associated table, you should use a with model, and define the necessary belongsTo associations on
it.
fields: A list of fields to be retrieved when the associated model data is fetched. Returns all fields by
default.
order: an array of find()-compatible order clauses or SQL strings
limit: The maximum number of associated rows you want returned.
offset: The number of associated rows to skip over (given the current conditions and order) before
fetching and associating.
finderQuery: A complete SQL query CakePHP can use to fetch associated model records. This
should be used in situations that require highly customized results.
Once this association has been defined, find operations on the Recipe model will also fetch related Tag
records if they exist:
// Sample results from a $this->Recipe->find() call.
Array
(
[Recipe] => Array
(
[id] => 2745
[name] => Chocolate Frosted Sugar Bombs
[created] => 2007-05-01 10:31:01
[user_id] => 2346
)
[Ingredient] => Array
(
[0] => Array
(
[id] => 123
[name] => Chocolate
)
[1] => Array
(
[id] => 124
[name] => Sugar
)
[2] => Array
(
214
Chapter 6. Models
Remember to define a HABTM association in the Ingredient model if youd like to fetch Recipe data when
using the Ingredient model.
Note: HABTM data is treated like a complete set. Each time a new data association is added, the complete
set of associated rows in the database is dropped and created again so you will always need to pass the whole
data set for saving. For an alternative to using HABTM, see hasMany through (The Join Model).
Tip: For more information on saving HABTM objects, see Saving Related Model Data (HABTM)
Now what if we want to store the number of days that were attended by the student on the course and their
final grade? The table wed want would be:
id | student_id | course_id | days_attended | grade
The trouble is, hasAndBelongsToMany will not support this type of scenario because when hasAndBelongsToMany associations are saved, the association is deleted first. You would lose the extra data in the
columns as it is not replaced in the new insert.
Changed in version 2.1.
You can set the unique setting to keepExisting to circumvent losing extra data during the
save operation. See unique key in HABTM association arrays.
The way to implement our requirement is to use a join model, otherwise known as a hasMany through
association. That is, the association is a model itself. So, we can create a new model CourseMembership.
Take a look at the following models.
// Student.php
class Student extends AppModel {
public $hasMany = array(
CourseMembership
);
More on models
215
}
// Course.php
class Course extends AppModel {
public $hasMany = array(
CourseMembership
);
}
// CourseMembership.php
class CourseMembership extends AppModel {
public $belongsTo = array(
Student, Course
);
}
The CourseMembership join model uniquely identifies a given Students participation on a Course in addition to extra meta-information.
Join models are pretty useful things to be able to use, and CakePHP makes it easy to do so with its built-in
hasMany and belongsTo associations and saveAll feature.
Creating and Destroying Associations on the Fly
Sometimes it becomes necessary to create and destroy model associations on the fly. This may be for any
number of reasons:
You want to reduce the amount of associated data fetched, but all your associations are on the first
level of recursion.
You want to change the way an association is defined in order to sort or filter associated data.
This association creation and destruction is done using the CakePHP model bindModel() and unbindModel()
methods. (There is also a very helpful behavior called Containable. Please refer to the manual section
about Built-in behaviors for more information.) Lets set up a few models so we can see how bindModel()
and unbindModel() work. Well start with two models:
class Leader extends AppModel {
public $hasMany = array(
Follower => array(
className => Follower,
order => Follower.rank
)
);
}
class Follower extends AppModel {
public $name = Follower;
}
216
Chapter 6. Models
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, lets use unbindModel() to remove that
association in a controller action:
public function some_action() {
// This fetches Leaders, and their associated Followers
$this->Leader->find(all);
// Lets remove the hasMany...
$this->Leader->unbindModel(
array(hasMany => array(Follower))
);
// Now using a find function will return
// Leaders, with no Followers
$this->Leader->find(all);
// NOTE: unbindModel only affects the very next
// find function. An additional find call will use
// the configured association information.
// Weve already used find(all) after unbindModel(),
// so this will fetch Leaders with associated
// Followers once again...
$this->Leader->find(all);
}
Note: Removing or adding associations using bind- and unbindModel() only works for the next find operation unless the second parameter has been set to false. If the second parameter has been set to false, the bind
remains in place for the remainder of the request.
Heres the basic usage pattern for unbindModel():
$this->Model->unbindModel(
array(associationType => array(associatedModelClassName))
);
Now that weve successfully removed an association on the fly, lets add one. Our as-of-yet unprincipled
Leader needs some associated Principles. The model file for our Principle model is bare, except for the
public $name statement. Lets associate some Principles to our Leader on the fly (but remember, only for
the following find operation). This function appears in the LeadersController:
public function another_action() {
// There is no Leader hasMany Principles in
// the leader.php model file, so a find here
// only fetches Leaders.
$this->Leader->find(all);
// Lets use bindModel() to add a new association
// to the Leader model:
$this->Leader->bindModel(
More on models
217
There you have it. The basic usage for bindModel() is the encapsulation of a normal association array inside
an array whose key is named after the type of association you are trying to create:
$this->Model->bindModel(
array(associationName => array(
associatedModelClassName => array(
// normal association keys go here...
)
)
)
);
Even though the newly bound model doesnt need any sort of association definition in its model file, it will
still need to be correctly keyed in order for the new association to work properly.
Multiple relations to the same model
There are cases where a Model has more than one relation to another Model. For example, you might have
a Message model that has two relations to the User model: one relation to the user who sends a message,
and a second to the user who receives the message. The messages table will have a field user_id, but also a
field recipient_id. Now your Message model can look something like:
class Message extends AppModel {
public $belongsTo = array(
Sender => array(
className => User,
foreignKey => user_id
),
Recipient => array(
className => User,
foreignKey => recipient_id
)
);
}
Recipient is an alias for the User model. Now lets see what the User model would look like:
218
Chapter 6. Models
More on models
219
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:
$options[joins] = array(
array(table => channels,
alias => Channel,
type => LEFT,
conditions => array(
Channel.id = Item.channel_id,
)
)
);
$Item->find(all, $options);
220
Chapter 6. Models
Using joins allows you to have maximum flexibility in how CakePHP handles associations and fetches the
data. However, in most cases, you can use other tools to achieve the same results such as correctly defining
associations, binding models on the fly and using the Containable behavior. This feature should be used
with care because it could lead, in a few cases, into ill-formed SQL queries if combined with any of the
former techniques described for associating models.
More on models
221
Its also possible to add and use other parameters. Some types of find() and behaviors make use of this
ability, and your own model methods can, too.
If your find() operation fails to match any records, you will get an empty array.
find(first)
find(first, $params) will return one result. Youd use this for any case where you expect only
one result. Below are a couple of simple (controller code) examples:
public function some_function() {
// ...
$semiRandomArticle = $this->Article->find(first);
$lastCreated = $this->Article->find(first, array(
order => array(Article.created => desc)
));
$specificallyThisOne = $this->Article->find(first, array(
conditions => array(Article.id => 1)
));
// ...
}
In the first example, no parameters at all are passed to find, so no conditions or sort order will be used. The
format returned from find(first) call is of the form:
Array
(
[ModelName] => Array
(
[id] => 83
[field1] => value1
[field2] => value2
[field3] => value3
)
[AssociatedModelName] => Array
(
[id] => 1
[field1] => value1
[field2] => value2
[field3] => value3
)
)
222
Chapter 6. Models
find(count)
find(count, $params) returns an integer value. Below are a couple of simple (controller code)
examples:
public function some_function() {
// ...
$total = $this->Article->find(count);
$pending = $this->Article->find(count, array(
conditions => array(Article.status => pending)
));
$authors = $this->Article->User->find(count);
$publishedAuthors = $this->Article->find(count, array(
fields => DISTINCT Article.user_id,
conditions => array(Article.status != => pending)
));
// ...
}
Note: Dont pass fields as an array to find(count). You would only need to specify fields for a
DISTINCT count (since otherwise, the count is always the same, dictated by the conditions).
find(all)
find(all, $params) returns an array of potentially multiple results. It is, in fact, the mechanism
used by all find() variants, as well as paginate. Below are a couple of simple (controller code)
examples:
public function some_function() {
// ...
$allArticles = $this->Article->find(all);
$pending = $this->Article->find(all, array(
conditions => array(Article.status => pending)
));
$allAuthors = $this->Article->User->find(all);
$allPublishedAuthors = $this->Article->User->find(all, array(
conditions => array(Article.status != => pending)
));
// ...
}
Note: In the above example, $allAuthors will contain every user in the users table. There will be no
condition applied to the find, since none were passed.
The results of a call to find(all) will be of the following form:
Array
(
[0] => Array
(
More on models
223
find(list)
find(list, $params) returns an indexed array, useful for any place where you would want a list,
such as for populating input select boxes. Below are a couple of simple (controller code) examples:
public function some_function() {
// ...
$allArticles = $this->Article->find(list);
$pending = $this->Article->find(list, array(
conditions => array(Article.status => pending)
));
$allAuthors = $this->Article->User->find(list);
$allPublishedAuthors = $this->Article->find(list, array(
fields => array(User.id, User.name),
conditions => array(Article.status != => pending),
recursive => 0
));
// ...
}
Note: In the above example, $allAuthors will contain every user in the users table. There will be no
condition applied to the find, since none were passed.
The results of a call to find(list) will be in the following form:
Array
(
//[id]
[1] =>
[2] =>
[4] =>
[5] =>
224
=> displayValue,
displayValue1,
displayValue2,
displayValue4,
displayValue5,
Chapter 6. Models
When calling find(list), the fields passed are used to determine what should be used as the array
key and 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:
public function some_function() {
// ...
$justusernames = $this->Article->User->find(list, array(
fields => array(User.username)
));
$usernameMap = $this->Article->User->find(list, array(
fields => array(User.username, User.first_name)
));
$usernameGroups = $this->Article->User->find(list, array(
fields => array(User.username, User.first_name, User.group)
));
// ...
}
With the above code example, the resultant vars would look something like this:
$justusernames = Array
(
//[id] => username,
[213] => AD7six,
[25] => _psychic_,
[1] => PHPNut,
[2] => gwoo,
[400] => jperras,
)
$usernameMap = Array
(
//[username] => firstname,
[AD7six] => Andy,
[_psychic_] => John,
[PHPNut] => Larry,
[gwoo] => Gwoo,
[jperras] => Jol,
)
$usernameGroups = Array
(
[User] => Array
(
[PHPNut] => Larry,
[gwoo] => Gwoo,
)
More on models
225
find(threaded)
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:
public function some_function() {
// ...
$allCategories = $this->Category->find(threaded);
$comments = $this->Comment->find(threaded, array(
conditions => array(article_id => 50)
));
// ...
}
Tip: A better way to deal with nested data is using the Tree behavior
In the above code example, $allCategories will contain a nested array representing the whole category
structure. The results of a call to find(threaded) will be of the following form:
Array
(
[0] => Array
(
[ModelName] => Array
(
[id] => 83
[parent_id] => null
[field1] => value1
[field2] => value2
[field3] => value3
)
[AssociatedModelName] => Array
(
[id] => 1
[field1] => value1
[field2] => value2
[field3] => value3
)
[children] => Array
226
Chapter 6. Models
(
[0] => Array
(
[ModelName] => Array
(
[id] => 42
[parent_id] => 83
[field1] => value1
[field2] => value2
[field3] => value3
)
[AssociatedModelName] => Array
(
[id] => 2
[field1] => value1
[field2] => value2
[field3] => value3
)
[children] => Array
(
)
)
...
)
)
)
The order in which results appear can be changed, as it is influenced by the order of processing. For example,
if order => name ASC is passed in the params to find(threaded), the results will appear
in name order. Any order can be used; there is no built-in requirement of this method for the top result to be
returned first.
Warning: If you specify fields, you need to always include the id and parent_id (or their current
aliases):
public function some_function() {
$categories = $this->Category->find(threaded, array(
fields => array(id, name, parent_id)
));
}
Otherwise, the returned array will not be of the expected nested structure from above.
find(neighbors)
find(neighbors, $params) will perform a find similar to first, but will return the row before
and after the one you request. Below is a simple (controller code) example:
More on models
227
You can see in this example the two required elements of the $params array: field and value. Other
elements are still allowed as with any other find. (For example: If your model acts as containable, then you
can specify contain in $params.) The result returned from a find(neighbors) call is in the form:
Array
(
[prev] => Array
(
[ModelName] => Array
(
[id] => 2
[field1] => value1
[field2] => value2
...
)
[AssociatedModelName] => Array
(
[id] => 151
[field1] => value1
[field2] => value2
...
)
)
[next] => Array
(
[ModelName] => Array
(
[id] => 4
[field1] => value1
[field2] => value2
...
)
[AssociatedModelName] => Array
(
[id] => 122
[field1] => value1
[field2] => value2
...
)
)
)
Note: Note how the result always contains only two root elements: prev and next. This function does not
honor a models default recursive var. The recursive setting must be passed in the parameters on each call.
228
Chapter 6. Models
true);
Basically this is just telling CakePHP to accept the value available as the first argument of the find
function. The next step is to implement the function _findAvailable. This is done by convention. If
you wanted to implement a finder called myFancySearch, then the method to implement would be named
_findMyFancySearch.
class Article extends AppModel {
public $findMethods = array(available =>
true);
More on models
229
));
}
}
The special _find[Type] methods receive three arguments as shown above. The first one means the state
of the query execution, which could be either before or after. It is done this way because this function
is just a sort of callback function that has the ability to modify the query before it is done, or to modify the
results after they are fetched.
Typically the first thing to check in our custom find function is the state of the query. The before state
is the moment to modify the query, bind new associations, apply more behaviors, and interpret any special
key that is passed in the second argument of find. This state requires you to return the $query argument
(modified or not).
The after state is the perfect place to inspect the results, inject new data, process it in order to return it
in another format, or do whatever you like to the recently fetched data. This state requires you to return the
$results array (modified or not).
You can create as many custom finders as you like, and they are a great way of reusing code in your
application across models.
It is also possible to paginate via a custom find type as follows:
class ArticlesController extends AppController {
// Will paginate all published articles
public function index() {
$this->paginate = array(available);
$articles = $this->paginate();
$this->set(compact(articles));
}
}
Setting the $this->paginate property as above on the controller will result in the type of the find
becoming available, and will also allow you to continue to modify the find results.
To simply return the count of a custom find type, call count like you normally would, but pass in the find
type in an array for the second argument.
class ArticlesController extends AppController {
// Will find the count of all published articles (using the available find defined abov
public function index() {
$count = $this->Article->find(count, array(
type => available
));
}
}
If your pagination page count is becoming corrupt, it may be necessary to add the following code to your
AppModel, which should fix the pagination count:
230
Chapter 6. Models
More on models
231
findBy<fieldName>(string $value);
The findBy magic functions also accept some optional parameters:
findBy<fieldName>(string $value[, mixed $fields[, mixed $order]]);
findBy<x> Example
Corresponding SQL Fragment
$this->Product->findByOrderStatus(3);
Product.order_status = 3
$this->Recipe->findByType(Cookie); Recipe.type = Cookie
$this->User->findByLastName(Anderson);
User.last_name = Anderson;
$this->User->findByEmailOrUsername(jhon,
User.email = jhon OR
jhon);
User.username = jhon;
$this->User->findByUsernameAndPassword(jhon,
User.username = jhon AND
123);
User.password = 123;
$this->Cake->findById(7);
Cake.id = 7
findBy() functions return results like find(first)
232
Chapter 6. Models
Model::query()
query(string $query)
SQL calls that you cant or dont want to make via other model methods can be made using the models
query() method (though this should only rarely be necessary).
If you use this method, be sure to properly escape all parameters using the value() method on the database
driver. Failing to escape parameters will create SQL injection vulnerabilities.
Note: query() does not honor $Model->cacheQueries as its functionality is inherently disjoint from
that of the calling model. To avoid caching calls to query, supply a second argument of false, ie:
query($query, $cachequeries = false)
query() uses the table name in the query as the array key for the returned data, rather than the model
name. For example:
$this->Picture->query("SELECT * FROM pictures LIMIT 2;");
might return:
Array
(
[0] => Array
(
[pictures] => Array
(
[id] => 1304
[user_id] => 759
)
)
[1] => Array
(
[pictures] => Array
(
[id] => 1305
[user_id] => 759
)
)
)
To use the model name as the array key, and get a result consistent with that returned by the Find methods,
the query can be rewritten:
$this->Picture->query("SELECT * FROM pictures AS Picture LIMIT 2;");
which returns:
Array
(
[0] => Array
(
More on models
233
Note: This syntax and the corresponding array structure is valid for MySQL only. CakePHP does not
provide any data abstraction when running queries manually, so exact results will vary between databases.
Model::field()
field(string $name, array $conditions = null, string $order = null)
Returns the value of a single 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, it will return the field value for the
current model result. If no matching record is found, it returns false.
$this->Post->id = 22;
echo $this->Post->field(name); // echo the name for row id 22
// echo the name of the last created instance
echo $this->Post->field(
name,
array(created < => date(Y-m-d H:i:s)),
created DESC
);
Model::read()
read($fields, $id)
read() is a method used to set the current model data (Model::$data)such as during editsbut it can
also be used in other circumstances to retrieve a single record from the database.
$fields is used to pass a single 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.
234
Chapter 6. Models
read() always returns an array (even if only a single field name is requested). Use field to retrieve the
value of a single field.
Warning: As the read method overwrites any information stored in the data and id property of
the model, you should be very careful when using this function in general, especially using it in the
model callback functions such as beforeValidate and beforeSave. Generally the find function
provides a more robust and easy to work with API than the read method.
The structure here is fairly self-explanatory: it will find any post where the title equals This is a post and
the author id is equal to 1. Note that we could have used just title as the field name, but when building
queries, it is good practice to always specify the model name, as it improves the clarity of the code, and
helps prevent collisions in the future, should you choose to change your schema.
What about other types of matches? These are equally simple. Lets say we wanted to find all the posts
where the title is not This is a post:
array("Post.title !=" => "This is a post")
Notice the != that follows the field name. CakePHP can parse out any valid SQL comparison operator,
including match expressions using LIKE, BETWEEN, or REGEX, as long as you leave a space between field
name and the operator. The one exception here is IN (...)-style matches. Lets say you wanted to find posts
where the title was in a given set of values:
array(
"Post.title" => array("First post", "Second post", "Third post")
)
To do a NOT IN(...) match to find posts where the title is not in the given set of values, do the following:
More on models
235
array(
"NOT" => array(
"Post.title" => array("First post", "Second post", "Third post")
)
)
Adding additional filters to the conditions is as simple as adding additional key/value pairs to the array:
array (
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created >" => date(Y-m-d, strtotime("-2 weeks"))
)
You can also create finds that compare two fields in the database:
array("Post.created = Post.modified")
The above example will return posts where the created date is equal to the modified date (that is, it will
return posts that have never been modified).
Remember that if you find yourself unable to form a WHERE clause in this method (for example, boolean
operations), you can always specify it as a string like:
array(
Model.field & 8 = 1,
// other conditions as usual
)
By default, CakePHP joins multiple conditions with boolean AND. This means the snippet below would only
match posts that have been created in the past two weeks, and have a title that matches one in the given set.
However, we could just as easily find posts that match either condition:
array("OR" => array(
"Post.title" => array("First post", "Second post", "Third post"),
"Post.created >" => date(Y-m-d, strtotime("-2 weeks"))
))
CakePHP 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 nestable. Lets say you had a
belongsTo relationship between Posts and Authors. Lets say you wanted to find all the posts that contained
a certain keyword (magic) or were created in the past two weeks, but you wanted to restrict your search
to posts written by Bob:
array(
"Author.name" => "Bob",
"OR" => array(
"Post.title LIKE" => "%magic%",
"Post.created >" => date(Y-m-d, strtotime("-2 weeks"))
)
)
If you need to set multiple conditions on the same field, like when you want to do a LIKE search with
multiple terms, you can do so by using conditions similar to:
236
Chapter 6. Models
The wildcard operators ILIKE and RLIKE (RLIKE since version 2.6) are also available.
CakePHP can also check for null fields. In this example, the query will return records where the post title is
not null:
array("NOT" => array(
"Post.title" => null
)
)
Note: CakePHP will quote the numeric values depending on the field type in your DB.
How about GROUP BY?:
array(
fields => array(
Product.type,
MIN(Product.price) as price
),
group => Product.type
)
A quick example of doing a DISTINCT query. You can use other operators, such as MIN(), MAX(), etc.,
in a similar fashion:
More on models
237
array(
fields => array(DISTINCT (User.name) AS my_column_name),
order = >array(User.id DESC)
)
You can create very complex conditions by nesting multiple condition arrays:
array(
OR => array(
array(Company.name => Future Holdings),
array(Company.city => CA)
),
AND => array(
array(
OR => array(
array(Company.status => active),
NOT => array(
array(Company.status => array(inactive, suspended))
)
)
)
)
)
Sub-queries
For this example, imagine that we have a users table with id, name and status. The status can be
A, B or C. We want to retrieve all the users that have status other than B using a 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:
$conditionsSubQuery["User2"."status"] = B;
$db = $this->User->getDataSource();
238
Chapter 6. Models
$subQuery = $db->buildStatement(
array(
fields
=> array("User2"."id"),
table
=> $db->fullTableName($this->User),
alias
=> User2,
limit
=> null,
offset
=> null,
joins
=> array(),
conditions => $conditionsSubQuery,
order
=> null,
group
=> null
),
$this->User
);
$subQuery = "User"."id" NOT IN ( . $subQuery . ) ;
$subQueryExpression = $db->expression($subQuery);
$conditions[] = $subQueryExpression;
$this->User->find(all, compact(conditions));
Also, if you need to pass just part of your query as raw SQL as above, datasource expressions with raw
SQL work for any part of the find query.
Prepared Statements
Should you need even more control over your queries, you can make use of prepared statements. This allows
you to talk directly to the database driver and send any custom query you like:
$db = $this->getDataSource();
$db->fetchAll(
SELECT * from users where username = ? AND password = ?,
array(jhon, 12345)
);
More on models
239
$db->fetchAll(
SELECT * from users where username = :username AND password = :password,
array(username => jhon,password => 12345)
);
Most of the time you wont even need to worry about this format: CakePHPs FormHelper, and model
find methods all package data in this format. If youre using FormHelper, the data is also conveniently
available in $this->request->data for quick usage.
Heres a quick example of a controller action that uses a CakePHP model to save data to a database table:
public function edit($id) {
// Has any form data been POSTed?
if ($this->request->is(post)) {
// If the form data can be validated and saved...
if ($this->Recipe->save($this->request->data)) {
// Set a session flash message and redirect.
$this->Session->setFlash(Recipe Saved!);
return $this->redirect(/recipes);
}
}
// If no form data, find the recipe to be edited
// and hand it to the view.
$this->set(recipe, $this->Recipe->findById($id));
}
When save is called, the data passed to it in the first parameter is validated using CakePHPs validation
mechanism (see Data Validation chapter for more information). If for some reason your data isnt saving,
be sure to check to see if some validation rules are being broken. You can debug this situation by outputting
Model::$validationErrors:
if ($this->Recipe->save($this->request->data)) {
// handle the success.
}
debug($this->Recipe->validationErrors);
There are a few other save-related methods in the model that youll find useful:
240
Chapter 6. Models
Is an example of how you can use set() to update single fields, in an ActiveRecord approach. You can
also use set() to assign new values to multiple fields:
$this->Post->read(null, 1);
$this->Post->set(array(
title => New title,
published => false
));
$this->Post->save();
The above would update the title and published fields and save the record to the database.
Model::clear()
This method can be used to reset model state and clear out any unsaved data and validation errors.
New in version 2.4.
Model::save(array $data = null, boolean $validate = true, array
$fieldList = array())
Featured above, this method saves array-formatted data. The second parameter allows you to sidestep validation, and the third allows you to supply a list of model fields to be saved. For added security, you can
limit the saved fields to those listed in $fieldList.
Note: If $fieldList is not supplied, a malicious user can add additional fields to the form data (if you
are not using SecurityComponent), and by this change fields that were not originally intended to be
changed.
The save method also has an alternate syntax:
save(array $data = null, array $params = array())
$params array can have any of the following available options as keys:
validate Set to true/false to enable/disable validation.
fieldList An array of fields you want to allow for saving.
callbacks Set to false to disable callbacks. Using before or after will enable only those callbacks.
More on models
241
counterCache (since 2.4) Boolean to control updating of counter caches (if any)
atomic (since 2.6) Boolean to indicate you want records saved in a transaction.
More information about model callbacks is available here
Tip: If you dont want the modified field to be automatically updated when saving some data add
modified => false to your $data array
Once a save has been completed, the ID for the object can be found in the $id attribute of the model object
- something especially handy when creating new objects.
$this->Ingredient->save($newData);
$newIngredientId = $this->Ingredient->id;
Creating or updating is controlled by the models id field. If $Model->id is set, the record with this
primary key is updated. Otherwise a new record is created:
// Create: id isnt set or is null
$this->Recipe->create();
$this->Recipe->save($this->request->data);
// Update: id is set to a numerical value
$this->Recipe->id = 2;
$this->Recipe->save($this->request->data);
242
Chapter 6. Models
Warning: You cant stop the modified field being updated with this method, you need to use the
save() method.
The saveField method also has an alternate syntax:
saveField(string $fieldName, string $fieldValue, array $params = array())
$params array can have any of the following available options as keys:
validate Set to true/false to enable/disable validation.
callbacks Set to false to disable callbacks. Using before or after will enable only those callbacks.
counterCache (since 2.4) Boolean to control updating of counter caches (if any)
Model::updateAll(array $fields, mixed $conditions)
Updates one or more records in a single call. Fields to be updated, along with their values, are identified by
the $fields array. Records to be updated are identified by the $conditions array. If $conditions
argument is not supplied or it is set to true, all records will be updated.
For example, to approve all bakers who have been members for over a year, the update call might look
something like:
$thisYear = date(Y-m-d H:i:s, strtotime(-1 year));
$this->Baker->updateAll(
array(Baker.approved => true),
array(Baker.created <= => $thisYear)
);
The $fields array accepts SQL expressions. Literal values should be quoted manually using
DboSource::value(). For example if one of your model methods was calling updateAll() you
would do the following:
$db = $this->getDataSource();
$value = $db->value($value, string);
$this->updateAll(
array(Baker.approved => true),
More on models
243
Note: Even if the modified field exists for the model being updated, it is not going to be updated automatically by the ORM. Just add it manually to the array if you need it to be updated.
For example, to close all tickets that belong to a certain customer:
$this->Ticket->updateAll(
array(Ticket.status => "closed"),
array(Ticket.customer_id => 453)
);
By default, updateAll() will automatically join any belongsTo association for databases that support joins.
To prevent this, temporarily unbind the associations.
Model::saveMany(array $data = null, array $options = array())
Method used to save multiple rows of the same model at once. The following options may be used:
validate: Set to false to disable validation, true to validate each record before saving, first to
validate all records before any are saved (default),
atomic: If true (default), will attempt to save all records in a single transaction. Should be set to
false if database/table does not support transactions.
fieldList: Equivalent to the $fieldList parameter in Model::save()
deep: (since 2.1) If set to true, also associated data is saved; see also saveAssociated()
callbacks Set to false to disable callbacks. Using before or after will enable only those callbacks.
counterCache (since 2.4) Boolean to control updating of counter caches (if any)
For saving multiple records of single model, $data needs to be a numerically indexed array of records like
this:
$data = array(
array(title => title 1),
array(title => title 2),
);
Note: Note that we are passing numerical indexes instead of usual $data containing the Article key.
When saving multiple records of same model the records arrays should be just numerically indexed without
the model key.
It is also acceptable to have the data in the following format:
$data = array(
array(Article => array(title => title 1)),
244
Chapter 6. Models
To save also associated data with $options[deep] = true (since 2.1), the two above examples
would look like:
$data = array(
array(title => title 1, Assoc => array(field => value)),
array(title => title 2),
);
$data = array(
array(
Article => array(title => title 1),
Assoc => array(field => value)
),
array(Article => array(title => title 2)),
);
$Model->saveMany($data, array(deep => true));
Keep in mind that if you want to update a record instead of creating a new one you just need to add the
primary key index to the data row:
$data = array(
array(
// This creates a new row
Article => array(title => New article)),
array(
// This updates an existing row
Article => array(id => 2, title => title 2)),
);
More on models
245
For saving a record along with its related records having hasMany association, the data array should be like
this:
$data = array(
Article => array(title => My first article),
Comment => array(
array(body => Comment 1, user_id => 1),
array(body => Comment 2, user_id => 12),
array(body => Comment 3, user_id => 40),
),
);
And for saving a record along with its related records having hasMany with more than two levels deep
associations, the data array should be as follow:
$data = array(
User => array(email => [email protected]),
Cart => array(
array(
payment_status_id => 2,
total_cost => 250,
CartItem => array(
array(
cart_product_id => 3,
quantity => 1,
cost => 100,
),
array(
cart_product_id => 5,
quantity => 1,
cost => 150,
)
)
)
)
);
Note: If successful, the foreign key of the main model will be stored in the related models id field, i.e.
$this->RelatedModel->id.
Warning: Be careful when checking saveAssociated calls with atomic option set to false. It returns an
array instead of boolean.
Changed in version 2.1: You can now save deeper associated data as well with setting
$options[deep] = true;
For saving a record along with its related records having hasMany association and deeper associated Comment belongsTo User data as well, the data array should be like this:
246
Chapter 6. Models
$data = array(
Article => array(title => My first article),
Comment => array(
array(body => Comment 1, user_id => 1),
array(
body => Save a new user as well,
User => array(first => mad, last => coder)
),
),
);
Changed in version 2.1: Model::saveAll() and friends now support passing the fieldList for multiple
models.
Example of using fieldList with multiple models:
$this->SomeModel->saveAll($data, array(
fieldList => array(
SomeModel => array(field_1),
AssociatedModel => array(field_2, field_3)
)
));
The fieldList will be an array of model aliases as keys and arrays with fields as values. The model names
are not nested like in the data to be saved.
Model::saveAll(array $data = null, array $options = array())
The saveAll function is just a wrapper around the saveMany and saveAssociated methods. it will
inspect the data and determine what type of save it should perform. If data is formatted in a numerical
indexed array, saveMany will be called, otherwise saveAssociated is used.
This function receives the same options as the former two, and is generally a backwards compatible function.
It is recommended using either saveMany or saveAssociated depending on the case.
Saving Related Model Data (hasOne, hasMany, belongsTo)
When working with associated models, it is important to realize that saving model data should always be
done by the corresponding CakePHP model. If you are saving a new Post and its associated Comments, then
you would use both Post and Comment models during the save operation.
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), youll need to first save the primary, or parent model.
To get an idea of how this works, lets imagine that we have an action in our UsersController that handles
the saving of a new User and a related Profile. The example action shown below will assume that youve
POSTed enough data (using the FormHelper) to create a single User and a single Profile:
More on models
247
As a rule, when working with hasOne, hasMany, and belongsTo associations, its all about keying. The
basic idea is to get the key from one model and place it in the foreign key field on the other. Sometimes
this might involve using the $id attribute of the model class after a save(), but other times it might just
involve gathering the ID from a hidden input on a form thats just been POSTed to a controller action.
To supplement the basic approach used above, CakePHP also offers a very handy method
saveAssociated(), which allows you to validate and save multiple models in one shot. In addition,
saveAssociated() provides transactional support to ensure data integrity in your database (i.e. if one
model fails to save, the other models will not be saved either).
Note: For transactions to work correctly in MySQL your tables must use InnoDB engine. Remember that
MyISAM tables do not support transactions.
Lets see how we can use saveAssociated() to save Company and Account models at the same time.
First, you need to build your form for both Company and Account models (well assume that Company
hasMany Account):
echo
echo
echo
echo
Take a look at the way we named the form fields for the Account model. If Company is our main model,
248
Chapter 6. Models
saveAssociated() will expect the related models (Account) data to arrive in a specific format. And
having Account.0.fieldName is exactly what we need.
Note: 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 CompaniesController we can create an add() action:
public function add() {
if (!empty($this->request->data)) {
// Use the following to avoid validation errors:
unset($this->Company->Account->validate[company_id]);
$this->Company->saveAssociated($this->request->data);
}
}
Thats all there is to it. Now our Company and Account models will be validated and saved all at the same
time. By default saveAssociated will validate all values passed and then try to perform a save for each.
Saving hasMany through data
Lets see how data stored in a join table for two models is saved. As shown in the hasMany through (The
Join Model) section, the join table is associated to each model using a hasMany type of relationship. Our
example involves the Head of Cake School asking us to write an application that allows him to log a students
attendance on a course with days attended and grade. Take a look at the following code.
// Controller/CourseMembershipController.php
class CourseMembershipsController extends AppController {
public $uses = array(CourseMembership);
public function index() {
$this->set(
courseMembershipsList,
$this->CourseMembership->find(all)
);
}
public function add() {
if ($this->request->is(post)) {
if ($this->CourseMembership->saveAssociated($this->request->data)) {
return $this->redirect(array(action => index));
}
}
}
}
// View/CourseMemberships/add.ctp
<?php echo $this->Form->create(CourseMembership); ?>
<?php echo $this->Form->input(Student.first_name); ?>
<?php echo $this->Form->input(Student.last_name); ?>
More on models
249
CakePHP will happily be able to save the lot together and assign the foreign keys of the Student and Course
into CourseMembership with a saveAssociated call with this data structure. If we run the index action of
our CourseMembershipsController the data structure received now from a find(all) is:
Array
(
[0] => Array
(
[CourseMembership] => Array
(
[id] => 1
[student_id] => 1
[course_id] => 1
[days_attended] => 5
[grade] => A
)
[Student] => Array
(
[id] => 1
[first_name] => Joe
[last_name] => Bloggs
)
[Course] => Array
250
Chapter 6. Models
(
[id] => 1
[name] => Cake
)
)
)
There are of course many ways to work with a join model. The version above assumes you want to save
everything at-once. There will be cases where you want to create the Student and Course independently and
at a later point associate the two together with a CourseMembership. So you might have a form that allows
selection of existing students and courses from pick lists or ID entry and then the two meta-fields for the
CourseMembership, e.g.
// View/CourseMemberships/add.ctp
<?php echo $this->Form->create(CourseMembership); ?>
<?php
echo $this->Form->input(
Student.id,
array(
type => text,
label => Student ID,
default => 1
)
);
?>
<?php
echo $this->Form->input(
Course.id,
array(
type => text,
label => Course ID,
default => 1
)
);
?>
<?php echo $this->Form->input(CourseMembership.days_attended); ?>
<?php echo $this->Form->input(CourseMembership.grade); ?>
<button type="submit">Save</button>
<?php echo $this->Form->end(); ?>
More on models
251
)
[CourseMembership] => Array
(
[days_attended] => 10
[grade] => 5
)
)
Again CakePHP is good to us and pulls the Student id and Course id into the CourseMembership with the
saveAssociated.
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 thats done, you just call the save()
method on the model, and everything gets linked up correctly. An example of the required format for the
data array passed to save() for the Tag model is shown below:
Array
(
[Recipe] => Array
(
[id] => 42
)
[Tag] => Array
(
[name] => Italian
)
)
You can also use this format to save several records and their HABTM associations with saveAll(),
using an array like the following:
Array
(
[0] => Array
(
[Recipe] => Array
(
[id] => 42
)
[Tag] => Array
(
[name] => Italian
)
)
[1] => Array
(
[Recipe] => Array
(
[id] => 43
252
Chapter 6. Models
)
[Tag] => Array
(
[name] => Pasta
)
)
[2] => Array
(
[Recipe] => Array
(
[id] => 51
)
[Tag] => Array
(
[name] => Mexican
)
)
[3] => Array
(
[Recipe] => Array
(
[id] => 17
)
[Tag] => Array
(
[name] => American (new)
)
)
)
Passing the above array to saveAll() will create the contained tags, each associated with their respective
recipes.
Another example that is helpful is when you need to save many Tags to a Post. You need to pass the
associated HABTM data in the following HABTM array format. Note you only need to pass in the ids of
the associated HABTM model however it needs to be nested again:
Array
(
[0] => Array
(
[Post] => Array
(
[title] => Saving HABTM arrays
)
[Tag] => Array
(
[Tag] => Array(1, 2, 5, 9)
)
)
[1] => Array
(
[Post] => Array
More on models
253
(
[title] => Dr Who\s Name is Revealed
)
[Tag] => Array
(
[Tag] => Array(7, 9, 15, 19)
)
)
[2] => Array
(
[Post] => Array
(
[title] => I Came, I Saw and I Conquered
)
[Tag] => Array
(
[Tag] => Array(11, 12, 15, 19)
)
)
[3] => Array
(
[Post] => Array
(
[title] => Simplicity is the Ultimate Sophistication
)
[Tag] => Array
(
[Tag] => Array(12, 22, 25, 29)
)
)
)
Passing the above array to saveAll($data, array(deep => true)) will populate the
posts_tags join table with the Tag to Post associations.
As an example, well build a form that creates a new tag and generates the proper data array to associate it
on the fly with some recipe.
The simplest form might look something like this (well assume that $recipe_id is already set to something):
<?php echo $this->Form->create(Tag); ?>
<?php echo $this->Form->input(
Recipe.id,
array(type => hidden, value => $recipe_id)
); ?>
<?php echo $this->Form->input(Tag.name); ?>
<?php echo $this->Form->end(Add Tag); ?>
In this example, you can see the Recipe.id hidden 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, itll automatically save the HABTM data to the
database:
254
Chapter 6. Models
With the preceding code, our new Tag is created and associated with a Recipe, whose ID was set in
$this->request->data[Recipe][id].
Other ways we might want to present our associated data can include a select drop down list. The data can
be pulled from the model using the find(list) method and assigned to a view variable of the model
name. An input with the same name will automatically pull in this data into a <select>:
// in the controller:
$this->set(tags, $this->Recipe->Tag->find(list));
// in the view:
$this->Form->input(tags);
A more likely scenario with a HABTM relationship would include a <select> set to allow multiple
selections. For example, a Recipe can have multiple Tags assigned to it. In this case, the data is pulled out
of the model the same way, but the form input is declared slightly different. The tag name is defined using
the ModelName convention:
// in the controller:
$this->set(tags, $this->Recipe->Tag->find(list));
// in the view:
$this->Form->input(Tag);
Using the preceding code, a multiple select drop down is created, allowing for multiple choices to automatically be saved to the existing Recipe being added or saved to the database.
Self HABTM Normally HABTM is used to bring 2 models together but it can also be used with only 1
model, though it requires some extra attention.
The key is in the model setup the className. Simply adding a Project HABTM Project relation
causes issues saving data. By setting the className to the models name and use the alias as key we avoid
those issues.
class Project extends AppModel {
public $hasAndBelongsToMany = array(
RelatedProject => array(
className
=> Project,
foreignKey
=> projects_a_id,
associationForeignKey => projects_b_id,
),
);
}
Creating form elements and saving the data works the same as before but you use the alias instead. This:
More on models
255
$this->set(projects, $this->Project->find(list));
$this->Form->input(Project);
Becomes this:
$this->set(relatedProjects, $this->Project->find(list));
$this->Form->input(RelatedProject);
These two examples are almost the exact same. They use the same amount of named fields in the database
and the same amount of models. The important differences are that the join model is named differently
and its behavior is more predictable.
Tip: When your join table contains extra fields besides two foreign keys, you can prevent losing the extra
field values by setting unique array key to keepExisting. You could think of this similar to
unique => true, but without losing data from the extra fields during save operation. Additionally, if you
used bake in order to create the models, this is set automatically. See: HABTM association arrays.
However, in most cases its easier to make a model for the join table and setup hasMany, belongsTo associations as shown in example above instead of using HABTM association.
Datatables
While CakePHP can have datasources that arent database driven, most of the time, they are. CakePHP is
designed to be agnostic and will work with MySQL, MSSQL, PostgreSQL and others. You can create your
database tables as you normally would. When you create your Model classes, theyll automatically map
to the tables that youve created. Table names are by convention lowercase and pluralized with 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
256
Chapter 6. Models
will inspect your tables to determine the data type of each field and uses this information to automate various
features such as outputting form fields in the view. Field names are by convention lowercase and separated
by underscores.
Using created and modified
By defining a created and/or modified field in your database table as datetime fields (default null),
CakePHP will recognize those fields and populate them automatically whenever a record is created or saved
to the database (unless the data being saved already contains a value for these fields).
The created and modified fields will be set to the current date and time when the record is initially
added. The modified field will be updated with the current date and time whenever the existing record is
saved.
If you have created or modified data in your $this->data (e.g. from a Model::read or Model::set)
before a Model::save() then the values will be taken from $this->data and not automagically updated. If you
dont want that you can use unset($this->data[Model][modified]), etc. Alternatively
you can override the Model::save() to always do it for you:
class AppModel extends Model {
public function save($data = null, $validate = true, $fieldList = array()) {
// Clear modified field value before each save
$this->set($data);
if (isset($this->data[$this->alias][modified])) {
unset($this->data[$this->alias][modified]);
}
return parent::save($this->data, $validate, $fieldList);
}
}
Deleting Data
CakePHPs Model class offers a few ways to delete records from your database.
delete
delete(integer $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 (User hasMany or hasAndBelongsToMany Recipes):
if $cascade is set to true, the related Recipe records are also deleted if the models dependent-value is
set to true.
if $cascade is set to false, the Recipe records will remain after the User has been deleted.
More on models
257
If your database supports foreign keys and cascading deletes, its often more efficient to rely on that feature
than CakePHPs cascading. The one benefit to using the cascade feature of Model::delete() is that it
allows you to leverage behaviors and model callbacks:
$this->Comment->delete($this->request->data(Comment.id));
You can hook custom logic into the delete process using the beforeDelete and afterDelete callbacks present in both Models and Behaviors. See Callback Methods for more information.
deleteAll
deleteAll(mixed $conditions, $cascade = true, $callbacks = false)
deleteAll() is similar to delete(), except that deleteAll() will delete all records that match the
supplied conditions. The $conditions array should be supplied as a SQL fragment or array.
conditions Conditions to match
cascade Boolean, Set to true to delete records that depend on this record
callbacks Boolean, Run callbacks
Return boolean True on success, false on failure.
Example:
// Delete with array conditions similar to find()
$this->Comment->deleteAll(array(Comment.spam => true), false);
If you delete with either callbacks and/or cascade, rows will be found and then deleted. This will often
result in more queries being issued. Associations will be reset before the matched records are deleted in
deleteAll(). If you use bindModel() or unbindModel() to change the associations, you should set reset to
false.
Note: deleteAll() will return true even if no records are deleted, as the conditions for the delete query were
successful and no matching records remain.
Data Validation
Data validation is an important part of any application, as it helps to make sure that the data in a Model
conforms to the business rules of the application. For example, you might want to make sure that passwords
are at least eight characters long, or ensure that usernames are unique. Defining validation rules makes form
handling much, much easier.
There are many different aspects to the validation process. What well cover in this section is the model side
of things. Essentially: what happens when you call the save() method of your model. For more information
about how to handle the displaying of validation errors, check out FormHelper.
The first step to data validation is creating the validation rules in the Model.
Model::validate array in the Model definition, for example:
258
Chapter 6. Models
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:
class User extends AppModel {
public $validate = array(
login => alphaNumeric,
email => email,
born => date
);
}
This last example shows how validation rules can be added to model 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 CakePHPs 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 well cover these in detail later on.
Here is a more complex validation example that takes advantage of some of these built-in validation rules:
class User extends AppModel {
public $validate = array(
login => array(
alphaNumeric => array(
rule => alphaNumeric,
required => true,
message => Letters and numbers only
),
between => array(
rule
=> array(lengthBetween, 5, 15),
message => Between 5 to 15 characters
)
),
password => array(
rule => array(minLength, 8),
message => Minimum 8 characters long
),
email => email,
born => array(
rule => date,
message => Enter a valid date,
allowEmpty => true
)
);
}
Two validation rules are 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
More on models
259
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 youve seen the big picture on how validation works, lets 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.
Simple Rules
As the name suggests, this is the simplest way to define a validation rule. The general syntax for defining
rules this way is:
public $validate = array(fieldName => ruleName);
Where, fieldName is the name of the field the rule is defined for, and ruleName is a pre-defined rule
name, such as alphaNumeric, email or isUnique.
For example, to ensure that the user is giving a well formatted email address, you could use this rule:
public $validate = array(user_email => email);
The rule key is required. If you only set required => true, the form validation will not function correctly.
This is because required is not actually a rule.
As you can see here, each field (only one field shown above) is associated with an array that contains five
keys: rule, required, allowEmpty, on and message. Lets have a closer look at these keys.
260
Chapter 6. Models
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.
public $validate = array(
login => array(
rule => alphaNumeric
)
);
If the rule requires some parameters (like the max, min or range), rule should be an array:
public $validate = array(
password => array(
rule => array(minLength, 8)
)
);
This key accepts either a boolean, or create or update. Setting this key to true will make the field
always required. While setting it to create or update will make the field required only for update or
create operations. If required is evaluated to true, the field must be present in the data array. For example,
if the validation rule has been defined as follows:
public $validate = array(
login => array(
rule => alphaNumeric,
required => true
)
);
The data sent to the models save() method must contain data for the login field. If it doesnt, 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 ().
Changed in version 2.1: Support for create and update were added.
More on models
261
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.
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.
message
The message key allows you to define a custom validation error message for the rule:
public $validate = array(
password => array(
rule => array(minLength, 8),
message => Password must be at least 8 characters long
)
);
Note: Regardless of the rule, validation failure without a defined message defaults to This field cannot be
left blank.
262
array(
array(
=> array(
=> ruleName,
Chapter 6. Models
As you can see, this is quite similar to what we did in the previous section. There, for each field we had only
one array of validation parameters. In this case, each fieldName consists of an array of rule indexes. Each
ruleName contains a separate array of validation parameters.
This is better explained with a practical example:
public $validate = array(
login => array(
loginRule-1 => array(
rule => alphaNumeric,
message => Only alphabets and numbers allowed,
),
loginRule-2 => array(
rule => array(minLength, 8),
message => Minimum length of 8 characters
)
)
);
The above example defines two rules for the login field: loginRule-1 and loginRule-2. As you can see, each
rule is identified with an arbitrary name.
When using multiple rules per field the required and allowEmpty keys need to be used only once in the
first rule.
last
In case of multiple rules per field by default if a particular rule fails error message for that rule is returned
and the following rules for that field are not processed. If you want validation to continue in spite of a rule
failing set key last to false for that rule.
In the following example even if rule1 fails rule2 will be processed and error messages for both failing
rules will be returned if rule2 also fails:
public $validate = array(
login => array(
rule1 => array(
rule => alphaNumeric,
message => Only alphabets and numbers allowed,
last => false
),
rule2 => array(
rule => array(minLength, 8),
More on models
263
When specifying validation rules in this array form its possible to avoid providing the message key.
Consider this example:
public $validate = array(
login => array(
Only alphabets and numbers allowed => array(
rule => alphaNumeric,
),
)
);
If the alphaNumeric rules fails the array key for this rule Only alphabets and numbers allowed will be
returned as error message since the message key is not set.
Custom Validation Rules
If you havent found what you need thus far, you can always create your own validation rules. There are two
ways you can do this: by defining custom regular expressions, or by creating custom validation methods.
Custom Regular Expression Validation
If the validation technique you need to use can be completed by using regular expression matching, you can
define a custom expression as a field validation rule:
public $validate = array(
login => array(
rule => /^[a-z0-9]{3,}$/i,
message => Only letters and integers, min 3 characters
)
);
The example above checks if the login contains only letters and integers, with a minimum of three characters.
The regular expression in the rule must be delimited by slashes. The optional trailing i after the last slash
means the reg-exp is case insensitive.
Adding your own Validation Methods
Sometimes checking data with regular expression patterns is not enough. For example, if you want to ensure
that a promotional code can only be used 25 times, you need to add your own validation function, as shown
below:
264
Chapter 6. Models
The current field to be validated is passed into the function as first parameter as an associated array with
field name as key and posted data as value.
If you want to pass extra parameters to your validation function, add elements onto the rule array, and
handle them as extra params (after the main $check param) in your function.
Your validation function can be in the model (as in the example above), or in a behavior that the model
implements. This includes mapped methods.
Model/behavior methods are checked 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:
class Post extends AppModel {
public $validate = array(
slug => array(
rule => alphaNumericDashUnderscore,
message => Slug can only be letters, .
numbers, dash and underscore
)
);
public function alphaNumericDashUnderscore($check) {
// $data array is passed using the form field name as the key
// have to extract the value to make the function generic
$value = array_values($check);
$value = $value[0];
return preg_match(|^[0-9a-zA-Z_-]*$|, $value);
More on models
265
}
}
Note:
Your own validation methods must have public visibility.
protected and private are not supported.
The method should return true if the value is valid. If the validation failed, return false. The other valid
return value are strings which will be shown as the error message. Returning a string means the validation
failed. The string will overwrite the message set in the $validate array and be shown in the views form as
the reason why the field was not valid.
Dynamically change validation rules
Using the $validate property to declare validation rules is a good way of statically defining rules for
each model. Nevertheless, there are cases when you want to dynamically add, change or remove validation
rules from the predefined set.
All validation rules are stored in a ModelValidator object, which holds every rule set for each field in
your model. Defining new validation rules is as easy as telling this object to store new validation methods
for the fields you want to.
Adding new validation rules
This will add a single rule to the password field in the model. You can chain multiple calls to add to create
as many rules as you like:
// Inside a model class
$this->validator()
->add(password, required, array(
rule => notEmpty,
required => create
))
->add(password, size, array(
rule => array(lengthBetween, 8, 20),
message => Password should be at least 8 chars long
));
266
Chapter 6. Models
$this->validator()->add(password, array(
required => array(
rule => notEmpty,
required => create
),
size => array(
rule => array(lengthBetween, 8, 20),
message => Password should be at least 8 chars long
)
));
Alternatively, you can use the validator object to set rules directly to fields using the array interface:
$validator = $this->validator();
$validator[username] = array(
unique => array(
rule => isUnique,
required => create
),
alphanumeric => array(
rule => alphanumeric
)
);
You can also completely replace all the rules for a field using a similar method:
// In a model class
$this->validator()->getField(password)->setRules(array(
required => array(...),
otherRule => array(...)
));
If you wish to just modify a single property in a rule you can set properties directly into the
CakeValidationRule object:
// In a model class
$this->validator()->getField(password)
->getRule(required)->message = This field cannot be left blank;
More on models
267
Properties in any CakeValidationRule get their name from the array keys one is allowed to use when
defining a validation rules properties, such as the array keys message and allowEmpty for example.
As with adding new rule to the set, it is also possible to modify existing rules using the array interface:
$validator = $this->validator();
$validator[username][unique] = array(
rule => isUnique,
required => create
);
$validator[username][unique]->last = true;
$validator[username][unique]->message = Name already taken;
Optionally, you can use the array interface to delete rules from the set:
$validator = $this->validator();
// Completely remove all rules for a field
unset($validator[username]);
// Remove required rule from password
unset($validator[password][required]);
268
Chapter 6. Models
The length of data is the number of bytes in the string representation of the data. Be careful that it
may be larger than the number of characters when handling non-ASCII characters.
static Validation::blank(mixed $check)
This rule is used to make sure that the field is left blank or only white space characters are present in
its value. White space characters include space, tab, carriage return, and newline.
public $validate = array(
id => array(
rule => blank,
on => create
)
);
static Validation::cc(mixed $check, mixed $type = fast, boolean $deep = false, string $regex
= null)
This rule is used to check whether the data is a valid credit card number. It takes three parameters:
type, deep and regex.
The type key can be assigned to the values of fast, all or any of the following:
amex
bankcard
diners
disc
electron
enroute
jcb
More on models
269
maestro
mc
solo
switch
visa
voyager
If type is set to fast, it validates the data against the major credit cards numbering formats. Setting
type to all will check with all the credit card types. You can also set type to an array of the types
you wish to match.
The deep key should be set to a boolean value. If it is set to true, the validation will check the Luhn
algorithm of the credit card (http://en.wikipedia.org/wiki/Luhn_algorithm). It defaults to false.
The regex key allows you to supply your own regular expression that will be used to validate the
credit card number:
public $validate = array(
ccnumber => array(
rule => array(cc, array(visa, maestro), false, null),
message => The credit card number you supplied was invalid.
)
);
270
Chapter 6. Models
)
);
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.
Changed in version 2.4: The ym and y formats were added.
static Validation::datetime(array $check, mixed $dateFormat = ymd, string $regex =
null)
This rule ensures that the data is a valid datetime format. A parameter (which can be an array) can
be passed to specify the format of the date. The value of the parameter can be one or more of the
following:
dmy e.g. 27-12-2006 or 27-12-06 (separators can be a space, period, dash, forward slash)
mdy e.g. 12-27-2006 or 12-27-06 (separators can be a space, period, dash, forward slash)
ymd e.g. 2006-12-27 or 06-12-27 (separators can be a space, period, dash, forward slash)
dMy e.g. 27 December 2006 or 27 Dec 2006
Mdy e.g. December 27, 2006 or Dec 27, 2006 (comma is optional)
More on models
271
Also a second parameter can be passed to specify a custom regular expression. If this parameter is
used, this will be the only validation that will occur.
Note that unlike date(), datetime() will validate a date and a time.
static Validation::decimal(string $check, integer $places = null, string $regex = null)
This rule ensures that the data is a valid decimal number. A parameter can be passed to specify the
number of digits required after the decimal point. If no parameter is passed, the data will be validated
as a scientific float, which will cause validation to fail if no digits are found after the decimal point:
public $validate = array(
price => array(
rule => array(decimal, 2)
)
);
Chapter 6. Models
in array form.
public $validate = array(
image => array(
rule => array(
extension,
array(gif, jpeg, png, jpg)
),
message => Please supply a valid image.
)
);
Comparison is case sensitive by default. You can set $caseInsensitive to true if you need case
insensitive comparison.
static Validation::ip(string $check, string $type = both)
This rule will ensure that a valid IPv4 or IPv6 address has been submitted. Accepts as option both
(default), IPv4 or IPv6.
public $validate = array(
clientip => array(
rule => array(ip, IPv4), // or IPv6 or both (default)
message => Please supply a valid IP address.
)
More on models
273
);
Model::isUnique()
The data for the field must be unique, it cannot be used by any other rows:
public $validate = array(
login => array(
rule => isUnique,
message => This username has already been taken.
)
);
You can validate that a set of fields are unique by providing multiple fields and set $or to false:
public $validate = array(
email => array(
rule => array(isUnique, array(email, username), false),
message => This username & email combination has already been used.
)
);
Make sure to include the original field in the list of fields when making a unique rule across multiple
fields.
If a listed field isnt included in the model data, then its treated as a null value. You may consider
marking the listed fields as required.
static Validation::luhn(string|array $check, boolean $deep = false)
The Luhn algorithm: A checksum formula to validate a variety of identification numbers. See
http://en.wikipedia.org/wiki/Luhn_algorithm for more information.
static Validation::maxLength(string $check, integer $max)
This rule ensures that the data stays within a maximum length requirement.
public $validate = array(
login => array(
rule => array(maxLength, 15),
message => Usernames must be no larger than 15 characters long.
)
);
The length here is the number of bytes in the string representation of the data. Be careful that it
may be larger than the number of characters when handling non-ASCII characters.
static Validation::mimeType(mixed $check, array|string $mimeTypes)
New in version 2.2.
This rule checks for valid mime types. Comparison is case sensitive.
Changed in version 2.5.
Since 2.5 $mimeTypes can be a regex string.
public $validate = array(
image => array(
274
Chapter 6. Models
The length here is the number of bytes in the string representation of the data. Be careful that it
may be larger than the number of characters when handling non-ASCII characters.
static Validation::money(string $check, string $symbolPosition = left)
This rule will ensure that the value is in a valid monetary amount.
Second parameter defines where symbol is located (left/right).
public $validate = array(
salary => array(
rule => array(money, left),
message => Please supply a valid monetary amount.
)
);
Comparison is case sensitive by default. You can set $caseInsensitive to true if you need case
insensitive comparison.
static Validation::notEmpty(mixed $check)
The basic rule to ensure that a field is not empty.
More on models
275
Do not use this for a multiple select input as it will cause an error. Instead, use multiple.
static Validation::numeric(string $check)
Checks if the data passed is a valid number.
public $validate = array(
cars => array(
rule => numeric,
message => Please supply the number of cars.
)
);
276
Chapter 6. Models
)
);
The above example will accept any value which is larger than -1 (e.g., -0.99) and less than 11 (e.g.,
10.99).
Note: The range lower/upper are not inclusive
static Validation::ssn(mixed $check, string $regex = null, string $country = null)
Ssn validates social security numbers from the U.S. (us), Denmark (dk), and the Netherlands (nl). For
other social security number formats, you may provide a regular expression.
public $validate = array(
ssn => array(
rule => array(ssn, null, us)
)
);
More on models
277
To ensure that a protocol is in the url, strict mode can be enabled like so:
public $validate = array(
website => array(
rule => array(url, true)
)
);
This validation method uses a complex regular expression that can sometimes cause issues with
Apache2 on Windows using mod_php.
static Validation::userDefined(mixed $check, object $object, string $method, array $args
= null)
Runs an user-defined validation.
static Validation::uuid(string $check)
Checks that a value is a valid UUID: http://tools.ietf.org/html/rfc4122
Localized Validation
The validation rules phone() and postal() will pass off any country prefix they do not know how to handle
to another class with the appropriate name. For example if you lived in the Netherlands you would create a
class like:
class NlValidation {
public static function phone($check) {
// ...
}
public static function postal($check) {
// ...
}
}
When your model data is validated, Validation will see that it cannot handle the nl locale and will attempt to
delegate out to NlValidation::postal() and the return of that method will be used as the pass/fail
for the validation. This approach allows you to create classes that handle a subset or group of locales,
something that a large switch would not have. The usage of the individual validation methods has not
changed, the ability to pass off to another validator has been added.
Tip: The Localized Plugin already contains a lot of rules ready to use: https://github.com/cakephp/localized
Also feel free to contribute with your localized validation rules.
278
Chapter 6. Models
While normally you would just use the save method of the model, there may be times where you wish to
validate the data without saving it. For example, you may wish to display some additional information to
the user before actually saving the data to the database. Validating data requires a slightly different process
than just saving the data.
First, set the data to the model:
$this->ModelName->set($this->request->data);
Then, to check if the data validates, use the validates method of the model, which will return true if it
validates and false if it doesnt:
if ($this->ModelName->validates()) {
// it validated logic
} else {
// didnt validate logic
$errors = $this->ModelName->validationErrors;
}
It may be desirable to validate your model only using a subset of the validations specified in your model.
For example say you had a User model with fields for first_name, last_name, email and password. In this
instance when creating or editing a user you would want to validate all 4 field rules. Yet when a user logs in
you would validate just email and password rules. To do this you can pass an options array specifying the
fields to validate:
if ($this->User->validates(array(fieldList => array(email, password)))) {
// valid
} else {
// invalid
}
The validates method invokes the invalidFields method which populates the validationErrors property of the
model. The invalidFields method also returns that data as the result:
$errors = $this->ModelName->invalidFields(); // contains validationErrors array
The validation errors list is not cleared between successive calls to invalidFields() So if you are
validating in a loop and want each set of errors separately dont use invalidFields(). Instead use
validates() and access the validationErrors model property.
It is important to note that the data must be set to the model before the data can be validated. This is different
from the save method which allows the data to be passed in as a parameter. Also, keep in mind that it is
not required to call validates prior to calling save as save will automatically validate the data before actually
saving.
To validate multiple models, the following approach should be used:
if ($this->ModelName->saveAll(
$this->request->data, array(validate => only)
)) {
// validates
More on models
279
} else {
// does not validate
}
If you have validated data before save, you can turn off validation to avoid second check:
if ($this->ModelName->saveAll(
$this->request->data, array(validate => false)
)) {
// saving without validation
}
Callback Methods
If you want to sneak in some logic just before or after a CakePHP model operation, use model callbacks.
These functions can be defined in model classes (including your AppModel) class. Be sure to note the
expected return values for each of these special functions.
When using callback methods you should remember that behavior callbacks are fired before model callbacks
are.
beforeFind
beforeFind(array $query)
Called before any find-related operation. The $query 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 $query options),
return false. Otherwise, return the possibly modified $query, or anything you want to get passed to find
and its counterparts.
You might use this callback to restrict find operations based on a users role, or make caching decisions
based on the current load.
afterFind
afterFind(array $results, boolean $primary = false)
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 models
find operation, i.e. something like:
$results = array(
0 => array(
ModelName => array(
field1 => value1,
field2 => value2,
),
280
Chapter 6. Models
),
);
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 association
the format of $results can differ; instead of the result you would normally get from a find operation, you
may get this:
$results = array(
field_1 => value1,
field_2 => value2
);
Warning: Code expecting $primary to be true will probably get a Cannot use string offset as an
array fatal error from PHP if a recursive find is used.
Below is an example of how afterfind can be used for date formatting:
public function afterFind($results, $primary = false) {
foreach ($results as $key => $val) {
if (isset($val[Event][begindate])) {
$results[$key][Event][begindate] = $this->dateFormatAfterFind(
$val[Event][begindate]
);
}
}
return $results;
}
public function dateFormatAfterFind($dateString) {
return date(d-m-Y, strtotime($dateString));
}
beforeValidate
beforeValidate(array $options = array())
Use this callback to modify model data before it is validated, or to modify validation rules if required. This
function must also return true, otherwise the current save() execution will abort.
afterValidate
afterValidate()
Called after data has been checked for errors. Use this callback to perform any data cleanup or preparation
if needed.
More on models
281
beforeSave
beforeSave(array $options = array())
Place any pre-save logic in this function. This function executes immediately after model data has been
successfully validated, but just before the data is saved. This function should also return true if you want the
save operation to continue.
This callback is especially handy for any data-massaging logic that needs to happen before your data is
stored. If your storage engine needs dates in a 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.
public function beforeSave($options = array()) {
if (!empty($this->data[Event][begindate]) &&
!empty($this->data[Event][enddate])
) {
$this->data[Event][begindate] = $this->dateFormatBeforeSave(
$this->data[Event][begindate]
);
$this->data[Event][enddate] = $this->dateFormatBeforeSave(
$this->data[Event][enddate]
);
}
return true;
}
public function dateFormatBeforeSave($dateString) {
return date(Y-m-d, strtotime($dateString));
}
Tip: Be sure that beforeSave() returns true, or your save is going to fail.
afterSave
afterSave(boolean $created, array $options = array())
If you have logic you need to be executed just after every save operation, place it in this callback method.
The saved data will be available in $this->data.
The value of $created will be true if a new record was created (rather than an update).
The $options array is the same one passed to Model::save().
beforeDelete
beforeDelete(boolean $cascade = true)
282
Chapter 6. Models
Place any pre-deletion logic in this function. This function should return true if you want the deletion to
continue, and false if you want to abort.
The value of $cascade will be true if records that depend on this record will also be deleted.
Tip: Be sure that beforeDelete() returns true, or your delete is going to fail.
// using app/Model/ProductCategory.php
// In the following example, do not let a product category be deleted if it
// still contains products.
// A call of $this->Product->delete($id) from ProductsController.php has set
// $this->id .
// Assuming ProductCategory hasMany Product, we can access $this->Product
// in the model.
public function beforeDelete($cascade = true) {
$count = $this->Product->find("count", array(
"conditions" => array("product_category_id" => $this->id)
));
if ($count == 0) {
return true;
}
return false;
}
afterDelete
afterDelete()
Place any logic that you want to be executed after every deletion in this callback method.
// perhaps after deleting a record from the database, you also want to delete
// an associated file
public function afterDelete() {
$file = new File($this->data[SomeModel][file_path]);
$file->delete();
}
onError
onError()
Called if any problems occur.
Behaviors
Model behaviors are a way to organize some of the functionality defined in CakePHP models. They allow
us to separate and reuse logic that creates a type of behavior, and they do this without requiring inheritance.
For example creating tree structures. By providing a simple yet powerful way to enhance models, behaviors
allow us to attach functionality to models by defining a simple class variable. Thats how behaviors allow
More on models
283
models to get rid of all the extra weight that might not be part of the business contract they are modeling, or
that is also needed in different models and can then be extrapolated.
As an example, consider a model that gives us access to a database table which stores structural information
about a tree. Removing, adding, and migrating nodes in the tree is not as simple as deleting, inserting, and
editing rows in the table. Many records may need to be updated as things move around. Rather than creating
those tree-manipulation methods on a per model basis (for every model that needs that functionality), we
could simply tell our model to use the TreeBehavior, or in more formal terms, we tell our model to
behave as a Tree. This is known as attaching a behavior to a model. With just one line of code, our
CakePHP model takes on a whole new set of methods that allow it to interact with the underlying structure.
CakePHP already includes behaviors for tree structures, translated content, access control list interaction, not to mention the community-contributed behaviors already available in the CakePHP Bakery
(http://bakery.cakephp.org). In this section, well cover the basic usage pattern for adding behaviors to
models, how to use CakePHPs built-in behaviors, and how to create our own.
In essence, Behaviors are Mixins1 with callbacks.
There are a number of Behaviors included in CakePHP. To find out more about each one, reference the
chapters below:
ACL
class AclBehavior
The Acl behavior provides a way to seamlessly integrate a model with your ACL system. It can create both
AROs or ACOs transparently.
To use the new behavior, you can add it to the $actsAs property of your model. When adding it to the actsAs
array you choose to make the related Acl entry an ARO or an ACO. The default is to create ACOs:
class User extends AppModel {
public $actsAs = array(Acl => array(type => requester));
}
This would attach the Acl behavior in ARO mode. To join the ACL behavior in ACO mode use:
class Post extends AppModel {
public $actsAs = array(Acl => array(type => controlled));
}
For User and Group models it is common to have both ACO and ARO nodes, to achieve this use:
class User extends AppModel {
public $actsAs = array(Acl => array(type => both));
}
You can also attach the behavior on the fly like so:
$this->Post->Behaviors->load(Acl, array(type => controlled));
1
http://en.wikipedia.org/wiki/Mixin
284
Chapter 6. Models
Changed in version 2.1: You can now safely attach AclBehavior to AppModel. Aco, Aro and AclNode now
extend Model instead of AppModel, which would cause an infinite loop. If your application depends on
having those models to extend AppModel for some reason, then copy AclNode to your application and have
it extend AppModel again.
Using the AclBehavior
Most of the AclBehavior works transparently on your Models afterSave(). However, using it requires that
your Model has a parentNode() method defined. This is used by the AclBehavior to determine parent->child
relationships. A models parentNode() method must return null or return a parent Model reference:
public function parentNode() {
return null;
}
If you want to set an ACO or ARO node as the parent for your Model, parentNode() must return the alias of
the ACO or ARO node:
public function parentNode() {
return root_node;
}
A more complete example. Using an example User Model, where User belongsTo Group:
public function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
$data = $this->data;
if (empty($this->data)) {
$data = $this->read();
}
if (!$data[User][group_id]) {
return null;
}
return array(Group => array(id => $data[User][group_id]));
}
In the above example the return is an array that looks similar to the results of a model find. It is important
to have the id value set or the parentNode relation will fail. The AclBehavior uses this data to construct its
tree structure.
node()
The AclBehavior also allows you to retrieve the Acl node associated with a model record. After setting
$model->id. You can use $model->node() to retrieve the associated Acl node.
You can also retrieve the Acl Node for any row, by passing in a data array:
More on models
285
$this->User->id = 1;
$node = $this->User->node();
$user = array(User => array(
id => 1
));
$node = $this->User->node($user);
Containable
class ContainableBehavior
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. Since Containable only modifies existing
relationships it will not allow you to restrict results by distant associations. Instead you should refer to
Joining tables.
To use the new behavior, you can add it to the $actsAs property of your model:
class Post extends AppModel {
public $actsAs = array(Containable);
}
Using Containable
To see how Containable works, lets look at a few examples. First, well start off with a find() call on a
model named Post. Lets say that Post hasMany Comment, and Post hasAndBelongsToMany Tag.
The amount of data fetched in a normal find() call is rather extensive:
286
Chapter 6. Models
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
(...
More on models
287
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:
$this->Post->contain();
$this->Post->find(all);
You can also invoke Containables magic from inside the find() call:
$this->Post->find(all, array(contain => false));
Having done that, you end up with something a lot more concise:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[title] => Second article
[content] => bbb
[created] => 2008-05-19 00:00:00
)
)
This sort of help isnt new: in fact, you can do that without the ContainableBehavior doing something
like this:
$this->Post->recursive = -1;
$this->Post->find(all);
Containable really shines when you have complex associations, and you want to pare down things that sit
at the same level. The models $recursive property is helpful if you want to hack off an entire level of
recursion, but not when you want to pick and choose what to keep at each level. Lets see how it works by
using the contain() method.
The contain methods first argument accepts the name, or an array of names, of the models to keep in the
find operation. If we wanted to fetch all posts and their related tags (without any comment information),
wed try something like this:
$this->Post->contain(Tag);
$this->Post->find(all);
288
Chapter 6. Models
Without Containable, youd end up needing to use the unbindModel() method of the model, multiple
times if youre paring off multiple models. Containable creates a cleaner way to accomplish this same task.
Containing deeper associations
Containable also goes a step deeper: you can filter the data of the associated models. If you look at the
results of the original find() call, notice the author field in the Comment model. If you are interested in
the posts and the names of the comment authors and nothing else you could do something like the
following:
$this->Post->contain(Comment.author);
$this->Post->find(all);
// or..
$this->Post->find(all, array(contain => Comment.author));
Here, weve told Containable to give us our post information, and just the author field of the associated
Comment model. The output of the find call might look something like this:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[author] => Daniel
[post_id] => 1
)
[1] => Array
(
[author] => Sam
[post_id] => 1
)
)
)
[1] => Array
(...
As you can see, the Comment arrays only contain the author field (plus the post_id which is needed by
CakePHP to map the results).
You can also filter the associated Comment data by specifying a condition:
More on models
289
$this->Post->contain(Comment.author = "Daniel");
$this->Post->find(all);
//or...
$this->Post->find(all, array(contain => Comment.author = "Daniel"));
This gives us a result that gives us posts with comments authored by Daniel:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[author] => Daniel
[email] => [email protected]
[website] => http://example.com
[comment] => First comment
[created] => 2008-05-18 00:00:00
)
)
)
There is an important caveat to using Containable when filtering on a deeper association. In the previous
example, assume you had 3 posts in your database and Daniel had commented on 2 of those posts. The
operation $this->Post->find(all, array(contain => Comment.author = Daniel)); would return ALL
3 posts, not just the 2 posts that Daniel had commented on. It wont return all comments however, just
comments by Daniel.
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
290
Chapter 6. Models
[post_id] => 1
[author] => Daniel
[email] => [email protected]
[website] => http://example.com
[comment] => First comment
[created] => 2008-05-18 00:00:00
)
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[title] => Second article
[content] => bbb
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
)
)
[2] => Array
(
[Post] => Array
(
[id] => 3
[title] => Third article
[content] => ccc
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 22
[post_id] => 3
[author] => Daniel
[email] => [email protected]
[website] => http://example.com
[comment] => Another comment
[created] => 2008-05-18 00:00:00
)
)
)
If you want to filter the posts by the comments, so that posts without a comment by Daniel wont be returned,
the easiest way is to find all the comments by Daniel and contain the Posts.
$this->Comment->find(all, array(
conditions => Comment.author = "Daniel",
contain => Post
));
More on models
291
Heres an example of using the ContainableBehavior when youve got deep and complex model
relationships.
Lets consider the following model associations:
User->Profile
User->Account->AccountSummary
User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes
User->Post->Tag
Keep in mind that contain key is only used once in the main model, you dont need to use contain again
for related models.
Note: When using 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.
292
Chapter 6. Models
ContainableBehavior options
The ContainableBehavior has a number of options that can be set when the Behavior is attached to a
model. The settings allow you to 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.
order: (string, optional) the order of how the contained elements are sorted.
From the previous example, this is an example of how to force the posts to be ordered by the date when they
were last updated:
$this->User->find(all, array(
contain => array(
Profile,
Post => array(
order => Post.updated DESC
)
)
));
You can change ContainableBehavior settings at run time by reattaching the behavior as seen in Behaviors
(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 nonaggregate fields, try disabling the autoFields setting.
$this->Post->Behaviors->load(Containable, array(autoFields => false));
Using Containable with pagination By including the contain parameter in the $paginate property
it will apply to both the find(count) and the find(all) done on the model.
See the section Using Containable for further details.
Heres an example of how to contain associations when paginating:
$this->paginate[User] = array(
contain => array(Profile, Account),
order => User.username
);
$users = $this->paginate(User);
More on models
293
Note: If you contained the associations through the model instead, it will not honor Containables recursive
option. So if you set recursive to -1 for example for the model, it wont work:
$this->User->recursive = -1;
$this->User->contain(array(Profile, Account));
$users = $this->paginate(User);
Translate
class TranslateBehavior
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.
Initializing the i18n Database Tables
You can either use the CakePHP console or you can manually create it. It is advised to use the console for
this, because it might happen that the layout changes in future versions of CakePHP. Sticking to the console
will make sure that you have the correct layout.
./cake i18n
Select [I] which will run the i18n database initialization script. You will be asked if you want to drop any
existing and if you want to create it. Answer with yes if you are sure there is no i18n table already, and
answer with yes again to create the table.
Attaching the Translate Behavior to your Models
Add it to your model by using the $actsAs property like in the following example.
class Post extends AppModel {
public $actsAs = array(
Translate
);
}
This will do nothing yet, because it expects a couple of options before it begins to work. You need to define
which fields of the current model should be tracked in the translation table weve created in the first step.
Defining the Fields
You can set the fields by simply extending the Translate value with another array, like so:
294
Chapter 6. Models
After you have done that (for example putting title as one of the fields) you already finished the basic
setup. Great! According to our current example the model should now look something like this:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title
)
);
}
When defining fields for TranslateBehavior to translate, be sure to omit those fields from the translated
models schema. If you leave the fields in, there can be issues when retrieving data with fallback locales.
Note: If all the fields in your model are translated be sure to add created and modified columns to
your table. CakePHP requires at least one non primary key field before it will save a record.
Conclusion
From now on each record update/creation will cause TranslateBehavior to copy the value of title to the
translation table (default: i18n) along with the current locale. A locale is the identifier of the language, so to
speak.
Reading translated content
By default the TranslateBehavior will automatically fetch and add in data based on the current locale.
The current locale is read from Configure::read(Config.language) which is assigned by
the L10n class. You can override this default on the fly using $Model->locale.
Retrieve translated fields in a specific locale By setting $Model->locale you can read translations
for a specific locale:
// Read the spanish locale data.
$this->Post->locale = es;
$results = $this->Post->find(first, array(
conditions => array(Post.id => $id)
));
// $results will contain the spanish translation.
More on models
295
Retrieve all translation records for a field If you want to have all translation records attached to the
current model record you simply extend the field array in your behavior setup as shown below. The naming
is completely up to you.
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title => titleTranslation
)
);
}
With this setup the result of $this->Post->find() should look something like this:
Array
(
[Post] => Array
(
[id] => 1
[title] => Beispiel Eintrag
[body] => lorem ipsum...
[locale] => de_de
)
[titleTranslation] => Array
(
[0] => Array
(
[id] => 1
[locale] => en_us
[model] => Post
[foreign_key] => 1
[field] => title
[content] => Example entry
)
[1] => Array
(
[id] => 2
[locale] => de_de
[model] => Post
[foreign_key] => 1
[field] => title
[content] => Beispiel Eintrag
)
)
)
Note: The model record contains a virtual 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 as296
Chapter 6. Models
sociations wont be translated because triggering callbacks on associated models is currently not supported.
Using the bindTranslation method You can also retrieve all translations, only when you need them, using
the bindTranslation method
TranslateBehavior::bindTranslation($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.
$this->Post->bindTranslation(array(title => titleTranslation));
// need at least recursive 1 for this to work.
$this->Post->find(all, array(recursive => 1));
With this setup the result of your find() should look something like this:
Array
(
[Post] => Array
(
[id] => 1
[title] => Beispiel Eintrag
[body] => lorem ipsum...
[locale] => de_de
)
[titleTranslation] => Array
(
[0] => Array
(
[id] => 1
[locale] => en_us
[model] => Post
[foreign_key] => 1
[field] => title
[content] => Example entry
)
[1] => Array
(
[id] => 2
[locale] => de_de
[model] => Post
[foreign_key] => 1
[field] => title
[content] => Beispiel Eintrag
)
)
)
More on models
297
You can force the model which is using the TranslateBehavior to save in a language other than the one
detected.
To tell a model in what language the content is going to be you simply change the value of the $locale
property on the model before you save the data to the database. You can do that either in your controller or
you can define it directly in the model.
Example A: In your controller:
class PostsController extends AppController {
public function add() {
if (!empty($this->request->data)) {
// we are going to save the german version
$this->Post->locale = de_de;
$this->Post->create();
if ($this->Post->save($this->request->data)) {
return $this->redirect(array(action => index));
}
}
}
}
If you expect a lot entries you probably wonder how to deal with a rapidly growing database table. There are
two properties introduced by TranslateBehavior that allow to specify which Model to bind as the model
containing the translations.
These are $translateModel and $translateTable.
Lets say we want to save our translations for all posts in the table post_i18ns instead of the default i18n
table. To do so you need to setup your model like this:
298
Chapter 6. Models
Note: It is important that you to pluralize the table. It is now a usual model and can be treated as such and
thus comes with the conventions involved. The table schema itself must be identical with the one generated
by the CakePHP console script. To make sure it fits one could just initialize a empty i18n table using the
console and rename the table afterwards.
Create the TranslateModel For this to work you need to create the actual model file in your models
folder. Reason is that there is no property to set the displayField directly in the model using this behavior
yet.
Make sure that you change the $displayField to field.
class PostI18n extends AppModel {
public $displayField = field; // important
}
// filename: PostI18n.php
Thats all it takes. You can also add all other model stuff here like $useTable. But for better consistency we could do that in the model which actually uses this translation model. This is where the optional
$translateTable comes into play.
Changing the Table If you want to change the name of the table you simply define $translateTable in
your model, like so:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title
)
);
// Use a different model
public $translateModel = PostI18n;
// Use a different table for translateModel
public $translateTable = post_translations;
}
299
show you a Missing Table message for the default I18n model which is created in runtime.
Tree
class TreeBehavior
Its fairly common to want to store hierarchical data in a database table. Examples of such data might be
categories with unlimited subcategories, data related to a multilevel menu system or a literal representation
of hierarchy such as is used to store access control objects with ACL logic.
For small trees of data, or where the data is only a few levels deep it is simple to add a parent_id 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 logic2 without worrying about any of
the intricacies of the technique - unless you want to ;).
Requirements
To use the tree behavior, your database table needs 3 fields as listed below (all are ints):
parent - default fieldname is parent_id, to store the id of the parent object
left - default fieldname is lft, to store the lft value of the current row.
right - default fieldname is rght, to store the rght value of the current row.
If you are familiar with MPTT logic you may wonder why a parent field exists - quite simply its easier to
do certain tasks if a direct parent link is stored on the database - such as finding direct children.
Note: The parent field must be able to have a NULL value! It might seem to work if you just give the
top elements a parent value of zero, but reordering the tree (and possible other operations) will fail.
Basic Usage
The tree behavior has a lot packed into it, but lets start with a simple example - create the following database
table and put some data in it:
CREATE TABLE categories (
id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
parent_id INTEGER(10) DEFAULT NULL,
lft INTEGER(10) DEFAULT NULL,
rght INTEGER(10) DEFAULT NULL,
name VARCHAR(255) DEFAULT ,
PRIMARY KEY (id)
);
INSERT INTO
categories (id, name, parent_id, lft, rght)
VALUES
2
http://www.sitepoint.com/hierarchical-data-database-2/
300
Chapter 6. Models
More on models
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
301
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:
class CategoriesController extends AppController {
public function index() {
$data = $this->Category->generateTreeList(
null,
null,
null,
);
debug($data); die;
}
}
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
Adding data In the previous section, we used existing data and checked that it looked hierarchal via the
method generateTreeList. However, usually you would add your data in exactly the same way as you
would for any model. For example:
// pseudo controller code
$data[Category][parent_id] = 3;
$data[Category][name] = Skating;
$this->Category->save($data);
302
Chapter 6. Models
When using the tree behavior its not necessary to do any more than set the parent_id, and the tree behavior
will take care of the rest. If you dont set the parent_id, the tree behavior will add to the tree making your
new addition a new top level entry:
// pseudo controller code
$data = array();
$data[Category][name] = Other People\s Categories;
$this->Category->save($data);
Running the above two code snippets would alter your tree as follows:
My Categories - Fun
Sport - Surfing - Extreme knitting - Skating New
Friends - Gerald - Gwendolyn
Work - Reports
* Annual
* Status
* Trips - National - International
Other Peoples Categories New
Modifying data Modifying data is as transparent as adding new data. If you modify something, but do
not change the parent_id field - the structure of your data will remain unchanged. For example:
// pseudo controller code
$this->Category->id = 5; // id of Extreme knitting
$this->Category->save(array(name => Extreme fishing));
The above code did not affect the parent_id field - even if the parent_id is included in the data that is passed
to save if the value doesnt change, neither does the data structure. Therefore the tree of data would now
look like:
My Categories
Fun
Sport - Surfing - Extreme fishing Updated - Skating
Friends - Gerald - Gwendolyn
Work
Reports - Annual - Status
Trips - National - International
Other Peoples Categories
Moving data around in your tree is also a simple affair. Lets say that Extreme fishing does not belong under
Sport, but instead should be located under Other Peoples Categories. With the following code:
More on models
303
304
Chapter 6. Models
More on models
305
As with the method children, childCount takes the primary key value (the id) of a row and
returns how many children it has. The second optional parameter defines whether or not only direct
children are counted. Using the example data from the previous section:
$totalChildren = $this->Category->childCount(1); // will output 11
// -- or -$this->Category->id = 1;
$directChildren = $this->Category->childCount(); // will output 11
// Only counts the direct descendants of this category
$numChildren = $this->Category->childCount(1, true); // will output 2
Output:
array(
[1] =>
[2] =>
[3] =>
[4] =>
[16] =>
[6] =>
[7] =>
[8] =>
[9] =>
[13] =>
[14] =>
[15] =>
[17] =>
[5] =>
)
"My Categories",
"_Fun",
"__Sport",
"___Surfing",
"___Skating",
"__Friends",
"___Gerald",
"___Gwendolyn",
"_Work",
"__Trips",
"___National",
"___International",
"Other Peoples Categories",
"_Extreme fishing"
getParentNode()
306
Chapter 6. Models
This convenience function will, as the name suggests, return the parent node for any node, or false if
the node has no parent (its the root node). For example:
$parent = $this->Category->getParentNode(2); //<- id for fun
// $parent contains All categories
Advanced Usage
The tree behavior doesnt only work in the background, there are a number of specific methods defined in the behavior to cater for all your hierarchical data needs, and any unexpected
problems that might arise in the process.
TreeBehavior::moveDown()
Used to move a single node down the tree. You need to provide the ID of the element to be
moved and a positive number of how many positions the node should be moved down. All
child nodes for the specified node will also be moved.
More on models
307
Here is an example of a controller action (in a controller named Categories) that moves a specified node down the tree:
public function movedown($id = null, $delta = null) {
$this->Category->id = $id;
if (!$this->Category->exists()) {
throw new NotFoundException(__(Invalid category));
}
if ($delta > 0) {
$this->Category->moveDown($this->Category->id, abs($delta));
} else {
$this->Session->setFlash(
Please provide the number of positions the field should be .
moved down.
);
}
return $this->redirect(array(action => index));
}
For example, if youd like to move the Sport ( id of 3 ) category one position down, you would
request: /categories/movedown/3/1.
TreeBehavior::moveUp()
Used to move a single node up the tree. You need to provide the ID of the element to be moved
and a positive number of how many positions the node should be moved up. All child nodes
will also be moved.
Heres an example of a controller action (in a controller named Categories) that moves a node
up the tree:
public function moveup($id = null, $delta = null) {
$this->Category->id = $id;
if (!$this->Category->exists()) {
throw new NotFoundException(__(Invalid category));
}
if ($delta > 0) {
$this->Category->moveUp($this->Category->id, abs($delta));
} else {
$this->Session->setFlash(
Please provide a number of positions the category should .
be moved up.
);
}
return $this->redirect(array(action => index));
}
For example, if you would like to move the category Gwendolyn ( id of 8 ) up one position you
would request /categories/moveup/8/1. Now the order of Friends will be Gwendolyn, Gerald.
TreeBehavior::removeFromTree($id = null, $delete = false)
308
Chapter 6. Models
Using this method will either delete or move a node but retain its sub-tree, which will be reparented one level higher. It offers more control than delete, which for a model using the tree
behavior will remove the specified node and all of its children.
Taking the following tree as a starting point:
My Categories - Fun
Sport - Surfing - Extreme knitting - Skating
Running the following code with the id for Sport:
$this->Node->removeFromTree($id);
More on models
309
Note: If you have saved your data or made other operations on the model, you might want
to set $model->id = null before calling reorder. Otherwise only the current node and
its children will be reordered.
Data Integrity
Due to the nature of complex self referential data structures such as trees and linked lists, they
can occasionally become broken by a careless call. Take heart, for all is not lost! The Tree
Behavior contains several previously undocumented features designed to recover from such
situations.
TreeBehavior::recover($mode = parent, $missingParentAction = null)
The mode parameter is used to specify the source of info that is valid/correct. The opposite
source of data will be populated based upon that source of info. E.g. if the MPTT 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 missingParentAction parameter 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_ids 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
Example:
// Rebuild all the left and right fields based on the parent_id
$this->Category->recover();
// or
$this->Category->recover(parent);
// Rebuild all the parent_ids based on the lft and rght fields
$this->Category->recover(tree);
TreeBehavior::reorder($options = array())
Reorders the nodes (and child nodes) of the tree according to the field and direction specified in
the parameters. This method does not change the parent of any node.
310
Chapter 6. Models
Reordering affects all nodes in the tree by default, however the following options can affect the
process:
id - only reorder nodes below this node.
field - field to use for sorting, default is the displayField for the model.
order - ASC for ascending, DESC for descending sort.
verify - whether or not to verify the tree prior to resorting.
$options is used to pass all extra parameters, and has the following possible keys by default,
all of which are optional:
array(
id => null,
field => $model->displayField,
order => ASC,
verify => true
)
TreeBehavior::verify()
Returns true if the tree is valid otherwise an array of errors, with fields for type, incorrect
index and message.
Each record in the output array is an array of the form (type, id, message)
type is either index or node
id is the id of the erroneous node.
message depends on the error
Example Use:
$this->Category->verify();
Example output:
Array
(
[0] => Array
(
[0] =>
[1] =>
[2] =>
)
[1] => Array
(
[0] =>
[1] =>
[2] =>
)
[10] => Array
(
[0] =>
More on models
node
3
left and right values identical
node
2
The parent node 999 doesn t exist
index
311
Using Behaviors
Behaviors are attached to models through the $actsAs model class variable:
class Category extends AppModel {
public $actsAs = array(Tree);
}
This example shows how a Category model could be managed in a tree structure using the TreeBehavior.
Once a behavior has been specified, use the methods added by the behavior as if they always existed as part
of the original model:
// Set ID
$this->Category->id = 42;
// Use behavior method, children():
$kids = $this->Category->children();
Some behaviors may require or allow settings to be defined when the behavior is attached to the model.
Here, we tell our TreeBehavior the names of the left and right fields in the underlying database table:
class Category extends AppModel {
public $actsAs = array(Tree => array(
left => left_node,
right => right_node
));
}
We can also attach several behaviors to a model. Theres no reason why, for example, our Category model
should only behave as a tree, it may also need internationalization support:
class Category extends AppModel {
public $actsAs = array(
Tree => array(
left => left_node,
right => right_node
),
Translate
);
}
312
Chapter 6. Models
So far we have been adding behaviors to models using a model class variable. That means that our behaviors
will be attached to our models throughout the models lifetime. However, we may need to detach behaviors
from our models at runtime. Lets say that on our previous Category model, which is acting as a Tree and a
Translate model, we need for some reason to force it to stop acting as a Translate model:
// Detach a behavior from our model:
$this->Category->Behaviors->unload(Translate);
That will make our Category model stop behaving as a Translate model from thereon. We may need, instead,
to just disable the Translate behavior from acting upon our normal model operations: our 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:
// 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:
// If our behavior is not handling model callbacks
if (!$this->Category->Behaviors->enabled(Translate)) {
// Tell it to start doing so
$this->Category->Behaviors->enable(Translate);
}
Just as we could completely detach a behavior from a model at runtime, we can also attach new behaviors.
Say that our familiar Category model needs to start behaving as a Christmas model, but only on Christmas
day:
// If today is Dec 25
if (date(m/d) === 12/25) {
// Our model needs to behave as a Christmas model
$this->Category->Behaviors->load(Christmas);
}
Theres also a method to obtain the list of behaviors a model has attached. If we pass the name of a behavior
to the method, it will tell us if that behavior is attached to the model, otherwise it will give us the list of
attached behaviors:
// If the Translate behavior is not attached
if (!$this->Category->Behaviors->loaded(Translate)) {
// Get the list of all behaviors the model has attached
$behaviors = $this->Category->Behaviors->loaded();
}
More on models
313
Creating Behaviors
Behaviors that are attached to Models get their callbacks called automatically. The callbacks are similar to those found in Models: beforeFind, afterFind, beforeValidate, afterValidate,
beforeSave, afterSave, beforeDelete, afterDelete and onError - see Callback Methods.
Your behaviors should be placed in app/Model/Behavior. They are named in CamelCase and postfixed
by Behavior, ex. NameBehavior.php. Its often helpful to use a core behavior as a template when creating
your own. Find them in lib/Cake/Model/Behavior/.
Every callback and behavior method takes a reference to the model it is being called from as the 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:
class Post extends AppModel {
public $actsAs = array(
YourBehavior => array(
option1_key => option1_value
)
);
}
Since behaviors are shared across all the model instances that use them, its a good practice to store the
settings per alias/model name that is using the behavior. When created behaviors will have their setup()
method called:
public function setup(Model $Model, $settings = array()) {
if (!isset($this->settings[$Model->alias])) {
$this->settings[$Model->alias] = array(
option1_key => option1_default_value,
option2_key => option2_default_value,
option3_key => option3_default_value,
);
}
$this->settings[$Model->alias] = array_merge(
$this->settings[$Model->alias], (array)$settings);
}
You would be able to call FlyingBehavior methods as if they were methods on your Duck model.
When creating behavior methods you automatically get passed a reference of the calling model as the first
314
Chapter 6. Models
parameter. All other supplied parameters are shifted one place to the right. For example:
$this->Duck->fly(toronto, montreal);
Although this method takes two parameters, the method signature should look like:
public function fly(Model $Model, $from, $to) {
// Do some flying.
}
Keep in mind that methods called in a $this->doIt() fashion from inside a behavior method will not
get the $model parameter automatically appended.
Mapped methods
In addition to providing mixin methods, behaviors can also provide pattern matching methods. Behaviors
can also define mapped methods. Mapped methods use pattern matching for method invocation. This allows
you to create methods similar to Model::findAllByXXX methods on your behaviors. Mapped methods
need to be declared in your behaviors $mapMethods array. The method signature for a mapped method is
slightly different than a normal behavior mixin method:
class MyBehavior extends ModelBehavior {
public $mapMethods = array(/do(\w+)/ => doSomething);
public function doSomething(Model $model, $method, $arg1, $arg2) {
debug(func_get_args());
//do something
}
}
The above will map every doXXX() method call to the behavior. As you can see, the model is still the first
parameter, but the called method name will be the 2nd parameter. This allows you to munge the method
name for additional information, much like Model::findAllByXX. If the above behavior was attached
to a model the following would happen:
$model->doReleaseTheHounds(homer, lenny);
// would output
ReleaseTheHounds, homer, lenny
Behavior callbacks
Model Behaviors can define a number of callbacks that are triggered before the model callbacks of the
same name. Behavior callbacks allow your behaviors to capture events in attached models and augment the
parameters or splice in additional behavior.
All behavior callbacks are fired before the model callbacks are:
beforeFind
afterFind
More on models
315
beforeValidate
afterValidate
beforeSave
afterSave
beforeDelete
afterDelete
Creating a behavior callback
class ModelBehavior
Model behavior callbacks are defined as simple methods in your behavior class. Much like regular behavior
methods, they receive a $Model parameter as the first argument. This parameter is the model that the
behavior method was invoked on.
ModelBehavior::setup(Model $Model, array $settings = array())
Called when a behavior is attached to a model. The settings come from the attached models
$actsAs property.
ModelBehavior::cleanup(Model $Model)
Called when a behavior is detached from a model. The base method removes model settings based on
$model->alias. You can override this method and provide custom cleanup functionality.
ModelBehavior::beforeFind(Model $Model, array $query)
If a behaviors beforeFind returns false it will abort the find(). Returning an array will augment the
query parameters used for the find operation.
ModelBehavior::afterFind(Model $Model, mixed $results, boolean $primary = false)
You can use the afterFind to augment the results of a find. The return value will be passed on as the
results to either the next behavior in the chain or the models afterFind.
ModelBehavior::beforeValidate(Model $Model, array $options = array())
You can use beforeValidate to modify a models validate array or handle any other pre-validation
logic. Returning false from a beforeValidate callback will abort the validation and cause it to fail.
ModelBehavior::afterValidate(Model $Model)
You can use afterValidate to perform any data cleanup or preparation if needed.
ModelBehavior::beforeSave(Model $Model, array $options = array())
You can return false from a behaviors beforeSave to abort the save. Return true to allow it continue.
ModelBehavior::afterSave(Model $Model, boolean $created, array $options = array())
You can use afterSave to perform clean up operations related to your behavior. $created will be true
when a record is created, and false when a record is updated.
ModelBehavior::beforeDelete(Model $Model, boolean $cascade = true)
You can return false from a behaviors beforeDelete to abort the delete. Return true to allow it continue.
316
Chapter 6. Models
ModelBehavior::afterDelete(Model $Model)
You can use afterDelete to perform clean up operations related to your behavior.
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 class files in
lib/Cake/Model/Datasource/Database), a summary of which is listed here for your convenience:
MySql
Postgres
Sqlite
Sqlserver
Note: You can find additional community contributed datasources in the CakePHP DataSources repository
on GitHub3 .
When specifying a database connection configuration in app/Config/database.php, CakePHP transparently uses the corresponding database datasource for all model operations. So, even though you might
not have known about datasources, youve been using them all along.
All of the above sources derive from a base DboSource class, which aggregates some logic that is common
to most relational databases. If you decide to write a RDBMS datasource, working from one of these (e.g.
Mysql, or Sqlite) is your best bet.
Most people, however, are interested in writing datasources for external sources of data, such as remote
REST APIs or even an LDAP server. So thats what were going to look at now.
Basic API For DataSources
A datasource can, and should implement at least one of the following methods: create, read, update
and/or delete (the actual method signatures & implementation details are not important for the moment,
and will be described later). You need not implement more of the methods listed above than necessary - if
you need a read-only datasource, theres no reason to implement create, update, and delete.
Methods that must be implemented for all CRUD methods:
describe($model)
listSources($data = null)
calculate($model, $func, $params)
At least one of:
create(Model $model, $fields = null, $values = null)
3
https://github.com/cakephp/datasources/tree/2.0
More on models
317
318
Chapter 6. Models
More on models
319
}
/**
* Implement the R in CRUD. Calls to Model::find() arrive here.
*/
public function read(Model $model, $queryData = array(),
$recursive = null) {
/**
* Here we do the actual count as instructed by our calculate()
* method above. We could either check the remote source or some
* other way to get the record count. Here well simply return 1 so
* update() and delete() will assume the record exists.
*/
if ($queryData[fields] === COUNT) {
return array(array(array(count => 1)));
}
/**
* Now we get, decode and return the remote data.
*/
$queryData[conditions][apiKey] = $this->config[apiKey];
$json = $this->Http->get(
http://example.com/api/list.json,
$queryData[conditions]
);
$res = json_decode($json, true);
if (is_null($res)) {
$error = json_last_error();
throw new CakeException($error);
}
return array($model->alias => $res);
}
/**
* Implement the C in CRUD. Calls to Model::save() without $model->id
* set arrive here.
*/
public function create(Model $model, $fields = null, $values = null) {
$data = array_combine($fields, $values);
$data[apiKey] = $this->config[apiKey];
$json = $this->Http->post(http://example.com/api/set.json, $data);
$res = json_decode($json, true);
if (is_null($res)) {
$error = json_last_error();
throw new CakeException($error);
}
return true;
}
/**
* Implement the U in CRUD. Calls to Model::save() with $Model->id
* set arrive here. Depending on the remote source you can just call
* $this->create().
*/
320
Chapter 6. Models
We can then configure the datasource in our app/Config/database.php file by adding something
like this:
public $faraway = array(
datasource => FarAwaySource,
apiKey
=> 1234abcd,
);
We can retrieve data from our remote source using the familiar model methods:
// Get all messages from Some Person
$messages = $this->MyModel->find(all, array(
conditions => array(name => Some Person),
));
Tip: Using find types other than all can have unexpected results if the result of your read method is
not a numerically indexed array.
Similarly we can save a new message:
$this->MyModel->save(array(
name => Some Person,
message => New Message,
));
More on models
321
Plugin DataSources
You can also package Datasources into plugins.
Simply place your datasource file into Plugin/[YourPlugin]/Model/Datasource/[YourSource].php
and refer to it using the plugin notation:
public $faraway = array(
datasource => MyPlugin.FarAwaySource,
apiKey
=> abcd1234,
);
First check if the SQL Server PHP extension pdo_sqlsrv and the SQL Server Native Client are installed
properly.
Model Attributes
Model attributes allow you to set properties that can override the default model behavior.
For a complete list of model attributes and their descriptions visit the CakePHP API4 .
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 con4
http://api.cakephp.org/2.4/class-Model.html
322
Chapter 6. Models
nections 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:
class Example extends AppModel {
public $useDbConfig = alternate;
}
useTable
The useTable property specifies the database table name. By default, the model uses the lowercase, plural
form of the models class name. Set this attribute to the name of an alternate table, or set it to false if you
wish the model to use no database table.
Example usage:
class Example extends AppModel {
public $useTable = false; // This model does not use a database table
}
Alternatively:
class Example extends AppModel {
public $useTable = exmp; // This model uses a database table exmp
}
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:
class Example extends AppModel {
public $tablePrefix = alternate_; // will look for alternate_examples
}
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:
class Example extends AppModel {
// example_id is the field name in the database
More on models
323
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:
class User extends AppModel {
public $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. Instead create a virtual field with the
Model attribute virtualFields
recursive
The recursive property defines how deep CakePHP should go to fetch associated model data via find(),
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:
-1 CakePHP fetches Group data only, no joins.
0 CakePHP fetches Group data and its domain
1 CakePHP fetches a Group, its domain and its associated Users
2 CakePHP fetches a Group, its domain, its associated Users, and the Users associated Articles
Set it no higher than you need. Having CakePHP fetch data you arent going to use slows your app unnecessarily. Also note that the default recursive level is 1.
Note: If you want to combine $recursive with the fields functionality, you will have to add the columns
containing the required foreign keys to the fields array manually. In the example above, this could mean
adding domain_id.
Tip: The recommended recursive level for your application should be -1. This avoids retrieving related
data where that is unnecessary or even unwanted. This is most likely the case for most of your find() calls.
Raise it only when needed or use Containable behavior.
You can achieve that by adding it to the AppModel:
public $recursive = -1;
324
Chapter 6. Models
Tip: If you use events in your system, using the value -1 for recursive will disable all event triggering in
the associated model. This happens because no relations are created when the value is set to -1.
order
The default ordering of data for any find operation. Possible values include:
$order
$order
$order
$order
$order
$order
=
=
=
=
=
=
"field"
"Model.field";
"Model.field asc";
"Model.field ASC";
"Model.field DESC";
array("Model.field" => "asc", "Model.field2" => "DESC");
data
The container for the models fetched data. While data returned from a model class is normally used as
returned from a find() call, you may need to access information stored in $data inside of model callbacks.
_schema
Contains metadata describing the models database table fields. Each field is described by:
name
type (integer, string, datetime, etc.)
null
default value
length
Example Usage:
protected $_schema = array(
first_name => array(
type => string,
length => 30
),
last_name => array(
type => string,
length => 30
),
email => array(
type => string,
length => 30
),
More on models
325
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.
Note: It is not necessary to call validate() before save() as save() will automatically validate your data
before actually saving.
For more information on validation, see the Data Validation later on in this manual.
virtualFields
Array of virtual fields this model has. Virtual fields are aliased SQL expressions. Fields added to this
property will be read as other fields in a model but will not be saveable.
Example usage for MySQL:
public $virtualFields = array(
name => "CONCAT(User.first_name, , User.last_name)"
);
In subsequent find operations, your User results would contain a name key with the result of the concatenation. It is not advisable to create virtual fields with the same names as columns on the database, this can
cause SQL errors.
For more information on the virtualFields property, its proper usage, as well as limitations, see Virtual
fields.
name
Name of the model. If you do not specify it in your model file it will be set to the class name by constructor.
Example usage:
class Example extends AppModel {
public $name = Example;
}
cacheQueries
If set to true, data fetched by the model during a single request is cached. This caching is in-memory only,
and only lasts for the duration of the request. Any duplicate requests for the same data is handled by the
cache.
326
Chapter 6. Models
Model::associations()
Get associations:
$result = $this->Example->associations();
// $result equals array(belongsTo, hasOne, hasMany, hasAndBelongsToMany)
More on models
327
If ID is not provided it calls Model::getID() to obtain the current record ID to verify, and then performs
a Model::find(count) on the currently configured datasource to ascertain the existence of the
record in persistent storage.
Note: Parameter $id was added in 2.1. Prior to that it does not take any parameter.
$this->Example->id = 9;
if ($this->Example->exists()) {
// ...
}
$exists = $this->Foo->exists(2);
Model::getAffectedRows()
Returns the number of rows affected by the last query.
Model::getAssociated(string $type = null)
Gets all the models with which this model is associated.
Model::getColumnType(string $column)
Returns the column type of a column in the model.
Model::getColumnTypes()
Returns an associative array of field names and column types.
Model::getID(integer $list = 0)
Returns the current records ID.
Model::getInsertID()
Returns the ID of the last record this model inserted.
Model::getLastInsertID()
Alias to getInsertID().
328
Chapter 6. Models
Virtual fields
Virtual fields allow you to create arbitrary SQL expressions and assign them as fields in a Model. These
fields cannot be saved, but will be treated like other model fields for read operations. They will be indexed
under the models key alongside other model fields.
Creating virtual fields
Creating virtual fields is easy. In each model you can define a $virtualFields property that contains
an array of field => expressions. An example of a virtual field definition using MySQL would be:
public $virtualFields = array(
name => CONCAT(User.first_name, " ", User.last_name)
);
In subsequent find operations, your User results would contain a name key with the result of the concatenation. It is not advisable to create virtual fields with the same names as columns on the database, this can
cause SQL errors.
It is not always useful to have User.first_name fully qualified. If you do not follow the convention (i.e. you
have multiple relations to other tables) this would result in an error. In this case it may be better to just use
first_name || \ \ || last_name without the Model Name.
Using virtual fields
Creating virtual fields is straightforward and easy, interacting with virtual fields can be done through a few
different methods.
Model::hasField()
Model::hasField() will return true if the model has a concrete field passed by the first parameter. By setting
the second parameter of hasField() to true, virtualFields will also be checked when checking if a model has
a field. Using the example field above:
// Will return false, as there is no concrete field called name
$this->User->hasField(name);
// Will return true as there is a virtual field called name
$this->User->hasField(name, true);
More on models
329
Model::isVirtualField()
This method can be used to check if a field/column is a virtual field or a concrete field. Will return true if
the column is virtual:
$this->User->isVirtualField(name); //true
$this->User->isVirtualField(first_name); //false
Model::getVirtualField()
This method can be used to access the SQL expression that comprises a virtual field. If no argument is
supplied it will return all virtual fields in a Model:
//returns CONCAT(User.first_name, , User.last_name)
$this->User->getVirtualField(name);
As stated earlier Model::find() will treat virtual fields much like any other field in a model. The value
of a virtual field will be placed under the models key in the resultset:
$results = $this->User->find(first);
// results contains the following
array(
User => array(
first_name => Mark,
last_name => Story,
name => Mark Story,
//more fields.
)
);
Since virtual fields behave much like regular fields when doing finds, Controller::paginate() will
be able to sort by virtual fields too.
Virtual fields and model aliases
When you are using virtualFields and models with aliases that are not the same as their name, you can run
into problems as virtualFields do not update to reflect the bound alias. If you are using virtualFields in
models that have more than one alias it is best to define the virtualFields in your models constructor:
public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
$this->virtualFields[name] = sprintf(
330
Chapter 6. Models
This will allow your virtualFields to work for any alias you give a model.
Virtual fields in SQL queries
Using functions in direct SQL queries will prevent data from being returned in the same array as your
models data. For example this:
$this->Timelog->query(
"SELECT
project_id, SUM(id) as TotalHours
FROM
timelogs
AS
Timelog
GROUP BY
project_id;"
);
If we want to group TotalHours into our Timelog array we should specify a virtual field for our aggregate
column. We can add this new virtual field on the fly rather than permanently declaring it in the model. We
will provide a default value of 0 in case another query attempts to use this virtual field. If that were to occur,
0 would be returned in the TotalHours column:
$this->Timelog->virtualFields[TotalHours] = 0;
In addition to adding the virtual field we also need to alias our column using the form of
MyModel__MyField like this:
$this->Timelog->query(
"SELECT
project_id, SUM(id) as Timelog__TotalHours
More on models
331
FROM
timelogs
AS
Timelog
GROUP BY
project_id;"
);
Running the query again after specifying the virtual field should result in a cleaner grouping of values:
Array
(
[0] => Array
(
[Timelog] => Array
(
[project_id] => 1234
[TotalHours] => 25.5
)
)
)
Limitations of virtualFields
The implementation of virtualFields has a few limitations. First you cannot use virtualFields
on associated models for conditions, order, or fields arrays. Doing so will generally result in an SQL error
as the fields are not replaced by the ORM. This is because it difficult to estimate the depth at which an
associated model might be found.
A common workaround for this implementation issue is to copy virtualFields from one model to
another at runtime when you need to access them:
$this->virtualFields[name] = $this->Author->virtualFields[name];
or:
$this->virtualFields += $this->Author->virtualFields;
Transactions
To perform a transaction, a models table must be of a datasource and type which supports transactions.
All transaction methods must be performed on a models DataSource object. To get a models DataSource
from within the model, use:
$dataSource = $this->getDataSource();
You can then use the data source to start, commit, or roll back transactions.
$dataSource->begin();
332
Chapter 6. Models
Nested Transactions
It is possible to start a transaction several times using the Datasource::begin() method. The transaction will finish only when the number of commit and rollback calls match with begin.
$dataSource->begin();
// Perform some tasks
$dataSource->begin();
// More few tasks
if (/*latest task ok*/) {
$dataSource->commit();
} else {
$dataSource->rollback();
// Change something in main task
}
$dataSource->commit();
This will perform the real nested transaction if your database supports it and it is enabled in the datasource.
The methods will always return true when in transaction mode and the nested is not supported or disabled.
If you want to use multiple begins but not use the nested transaction from database, disable it using
$dataSource->useNestedTransactions = false;. It will use only one global transaction.
The
real
nested
transaction
is
disabled
by
$dataSource->useNestedTransactions = true;.
More on models
default.
Enable
it
using
333
334
Chapter 6. Models
CHAPTER 7
Core Libraries
CakePHP comes with a plethora of built-in functions and classes. These classes and functions try to cover
some of the most common features required in web applications.
General Purpose
General purpose libraries are available and reused in many places across CakePHP.
General Purpose
Global Constants and Functions
While most of your day-to-day work in CakePHP will be utilizing core classes and methods, CakePHP
features a number of global convenience functions that may come in handy. Many of these functions are
for use with CakePHP classes (loading model or component classes), but many others make working with
arrays or strings a little easier.
Well also cover some of the constants available in CakePHP applications. Using these constants will help
make upgrades more smooth, but are also convenient ways to point to certain files or directories in your
CakePHP application.
Global Functions
Here are CakePHPs globally available functions. Most of them are just convenience wrappers for other
CakePHP functionality, such as debugging and translating content.
__(string $string_id[, $formatArgs ])
This function handles localization in CakePHP applications. The $string_id identifies the ID for
a translation. Strings used for translations are treated as format strings for sprintf(). You can
supply additional arguments to replace placeholders in your string:
335
Note: Check out the Internationalization & Localization section for more information.
__c(string $msg, integer $category, mixed $args = null)
Note that the category must be specified with an I18n class constant, instead of only the constant
name. The values are:
I18n::LC_ALL - LC_ALL
I18n::LC_COLLATE - LC_COLLATE
I18n::LC_CTYPE - LC_CTYPE
I18n::LC_MONETARY - LC_MONETARY
I18n::LC_NUMERIC - LC_NUMERIC
I18n::LC_TIME - LC_TIME
I18n::LC_MESSAGES - LC_MESSAGES
__d(string $domain, string $msg, mixed $args = null)
Allows you to override the current domain for a single message lookup.
Useful when internationalizing a plugin:
plugin);
I18n::LC_ALL - LC_ALL
I18n::LC_COLLATE - LC_COLLATE
I18n::LC_CTYPE - LC_CTYPE
I18n::LC_MONETARY - LC_MONETARY
I18n::LC_NUMERIC - LC_NUMERIC
I18n::LC_TIME - LC_TIME
I18n::LC_MESSAGES - LC_MESSAGES
__dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null)
Allows you to override the current domain for a single plural message lookup. Returns correct plural
form of message identified by $singular and $plural for count $count from domain $domain.
__n(string $singular, string $plural, integer $count, mixed $args = null)
Returns correct plural form of message identified by $singular and $plural for count $count. Some
languages have more than one form for plural messages dependent on the count.
am(array $one, $two, $three...)
Merges all the arrays passed as parameters and returns the merged array.
config()
Can be used to load files from your application config-folder via include_once. Function checks
for existence before include and returns boolean. Takes an optional number of arguments.
Example: config(some_file, myconfig);
convertSlash(string $string)
Converts forward slashes to underscores and removes the first and last underscores in a string. Returns
the converted string.
debug(mixed $var, boolean $showHtml = null, $showFrom = true)
If the applications DEBUG level is non-zero, $var is printed out. If $showHTML is true or left as
null, the data is rendered to be browser-friendly. If $showFrom is not set to false, the debug output
will start with the line from which it was called. Also see Debugging
stackTrace(array $options = array())
If the applications DEBUG level is non-zero, the stack trace is printed out.
env(string $key)
Gets an environment variable from available sources. Used as a backup if $_SERVER or $_ENV are
disabled.
This function also emulates PHP_SELF and DOCUMENT_ROOT on unsupporting servers. In fact,
its a good idea to always use env() instead of $_SERVER or getenv() (especially if you plan to
distribute the code), since its a full emulation wrapper.
fileExistsInPath(string $file)
Checks to make sure that the supplied file is within the current PHP include_path. Returns a boolean
result.
h(string $text, boolean $double = true, string $charset = null)
Convenience wrapper for htmlspecialchars().
General Purpose
337
LogError(string $message)
Shortcut to Log::write().
pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)
Splits a dot syntax plugin name into its plugin and class name. If $name does not have a dot, then
index 0 will be null.
Commonly used like list($plugin, $name) = pluginSplit(Users.User);
pr(mixed $var)
Convenience wrapper for print_r(), with the addition of wrapping <pre> tags around the output.
sortByKey(array &$array, string $sortby,
SORT_NUMERIC)
Sorts given $array by key $sortby.
integer $type =
stripslashes_deep(array $value)
Recursively strips slashes from the supplied $value. Returns the modified array.
Core Definition Constants
constant FULL_BASE_URL
Full URL prefix. Such as https://example.com
Deprecated since version 2.4:
Router::fullbaseUrl() instead.
This
constant
is
deprecated,
you
should
use
constant IMAGES
Path to the public images directory.
Deprecated since version 2.4.
constant IMAGES_URL
Web path to the public images directory.
Deprecated since version 2.4: Use config value App.imageBaseUrl instead.
constant JS
Path to the public JavaScript directory.
Deprecated since version 2.4.
constant JS_URL
Web path to the js files directory.
Deprecated since version 2.4: Use config value App.jsBaseUrl instead.
constant LOGS
Path to the logs directory.
constant ROOT
Path to the root directory.
constant TESTS
Path to the tests directory.
constant TMP
Path to the temporary files directory.
constant VENDORS
Path to the vendors directory.
constant WEBROOT_DIR
Equals webroot or the name of your webroot directory.
constant WWW_ROOT
Full path to the webroot.
Timing Definition Constants
constant TIME_START
Unix timestamp in microseconds as a float from when the application started.
constant SECOND
Equals 1
General Purpose
339
constant MINUTE
Equals 60
constant HOUR
Equals 3600
constant DAY
Equals 86400
constant WEEK
Equals 604800
constant MONTH
Equals 2592000
constant YEAR
Equals 31536000
App Class
class App
The app class is responsible for path management, class location and class loading. Make sure you follow
the File and Class Name Conventions.
Packages
CakePHP is organized around the idea of packages, each class belongs to a package or folder
where other classes reside. You can configure each package location in your application using
App::build(APackage/SubPackage, $paths) to inform the framework where should each
class be loaded. Almost every class in the CakePHP framework can be swapped with your own compatible
implementation. If you wish to use your own class instead of the classes the framework provides, just add
the class to your libs folder emulating the directory location of where CakePHP expects to find it.
For instance if youd like to use your own HttpSocket class, put it under:
app/Lib/Network/Http/HttpSocket.php
Once youve done this App will load your override file instead of the file inside CakePHP.
Loading classes
Loading files from plugins Loading classes in plugins works much the same as loading app and core
classes except you must specify the plugin you are loading from:
// Load the class Comment in app/Plugin/PluginName/Model/Comment.php
App::uses(Comment, PluginName.Model);
// Load the class CommentComponent in
// app/Plugin/PluginName/Controller/Component/CommentComponent.php
App::uses(CommentComponent, PluginName.Controller/Component);
This can be done for all packages that are apart of your application. You can also fetch paths for a
plugin:
// return the component paths in DebugKit
App::path(Component, DebugKit);
static App::paths()
Return type array
General Purpose
341
Get all the currently loaded paths from App. Useful for inspecting or storing all paths App knows
about. For a paths to a specific package use App::path()
static App::core(string $package)
Return type array
Used for finding the path to a package inside CakePHP:
// Get the path to Cache engines.
App::core(Cache/Engine);
If reset is set to true, all loaded plugins will be forgotten and they will be needed to be loaded again.
Examples:
App::build(array(controllers => array(/full/path/to/controllers/)));
//becomes
App::build(array(Controller => array(/full/path/to/Controller/)));
App::build(array(helpers => array(/full/path/to/views/helpers/)));
//becomes
App::build(array(View/Helper => array(/full/path/to/View/Helper/)));
342
Changed in version 2.0: App::build() will not merge app paths with core paths anymore.
Add new packages to an application App::build() can be used to add new package locations. This
is useful when you want to add new top level packages or, sub-packages to your application:
App::build(array(
Service => array(%s . Service . DS)
), App::REGISTER);
The %s in newly registered packages will be replaced with the APP path. You must include a trailing / in
registered packages. Once packages are registered, you can use App::build() to append/prepend/reset
paths like any other package.
Changed in version 2.1: Registering packages was added in 2.1
Finding which objects CakePHP knows about
You can also search only within a plugins objects by using the plugin dot syntax.
// returns array(MyPluginPost, MyPluginComment);
App::objects(MyPlugin.Model);
General Purpose
343
Plugins can be located with App as well. Using App::pluginPath(DebugKit); for example, will give you the full path to the DebugKit plugin:
$path = App::pluginPath(DebugKit);
Locating themes
static App::import(mixed $type = null, string $name = null, mixed $parent = true, array $search
= array(), string $file = null, boolean $return = false)
Return type boolean
At first glance App::import seems complex, however in most use cases only 2 arguments are
required.
Note: This method is equivalent to requireing the file. It is important to realize that the class
subsequently needs to be initialized.
// The same as require(Controller/UsersController.php);
App::import(Controller, Users);
// We need to load the class
$Users = new UsersController();
// If we want the model associations, components, etc to be loaded
$Users->constructClasses();
All classes that were loaded in the past using App::import(Core, $class) will need to be loaded
using App::uses() referring to the correct package. This change has provided large performance
gains to the framework.
Changed in version 2.0.
The method no longer looks for classes recursively, it strictly uses the values for the paths defined
in App::build()
It will not be able to load App::import(Component, Component) use
App::uses(Component, Controller);.
Using App::import(Lib, CoreClass); to load core classes is no longer possible.
Importing a non-existent file, supplying a wrong type or package name, or null values for $name
and $file parameters will result in a false return value.
344
App::import(Core, CoreClass) is no longer supported, use App::uses() instead and let the class autoloading do the rest.
Loading Vendor files does not look recursively in the vendors folder, it will also not convert the
file to underscored anymore as it did in the past.
Overriding classes in CakePHP
You can override almost every class in the framework, exceptions are the App and Configure classes.
Whenever you like to perform such overriding, just add your class to your app/Lib folder mimicking the
internal structure of the framework. Some examples to follow:
To override the Dispatcher class, create app/Lib/Routing/Dispatcher.php
To override the CakeRoute class, create app/Lib/Routing/Route/CakeRoute.php
To override the Model class, create app/Lib/Model/Model.php
When you load the overridden classes now, the files in app/Lib will be loaded instead of the built-in core
ones.
Loading Vendor Files
You can use App::uses() to load classes in vendors directories. It follows the same conventions as
loading other files:
// Load the class Geshi in app/Vendor/Geshi.php
App::uses(Geshi, Vendor);
To load classes in subdirectories, youll need to add those paths with App::build():
// Load the class ClassInSomePackage in
// app/Vendor/SomePackage/ClassInSomePackage.php
App::build(array(Vendor => array(APP . Vendor . DS . SomePackage . DS)));
App::uses(ClassInSomePackage, Vendor);
Your vendor files may not follow conventions, have a class that differs from the file name or does not contain
classes. You can load those files using App::import(). 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/Vendor/geshi.php:
App::import(Vendor, geshi);
Note: The geshi file must be a lower-case file name as CakePHP will not find it otherwise.
To load app/Vendor/flickr/flickr.php:
App::import(Vendor, flickr, array(file => flickr/flickr.php));
General Purpose
345
To load app/Vendor/some.name.php:
App::import(Vendor, SomeName, array(file => some.name.php));
To load app/Vendor/services/well.named.php:
App::import(
Vendor,
WellNamed,
array(file => services . DS . well.named.php)
);
To load app/Plugin/Awesome/Vendor/services/well.named.php:
App::import(
Vendor,
Awesome.WellNamed,
array(file => services . DS . well.named.php)
);
To load app/Plugin/Awesome/Vendor/Folder/Foo.php:
App::import(
Vendor,
Awesome.Foo,
array(file => Folder . DS . Foo.php));
It wouldnt make a difference if your vendor files are inside your /vendors directory. CakePHP will automatically find it.
To load vendors/vendorName/libFile.php:
App::import(
Vendor,
aUniqueIdentifier,
array(file => vendorName . DS . libFile.php)
);
static App::init()
Return type void
Initializes the cache for App, registers a shutdown function.
static App::load(string $className)
Return type boolean
Method to handle the automatic class loading. It will look for each class package defined using
App::uses() and with this information it will resolve the package name to a full path to load the
class from. File name for each class should follow the class name. For instance, if a class is name
MyCustomClass the file name should be MyCustomClass.php
346
static App::shutdown()
Return type void
Object destructor. Writes cache file if changes have been made to the $_map.
Events System
New in version 2.1.
Creating maintainable applications is both a science and an art. It is well-known that a key for having good
quality code is making your objects loosely coupled and strongly cohesive at the same time. Cohesion
means that all methods and properties for a class are strongly related to the class itself and it is not trying
to do the job other objects should be doing, while loosely coupling is the measure of how little a class is
wired to external objects, and how much that class is depending on them.
There are certain cases where you need to cleanly communicate with other parts of an application, without
having to hard code dependencies, thus losing cohesion and increasing class coupling. Using the Observer
pattern, which allows objects to notify other objects and anonymous listeners about changes is a useful
pattern to achieve this goal.
Listeners in the observer pattern can subscribe to events and choose to act upon them if they are relevant. If
you have used JavaScript, there is a good chance that you are already familiar with event driven programming.
CakePHP emulates several aspects of how events are triggered and managed in popular JavaScript libraries
such as jQuery. In the CakePHP implementation, an event object is dispatched to all listeners. The event
object holds information about the event, and provides the ability to stop event propagation at any point.
Listeners can register themselves or can delegate this task to other objects and have the chance to alter the
state and the event itself for the rest of the callbacks.
The event subsystem is at the heart of Model, Behavior, Controller, View and Helper callbacks. If youve
ever used any of them, you are already somewhat familiar with events in CakePHP.
Example Event Usage
Lets suppose you are building a Cart plugin, and youd like to focus on just handling order logic. You dont
really want to include shipping logic, emailing the user or decrementing the item from the stock, but these
are important tasks to the people using your plugin. If you were not using events, you may try to implement
this by attaching behaviors to models, or adding components to your controllers. Doing so represents a
challenge most of the time, since you would have to come up with the code for externally loading those
behaviors or attaching hooks to your plugin controllers.
Instead, you can use events to allow you to cleanly separate the concerns of your code and allow additional
concerns to hook into your plugin using events. For example in your Cart plugin you have an Order model
that deals with creating orders. Youd like to notify the rest of the application that an order has been created.
To keep your Order model clean you could use events:
// Cart/Model/Order.php
App::uses(CakeEvent, Event);
class Order extends AppModel {
General Purpose
347
The above code allows you to easily notify the other parts of the application that an order has been created.
You can then do tasks like send email notifications, update stock, log relevant statistics and other tasks in
separate objects that focus on those concerns.
Accessing Event Managers
In CakePHP events are triggered against event managers. Event managers are available in every Model,
View and Controller using getEventManager():
$events = $this->getEventManager();
Each model has a separate event manager, while the View and Controller share one. This allows model
events to be self contained, and allow components or controllers to act upon events created in the view if
necessary.
Global Event Manager In addition to instance level event managers, CakePHP provides a global event
manager that allows you to listen to any event fired in an application. This is useful when attaching listeners
to a specific instance might be cumbersome or difficult. The global manager is a singleton instance of
CakeEventManager. When an event is dispatched, it will be dispatched to the both the global and
instance level listeners in priority order. You can access the global manager using a static method:
// In any configuration file or piece of code that executes before the event
App::uses(CakeEventManager, Event);
CakeEventManager::instance()->attach(
$aCallback,
Model.Order.afterPlace
);
One important thing you should consider is that there are events that will be triggered having the same name
but different subjects, so checking it in the event object is usually required in any function that gets attached
globally in order to prevent some bugs. Remember that with the flexibility of using the global manager,
some additional complexity is incurred.
Changed in version 2.5: Prior to 2.5, listeners on the global manager were kept in a separate list and fired
before instance listeners are. After 2.5, global and instance listeners are fired in priority order.
348
Dispatching Events
Once you have obtained an instance of an event manager you can dispatch events using dispatch(). This
method takes an instance of the CakeEvent class. Lets look at dispatching an event:
// Create a new event and dispatch it.
$event = new CakeEvent(Model.Order.afterPlace, $this, array(
order => $order
));
$this->getEventManager()->dispatch($event);
CakeEvent accepts 3 arguments in its constructor. The first one is the event name, you should try
to keep this name as unique as possible, while making it readable. We suggest a convention as follows: Layer.eventName for general events happening at a layer level (e.g. Controller.startup,
View.beforeRender) and Layer.Class.eventName for events happening in specific classes on a
layer, for example Model.User.afterRegister or Controller.Courses.invalidAccess.
The second argument is the subject, meaning the object associated to the event, usually when it is
the same class triggering events about itself, using $this will be the most common case. Although a
Component could trigger controller events too. The subject class is important because listeners will get
immediate access to the object properties and have the chance to inspect or change them on the fly.
Finally, the third argument is any additional event data.This can be any data you consider useful to pass
around so listeners can act upon it. While this can be an argument of any type, we recommend passing an
associative array.
The dispatch() method accepts an event object as an argument and notifies all subscribed listeners.
Registering Listeners
Listeners are the preferred way to register callbacks for an event. This is done by implementing the
CakeEventListener interface in any class you wish to register some callbacks. Classes implementing it need to provide the implementedEvents() method. This method must return an associative
array with all event names that the class will handle.
To continue our previous example, lets imagine we have a UserStatistic class responsible for calculating
a users purchasing history, and compiling into global site statistics. This is a great place to use a listener
class. Doing so allows you concentrate the statistics logic in one place and react to events as necessary. Our
UserStatistics listener might start out like:
// In app/Lib/Event/UserStatistic.php
App::uses(CakeEventListener, Event);
class UserStatistic implements CakeEventListener {
public function implementedEvents() {
return array(
Model.Order.afterPlace => updateBuyStatistic,
);
}
General Purpose
349
As you can see in the above code, the attach function will accept instances of the
CakeEventListener interface. Internally, the event manager will use implementedEvents to attach the correct callbacks.
Registering Global Listeners As shown in the example above, event listeners are conventionally placed
in app/Lib/Event. Following this convention allows you to easily locate your listener classes. It is also
recommended that you attach global listeners during your application bootstrap process:
// In app/Config/bootstrap.php
// Load the global event listeners.
require_once APP . Config . DS . events.php
An example events bootstrap file for our cart application could look like:
// In app/Config/events.php
// Load event listeners
App::uses(UserStatistic, Lib/Event);
App::uses(ProductStatistic, Lib/Event);
App::uses(CakeEventManager, Event);
// Attach listeners.
CakeEventManager::instance()->attach(new UserStatistic());
CakeEventManager::instance()->attach(new ProductStatistic());
Registering Anonymous Listeners While event listener objects are generally a better way to implement
listeners, you can also bind any callable as an event listener. For example if we wanted to put any orders
into the log files, we could use a simple anonymous function to do so:
// Anonymous functions require PHP 5.3+
$this->Order->getEventManager()->attach(function($event) {
CakeLog::write(
info,
A new order was placed with id: . $event->subject()->id
);
}, Model.Order.afterPlace);
In addition to anonymous functions you can use any other callable type that PHP supports:
$events = array(
email-sending => EmailSender::sendBuyEmail,
350
Establishing Priorities In some cases you might want to control the order that listeners are invoked. For
instance, if we go back to our user statistics example. It would ideal if this listener was called at the end of
the stack. By calling it at the end of the listener stack, we can ensure that the event was not canceled, and
that no other listeners raised exceptions. We can also get the final state of the objects in the case that other
listeners have modified the subject or event object.
Priorities are defined as an integer when adding a listener. The higher the number, the later the method will
be fired. The default priority for all listeners is 10. If you need your method to be run earlier, using any
value below this default will work. On the other hand if you desire to run the callback after the others, using
a number above 10 will do.
If two callbacks happen to have the same priority value, they will be executed with a the order they
were attached. You set priorities using the attach method for callbacks, and declaring it in the
implementedEvents function for event listeners:
// Setting priority for a callback
$callback = array($this, doSomething);
$this->getEventManager()->attach(
$callback,
Model.Order.afterPlace,
array(priority => 2)
);
// Setting priority for a listener
class UserStatistic implements CakeEventListener {
public function implementedEvents() {
return array(
Model.Order.afterPlace => array(
callable => updateBuyStatistic,
priority => 100
),
);
}
}
As you see, the main difference for CakeEventListener objects is that you need to use an array for
specifying the callable method and the priority preference. The callable key is an special array entry
that the manager will read to know what function in the class it should be calling.
Getting Event Data as Function Parameters By default listeners receive the event object as their only
parameter. If you are building an event that doesnt need access to the event object you may want to have
the event data passed as function parameters. This feature is used by the callbacks CakePHP fires in order
to preserve backwards compatibility.
General Purpose
351
If you want to enable this feature, you have to add the passParams option to the third argument of the
attach method, or declare it in the implementedEvents returned array similar to what you do with
priorities:
// Enabling passed parameters mode for an anonymous listener
$callback = array($this, doSomething);
$this->getEventManager()->attach(
$callback,
Model.Order.afterPlace,
array(passParams => true)
);
// Enabling passed parameters mode for a listener
class UserStatistic implements CakeEventListener {
public function implementedEvents() {
return array(
Model.Order.afterPlace => array(
callable => updateBuyStatistic,
passParams => true
),
);
}
public function updateBuyStatistic($orderData) {
// ...
}
}
In the above code the doSomething function and updateBuyStatistic method will receive
$orderData instead of the $event object. This is so, because in our previous example we trigger
the Model.Order.afterPlace event with some data:
$event = new CakeEvent(Model.Order.afterPlace, $this, array(
order => $order
));
$this->getEventManager()->dispatch($event);
Note: The params can only be passed as function arguments if the event data is an array. Any other data
type cannot be converted to function parameters, thus not using this option is often the most adequate choice.
Stopping Events Much like DOM events, you may want to stop an event to prevent additional listeners
from being notified. You can see this in action during model callbacks (e.g. beforeSave) in which it is
possible to stop the saving operation if the code detects it cannot proceed any further.
In order to stop events you can either return false in your callbacks or call the stopPropagation
method on the event object:
public function doSomething($event) {
// ...
return false; // stops the event
}
352
Stopping an event will prevent any additional callbacks from being called. Additionally the code triggering
the event may behave differently based on the event being stopped or not. Generally it does not make
sense to stop after events, but stopping before events is often used to prevent the entire operation from
occurring.
To check if an event was stopped, you call the isStopped() method in the event object:
public function place($order) {
$event = new CakeEvent(
Model.Order.beforePlace,
$this, array(order => $order)
);
$this->getEventManager()->dispatch($event);
if ($event->isStopped()) {
return false;
}
if ($this->Order->save($order)) {
// ...
}
// ...
}
In the previous example the order would not get saved if the event is stopped during the beforePlace
process.
Getting Event Results Every time a callback returns a value, it gets stored in the $result property of
the event object. This is useful when you want to allow callbacks to modify the event execution. Lets take
again our beforePlace example and let callbacks modify the $order data.
Event results can be altered either using the event object result property directly or returning the value in the
callback itself:
// A listener callback
public function doSomething($event) {
// ...
$alteredData = $event->data[order] + $moreData;
return $alteredData;
}
// Another listener callback
public function doSomethingElse($event) {
// ...
$event->result[order] = $alteredData;
}
// Using the event result
public function place($order) {
General Purpose
353
It is possible to alter any event object property and have the new data passed to the next callback. In most of
the cases, providing objects as event data or result and directly altering the object is the best solution as the
reference is kept the same and modifications are shared across all callback calls.
Removing Callbacks and Listeners If for any reason you want to remove any callback from the event
manager just call the CakeEventManager::detach() method using as arguments the first two params
you used for attaching it:
// Attaching a function
$this->getEventManager()->attach(array($this, doSomething), My.event);
// Detaching the function
$this->getEventManager()->detach(array($this, doSomething), My.event);
// Attaching an anonymous function (PHP 5.3+ only);
$myFunction = function($event) { ... };
$this->getEventManager()->attach($myFunction, My.event);
// Detaching the anonymous function
$this->getEventManager()->detach($myFunction, My.event);
// Attaching a CakeEventListener
$listener = new MyEventLister();
$this->getEventManager()->attach($listener);
// Detaching a single event key from a listener
$this->getEventManager()->detach($listener, My.event);
// Detaching all callbacks implemented by a listener
$this->getEventManager()->detach($listener);
Conclusion
Events are a great way of separating concerns in your application and make classes both cohesive and decoupled from each other. Events can be utilized to de-couple application code and make extensible plugins.
354
Keep in mind that with great power comes great responsibility. Using too many events can make debugging
harder and require additional integration testing.
Additional Reading
Collections Components, Helpers, Behaviors and Tasks all share a similar structure and set of behaviors.
For 2.0, they were given a unified API for interacting with collections of similar objects. The collection
objects in CakePHP, give you a uniform way to interact with several different kinds of objects in your
application.
While the examples below, will use Components, the same behavior can be expected for Helpers, Behaviors,
and Tasks in addition to Components.
Loading and unloading objects Loading objects on every kind of collection can be done using the
load() method:
$this->Prg = $this->Components->load(Prg);
$this->Prg->process();
When loading a component, if the component is not currently loaded into the collection, a new instance
will be created. If the component is already loaded, another instance will not be created. When loading
components, you can also provide additional configuration for them:
$this->Cookie = $this->Components->load(Cookie, array(name => sweet));
Any keys & values provided will be passed to the Components constructor. The one exception to this rule
is className. ClassName is a special key that is used to alias objects in a collection. This allows you
to have component names that do not reflect the classnames, which can be helpful when extending core
components:
$this->Auth = $this->Components->load(
Auth,
array(className => MyCustomAuth)
);
$this->Auth->user(); // Actually using MyCustomAuth::user();
The inverse of loading an object, is unloading it. Unloaded objects are removed from memory, and will not
have additional callbacks triggered on them:
$this->Components->unload(Cookie);
$this->Cookie->read(); // Fatal error.
Triggering callbacks Callbacks are supported by collection objects. When a collection has a callback
triggered, that method will be called on all enabled objects in the collection. You can pass parameters to the
callback loop as well:
$this->Behaviors->trigger(afterFind, array($this, $results, $primary));
In the above $this would be passed as the first argument to every behaviors afterFind method. There are
several options that can be used to control how callbacks are fired:
General Purpose
355
breakOn Set to the value or values you want the callback propagation to stop on. Can either be a
scalar value, or an array of values to break on. Defaults to false.
break Set to true to enabled breaking. When a trigger is broken, the last returned value will be returned. If used in combination with collectReturn the collected results will be returned. Defaults
to false.
collectReturn Set to true to collect the return of each object into an array. This array of return
values will be returned from the trigger() call. Defaults to false.
triggerDisabled Will trigger the callback on all objects in the collection even the non-enabled
objects. Defaults to false.
modParams Allows each object the callback gets called on to modify the parameters to the next
object. Setting modParams to an integer value will allow you to modify the parameter with that index.
Any non-null value will modify the parameter index indicated. Defaults to false.
Canceling a callback loop Using the break and breakOn options you can cancel a callback loop
midway similar to stopping event propagation in JavaScript:
$this->Behaviors->trigger(
beforeFind,
array($this, $query),
array(break => true, breakOn => false)
);
In the above example, if any behavior returns false from its beforeFind method, no further callbacks will
be called. In addition, the return of trigger() will be false.
Enabling and disabling objects Once an object is loaded into a collection you may need to disable it.
Disabling an object in a collection prevents future callbacks from being fired on that object unless the
triggerDisabled option is used:
// Disable the HtmlHelper
$this->Helpers->disable(Html);
// Re-enable the helper later on
$this->Helpers->enable(Html);
Disabled objects can still have their normal methods and properties used. The primary difference between an
enabled and disabled object is with regards to callbacks. You can interrogate a collection about the enabled
objects, or check if a specific object is still enabled using enabled():
// Check whether or not a specific helper is enabled.
$this->Helpers->enabled(Html);
// $enabled will contain an array of helper currently enabled.
$enabled = $this->Helpers->enabled();
356
Object callback priorities You can prioritize the triggering object callbacks similar to event callbacks.
The handling of priority values and order of triggering is the same as explained here. Heres how you can
specify priority at declaration time:
class SomeController {
public $components = array(
Foo, //Foo gets default priority 10
// Bars callbacks are triggered before Foos
Bar => array(priority => 9)
);
public $helpers = array(
// Caches callbacks will be triggered last
Cache => array(priority => 12),
Asset,
Utility //Utility has priority 10 same as Asset and its callbacks
//are triggered after Assets
);
}
class Post {
public $actsAs = array(
DoFirst => array(priority => 1),
Media
);
}
When dynamically loading objects to a collection you can specify the priority like this:
$this->MyComponent = $this->Components->load(
MyComponent,
array(priority => 9)
);
You can also change priorities at run time using the ObjectCollection::setPriority() function:
//For a single object
$this->Components->setPriority(Foo, 2);
//For multiple objects
$this->Behaviors->setPriority(array(Object1 => 8, Object2 => 9));
Behaviors Model behaviors are a way to organize some of the functionality defined in CakePHP models.
They allow us to separate and reuse logic that creates a type of behavior, and they do this without requiring
inheritance. For example creating tree structures. By providing a simple yet powerful way to enhance
models, behaviors allow us to attach functionality to models by defining a simple class variable. Thats how
behaviors allow models to get rid of all the extra weight that might not be part of the business contract they
are modeling, or that is also needed in different models and can then be extrapolated.
As an example, consider a model that gives us access to a database table which stores structural information
about a tree. Removing, adding, and migrating nodes in the tree is not as simple as deleting, inserting, and
General Purpose
357
editing rows in the table. Many records may need to be updated as things move around. Rather than creating
those tree-manipulation methods on a per model basis (for every model that needs that functionality), we
could simply tell our model to use the TreeBehavior, or in more formal terms, we tell our model to
behave as a Tree. This is known as attaching a behavior to a model. With just one line of code, our
CakePHP model takes on a whole new set of methods that allow it to interact with the underlying structure.
CakePHP already includes behaviors for tree structures, translated content, access control list interaction, not to mention the community-contributed behaviors already available in the CakePHP Bakery
(http://bakery.cakephp.org). In this section, well cover the basic usage pattern for adding behaviors to
models, how to use CakePHPs built-in behaviors, and how to create our own.
In essence, Behaviors are Mixins1 with callbacks.
There are a number of Behaviors included in CakePHP. To find out more about each one, reference the
chapters below:
ACL
class AclBehavior
The Acl behavior provides a way to seamlessly integrate a model with your ACL system. It can create both
AROs or ACOs transparently.
To use the new behavior, you can add it to the $actsAs property of your model. When adding it to the actsAs
array you choose to make the related Acl entry an ARO or an ACO. The default is to create ACOs:
class User extends AppModel {
public $actsAs = array(Acl => array(type => requester));
}
This would attach the Acl behavior in ARO mode. To join the ACL behavior in ACO mode use:
class Post extends AppModel {
public $actsAs = array(Acl => array(type => controlled));
}
For User and Group models it is common to have both ACO and ARO nodes, to achieve this use:
class User extends AppModel {
public $actsAs = array(Acl => array(type => both));
}
You can also attach the behavior on the fly like so:
$this->Post->Behaviors->load(Acl, array(type => controlled));
Changed in version 2.1: You can now safely attach AclBehavior to AppModel. Aco, Aro and AclNode now
extend Model instead of AppModel, which would cause an infinite loop. If your application depends on
having those models to extend AppModel for some reason, then copy AclNode to your application and have
it extend AppModel again.
1
http://en.wikipedia.org/wiki/Mixin
358
Using the AclBehavior Most of the AclBehavior works transparently on your Models afterSave(). However, using it requires that your Model has a parentNode() method defined. This is used by the AclBehavior
to determine parent->child relationships. A models parentNode() method must return null or return a parent
Model reference:
public function parentNode() {
return null;
}
If you want to set an ACO or ARO node as the parent for your Model, parentNode() must return the alias of
the ACO or ARO node:
public function parentNode() {
return root_node;
}
A more complete example. Using an example User Model, where User belongsTo Group:
public function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
$data = $this->data;
if (empty($this->data)) {
$data = $this->read();
}
if (!$data[User][group_id]) {
return null;
}
return array(Group => array(id => $data[User][group_id]));
}
In the above example the return is an array that looks similar to the results of a model find. It is important
to have the id value set or the parentNode relation will fail. The AclBehavior uses this data to construct its
tree structure.
node() The AclBehavior also allows you to retrieve the Acl node associated with a model record. After
setting $model->id. You can use $model->node() to retrieve the associated Acl node.
You can also retrieve the Acl Node for any row, by passing in a data array:
$this->User->id = 1;
$node = $this->User->node();
$user = array(User => array(
id => 1
));
$node = $this->User->node($user);
359
$this->User->id = 1;
$node = $this->User->node(null, Aro);
$user = array(User => array(
id => 1
));
$node = $this->User->node($user, Aro);
Containable
class ContainableBehavior
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. Since Containable only modifies existing
relationships it will not allow you to restrict results by distant associations. Instead you should refer to
Joining tables.
To use the new behavior, you can add it to the $actsAs property of your model:
class Post extends AppModel {
public $actsAs = array(Containable);
}
Using Containable To see how Containable works, lets look at a few examples. First, well start off with
a find() call on a model named Post. Lets say that Post hasMany Comment, and Post hasAndBelongsToMany Tag. The amount of data fetched in a normal find() call is rather extensive:
debug($this->Post->find(all));
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
360
[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:
$this->Post->contain();
$this->Post->find(all);
You can also invoke Containables magic from inside the find() call:
$this->Post->find(all, array(contain => false));
Having done that, you end up with something a lot more concise:
[0] => Array
(
General Purpose
361
This sort of help isnt new: in fact, you can do that without the ContainableBehavior doing something
like this:
$this->Post->recursive = -1;
$this->Post->find(all);
Containable really shines when you have complex associations, and you want to pare down things that sit
at the same level. The models $recursive property is helpful if you want to hack off an entire level of
recursion, but not when you want to pick and choose what to keep at each level. Lets see how it works by
using the contain() method.
The contain methods first argument accepts the name, or an array of names, of the models to keep in the
find operation. If we wanted to fetch all posts and their related tags (without any comment information),
wed try something like this:
$this->Post->contain(Tag);
$this->Post->find(all);
Without Containable, youd end up needing to use the unbindModel() method of the model, multiple
times if youre paring off multiple models. Containable creates a cleaner way to accomplish this same task.
Containing deeper associations Containable also goes a step deeper: you can filter the data of the associated models. If you look at the results of the original find() call, notice the author field in the Comment
model. If you are interested in the posts and the names of the comment authors and nothing else you
could do something like the following:
$this->Post->contain(Comment.author);
$this->Post->find(all);
362
// or..
$this->Post->find(all, array(contain => Comment.author));
Here, weve told Containable to give us our post information, and just the author field of the associated
Comment model. The output of the find call might look something like this:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[author] => Daniel
[post_id] => 1
)
[1] => Array
(
[author] => Sam
[post_id] => 1
)
)
)
[1] => Array
(...
As you can see, the Comment arrays only contain the author field (plus the post_id which is needed by
CakePHP to map the results).
You can also filter the associated Comment data by specifying a condition:
$this->Post->contain(Comment.author = "Daniel");
$this->Post->find(all);
//or...
$this->Post->find(all, array(contain => Comment.author = "Daniel"));
This gives us a result that gives us posts with comments authored by Daniel:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
General Purpose
363
There is an important caveat to using Containable when filtering on a deeper association. In the previous
example, assume you had 3 posts in your database and Daniel had commented on 2 of those posts. The
operation $this->Post->find(all, array(contain => Comment.author = Daniel)); would return ALL
3 posts, not just the 2 posts that Daniel had commented on. It wont return all comments however, just
comments by Daniel.
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[author] => Daniel
[email] => [email protected]
[website] => http://example.com
[comment] => First comment
[created] => 2008-05-18 00:00:00
)
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[title] => Second article
[content] => bbb
364
If you want to filter the posts by the comments, so that posts without a comment by Daniel wont be returned,
the easiest way is to find all the comments by Daniel and contain the Posts.
$this->Comment->find(all, array(
conditions => Comment.author = "Daniel",
contain => Post
));
Heres an example of using the ContainableBehavior when youve got deep and complex model
relationships.
Lets consider the following model associations:
User->Profile
User->Account->AccountSummary
General Purpose
365
User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes
User->Post->Tag
Keep in mind that contain key is only used once in the main model, you dont need to use contain again
for related models.
Note: When using 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.
ContainableBehavior options The ContainableBehavior has a number of options that can be set
when the Behavior is attached to a model. The settings allow you to 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.
order: (string, optional) the order of how the contained elements are sorted.
From the previous example, this is an example of how to force the posts to be ordered by the date when they
were last updated:
366
$this->User->find(all, array(
contain => array(
Profile,
Post => array(
order => Post.updated DESC
)
)
));
You can change ContainableBehavior settings at run time by reattaching the behavior as seen in Behaviors
(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 nonaggregate fields, try disabling the autoFields setting.
$this->Post->Behaviors->load(Containable, array(autoFields => false));
Using Containable with pagination By including the contain parameter in the $paginate property
it will apply to both the find(count) and the find(all) done on the model.
See the section Using Containable for further details.
Heres an example of how to contain associations when paginating:
$this->paginate[User] = array(
contain => array(Profile, Account),
order => User.username
);
$users = $this->paginate(User);
Note: If you contained the associations through the model instead, it will not honor Containables recursive
option. So if you set recursive to -1 for example for the model, it wont work:
$this->User->recursive = -1;
$this->User->contain(array(Profile, Account));
$users = $this->paginate(User);
Translate
class TranslateBehavior
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.
General Purpose
367
Initializing the i18n Database Tables You can either use the CakePHP console or you can manually
create it. It is advised to use the console for this, because it might happen that the layout changes in future
versions of CakePHP. Sticking to the console will make sure that you have the correct layout.
./cake i18n
Select [I] which will run the i18n database initialization script. You will be asked if you want to drop any
existing and if you want to create it. Answer with yes if you are sure there is no i18n table already, and
answer with yes again to create the table.
Attaching the Translate Behavior to your Models Add it to your model by using the $actsAs property
like in the following example.
class Post extends AppModel {
public $actsAs = array(
Translate
);
}
This will do nothing yet, because it expects a couple of options before it begins to work. You need to define
which fields of the current model should be tracked in the translation table weve created in the first step.
Defining the Fields You can set the fields by simply extending the Translate value with another
array, like so:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
fieldOne, fieldTwo, and_so_on
)
);
}
After you have done that (for example putting title as one of the fields) you already finished the basic
setup. Great! According to our current example the model should now look something like this:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title
)
);
}
When defining fields for TranslateBehavior to translate, be sure to omit those fields from the translated
models schema. If you leave the fields in, there can be issues when retrieving data with fallback locales.
Note: If all the fields in your model are translated be sure to add created and modified columns to
your table. CakePHP requires at least one non primary key field before it will save a record.
368
Conclusion From now on each record update/creation will cause TranslateBehavior to copy the value of
title to the translation table (default: i18n) along with the current locale. A locale is the identifier of the
language, so to speak.
Reading translated content By default the TranslateBehavior will automatically fetch
and add in data based on the current locale.
The current locale is read from
Configure::read(Config.language) which is assigned by the L10n class. You can
override this default on the fly using $Model->locale.
Retrieve translated fields in a specific locale By setting $Model->locale you can read translations
for a specific locale:
// Read the spanish locale data.
$this->Post->locale = es;
$results = $this->Post->find(first, array(
conditions => array(Post.id => $id)
));
// $results will contain the spanish translation.
Retrieve all translation records for a field If you want to have all translation records attached to the
current model record you simply extend the field array in your behavior setup as shown below. The naming
is completely up to you.
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title => titleTranslation
)
);
}
With this setup the result of $this->Post->find() should look something like this:
Array
(
[Post] => Array
(
[id] => 1
[title] => Beispiel Eintrag
[body] => lorem ipsum...
[locale] => de_de
)
[titleTranslation] => Array
(
[0] => Array
(
[id] => 1
[locale] => en_us
[model] => Post
General Purpose
369
[foreign_key] => 1
[field] => title
[content] => Example entry
)
[1] => Array
(
[id] => 2
[locale] => de_de
[model] => Post
[foreign_key] => 1
[field] => title
[content] => Beispiel Eintrag
)
)
)
Note: The model record contains a virtual 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 wont be translated because triggering callbacks on associated models is currently not supported.
Using the bindTranslation method You can also retrieve all translations, only when you need them, using
the bindTranslation method
TranslateBehavior::bindTranslation($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.
$this->Post->bindTranslation(array(title => titleTranslation));
// need at least recursive 1 for this to work.
$this->Post->find(all, array(recursive => 1));
With this setup the result of your find() should look something like this:
Array
(
[Post] => Array
(
[id] => 1
[title] => Beispiel Eintrag
[body] => lorem ipsum...
[locale] => de_de
)
[titleTranslation] => Array
(
[0] => Array
(
370
[id] => 1
[locale] => en_us
[model] => Post
[foreign_key] => 1
[field] => title
[content] => Example entry
)
[1] => Array
(
[id] => 2
[locale] => de_de
[model] => Post
[foreign_key] => 1
[field] => title
[content] => Beispiel Eintrag
)
)
)
Saving in another language You can force the model which is using the TranslateBehavior to save in a
language other than the one detected.
To tell a model in what language the content is going to be you simply change the value of the $locale
property on the model before you save the data to the database. You can do that either in your controller or
you can define it directly in the model.
Example A: In your controller:
class PostsController extends AppController {
public function add() {
if (!empty($this->request->data)) {
// we are going to save the german version
$this->Post->locale = de_de;
$this->Post->create();
if ($this->Post->save($this->request->data)) {
return $this->redirect(array(action => index));
}
}
}
}
General Purpose
371
Multiple Translation Tables If you expect a lot entries you probably wonder how to deal with a rapidly
growing database table. There are two properties introduced by TranslateBehavior that allow to specify
which Model to bind as the model containing the translations.
These are $translateModel and $translateTable.
Lets say we want to save our translations for all posts in the table post_i18ns instead of the default i18n
table. To do so you need to setup your model like this:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title
)
);
// Use a different model (and table)
public $translateModel = PostI18n;
}
Note: It is important that you to pluralize the table. It is now a usual model and can be treated as such and
thus comes with the conventions involved. The table schema itself must be identical with the one generated
by the CakePHP console script. To make sure it fits one could just initialize a empty i18n table using the
console and rename the table afterwards.
Create the TranslateModel For this to work you need to create the actual model file in your models
folder. Reason is that there is no property to set the displayField directly in the model using this behavior
yet.
Make sure that you change the $displayField to field.
class PostI18n extends AppModel {
public $displayField = field; // important
}
// filename: PostI18n.php
Thats all it takes. You can also add all other model stuff here like $useTable. But for better consistency we could do that in the model which actually uses this translation model. This is where the optional
$translateTable comes into play.
372
Changing the Table If you want to change the name of the table you simply define $translateTable in
your model, like so:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title
)
);
// Use a different model
public $translateModel = PostI18n;
// Use a different table for translateModel
public $translateTable = post_translations;
}
Basic Usage The tree behavior has a lot packed into it, but lets start with a simple example - create the
following database table and put some data in it:
2
http://www.sitepoint.com/hierarchical-data-database-2/
General Purpose
373
374
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
INSERT INTO
categories (id, name, parent_id,
VALUES
(12, Status, 10, 20, 21);
INSERT INTO
categories (id, name, parent_id,
VALUES
(13, Trips, 9, 23, 28);
INSERT INTO
categories (id, name, parent_id,
VALUES
(14, National, 13, 24, 25);
INSERT INTO
categories (id, name, parent_id,
VALUES
(15, International, 13, 26, 27);
lft, rght)
lft, rght)
lft, rght)
lft, rght)
For the purpose of checking that everything is setup correctly, we can create a test method and output the
contents of our category tree to see what it looks like. With a simple controller:
class CategoriesController extends AppController {
public function index() {
$data = $this->Category->generateTreeList(
null,
null,
null,
);
debug($data); die;
}
}
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
General Purpose
375
Adding data In the previous section, we used existing data and checked that it looked hierarchal via the
method generateTreeList. However, usually you would add your data in exactly the same way as you
would for any model. For example:
// pseudo controller code
$data[Category][parent_id] = 3;
$data[Category][name] = Skating;
$this->Category->save($data);
When using the tree behavior its not necessary to do any more than set the parent_id, and the tree behavior
will take care of the rest. If you dont set the parent_id, the tree behavior will add to the tree making your
new addition a new top level entry:
// pseudo controller code
$data = array();
$data[Category][name] = Other People\s Categories;
$this->Category->save($data);
Running the above two code snippets would alter your tree as follows:
My Categories - Fun
Sport - Surfing - Extreme knitting - Skating New
Friends - Gerald - Gwendolyn
Work - Reports
* Annual
* Status
* Trips - National - International
Other Peoples Categories New
Modifying data Modifying data is as transparent as adding new data. If you modify something, but do
not change the parent_id field - the structure of your data will remain unchanged. For example:
// pseudo controller code
$this->Category->id = 5; // id of Extreme knitting
$this->Category->save(array(name => Extreme fishing));
The above code did not affect the parent_id field - even if the parent_id is included in the data that is passed
to save if the value doesnt change, neither does the data structure. Therefore the tree of data would now
look like:
My Categories
Fun
Sport - Surfing - Extreme fishing Updated - Skating
Friends - Gerald - Gwendolyn
Work
376
General Purpose
377
378
Output:
array(
[1] =>
[2] =>
[3] =>
[4] =>
[16] =>
[6] =>
[7] =>
[8] =>
[9] =>
[13] =>
[14] =>
[15] =>
[17] =>
General Purpose
"My Categories",
"_Fun",
"__Sport",
"___Surfing",
"___Skating",
"__Friends",
"___Gerald",
"___Gwendolyn",
"_Work",
"__Trips",
"___National",
"___International",
"Other Peoples Categories",
379
[5] =>
"_Extreme fishing"
getParentNode()
This convenience function will, as the name suggests, return the parent node for any node, or false if
the node has no parent (its the root node). For example:
$parent = $this->Category->getParentNode(2); //<- id for fun
// $parent contains All categories
Advanced Usage
The tree behavior doesnt only work in the background, there are a number of specific methods defined in the behavior to cater for all your hierarchical data needs, and any unexpected
problems that might arise in the process.
TreeBehavior::moveDown()
380
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:
public function movedown($id = null, $delta = null) {
$this->Category->id = $id;
if (!$this->Category->exists()) {
throw new NotFoundException(__(Invalid category));
}
if ($delta > 0) {
$this->Category->moveDown($this->Category->id, abs($delta));
} else {
$this->Session->setFlash(
Please provide the number of positions the field should be .
moved down.
);
}
return $this->redirect(array(action => index));
}
For example, if youd like to move the Sport ( id of 3 ) category one position down, you would
request: /categories/movedown/3/1.
TreeBehavior::moveUp()
Used to move a single node up the tree. You need to provide the ID of the element to be moved
and a positive number of how many positions the node should be moved up. All child nodes
will also be moved.
Heres an example of a controller action (in a controller named Categories) that moves a node
up the tree:
public function moveup($id = null, $delta = null) {
$this->Category->id = $id;
if (!$this->Category->exists()) {
throw new NotFoundException(__(Invalid category));
}
if ($delta > 0) {
$this->Category->moveUp($this->Category->id, abs($delta));
} else {
$this->Session->setFlash(
Please provide a number of positions the category should .
be moved up.
);
}
return $this->redirect(array(action => index));
}
General Purpose
381
For example, if you would like to move the category Gwendolyn ( id of 8 ) up one position you
would request /categories/moveup/8/1. Now the order of Friends will be Gwendolyn, Gerald.
TreeBehavior::removeFromTree($id = null, $delete = false)
Using this method will either delete or move a node but retain its sub-tree, which will be reparented one level higher. It offers more control than delete, which for a model using the tree
behavior will remove the specified node and all of its children.
Taking the following tree as a starting point:
My Categories - Fun
Sport - Surfing - Extreme knitting - Skating
Running the following code with the id for Sport:
$this->Node->removeFromTree($id);
382
$model->reorder(array(
//id of record to use as top node for reordering, default: $Model->id
id => ,
//which field to use in reordering, default: $Model->displayField
field => ,
//direction to order, default: ASC
order => ,
//whether or not to verify the tree before reorder, default: true
verify =>
));
Note: If you have saved your data or made other operations on the model, you might want
to set $model->id = null before calling reorder. Otherwise only the current node and
its children will be reordered.
Data Integrity
Due to the nature of complex self referential data structures such as trees and linked lists, they
can occasionally become broken by a careless call. Take heart, for all is not lost! The Tree
Behavior contains several previously undocumented features designed to recover from such
situations.
TreeBehavior::recover($mode = parent, $missingParentAction = null)
The mode parameter is used to specify the source of info that is valid/correct. The opposite
source of data will be populated based upon that source of info. E.g. if the MPTT 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 missingParentAction parameter 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_ids 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
Example:
// Rebuild all the left and right fields based on the parent_id
$this->Category->recover();
// or
$this->Category->recover(parent);
General Purpose
383
// Rebuild all the parent_ids based on the lft and rght fields
$this->Category->recover(tree);
TreeBehavior::reorder($options = array())
Reorders the nodes (and child nodes) of the tree according to the 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:
id - only reorder nodes below this node.
field - field to use for sorting, default is the displayField for the model.
order - ASC for ascending, DESC for descending sort.
verify - whether or not to verify the tree prior to resorting.
$options is used to pass all extra parameters, and has the following possible keys by default,
all of which are optional:
array(
id => null,
field => $model->displayField,
order => ASC,
verify => true
)
TreeBehavior::verify()
Returns true if the tree is valid otherwise an array of errors, with fields for type, incorrect
index and message.
Each record in the output array is an array of the form (type, id, message)
type is either index or node
id is the id of the erroneous node.
message depends on the error
Example Use:
$this->Category->verify();
Example output:
Array
(
[0] => Array
(
[0] => node
[1] => 3
[2] => left and right values identical
)
[1] => Array
384
(
[0] => node
[1] => 2
[2] => The parent node 999 doesn t exist
)
[10] => Array
(
[0] =>
[1] =>
[2] =>
)
[99] => Array
(
[0] =>
[1] =>
[2] =>
)
index
123
missing
node
163
left greater than right
Using Behaviors Behaviors are attached to models through the $actsAs model class variable:
class Category extends AppModel {
public $actsAs = array(Tree);
}
This example shows how a Category model could be managed in a tree structure using the TreeBehavior.
Once a behavior has been specified, use the methods added by the behavior as if they always existed as part
of the original model:
// Set ID
$this->Category->id = 42;
// Use behavior method, children():
$kids = $this->Category->children();
Some behaviors may require or allow settings to be defined when the behavior is attached to the model.
Here, we tell our TreeBehavior the names of the left and right fields in the underlying database table:
class Category extends AppModel {
public $actsAs = array(Tree => array(
left => left_node,
right => right_node
));
}
We can also attach several behaviors to a model. Theres no reason why, for example, our Category model
should only behave as a tree, it may also need internationalization support:
class Category extends AppModel {
public $actsAs = array(
Tree => array(
left => left_node,
General Purpose
385
So far we have been adding behaviors to models using a model class variable. That means that our behaviors
will be attached to our models throughout the models lifetime. However, we may need to detach behaviors
from our models at runtime. Lets say that on our previous Category model, which is acting as a Tree and a
Translate model, we need for some reason to force it to stop acting as a Translate model:
// Detach a behavior from our model:
$this->Category->Behaviors->unload(Translate);
That will make our Category model stop behaving as a Translate model from thereon. We may need, instead,
to just disable the Translate behavior from acting upon our normal model operations: our 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:
// 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:
// If our behavior is not handling model callbacks
if (!$this->Category->Behaviors->enabled(Translate)) {
// Tell it to start doing so
$this->Category->Behaviors->enable(Translate);
}
Just as we could completely detach a behavior from a model at runtime, we can also attach new behaviors.
Say that our familiar Category model needs to start behaving as a Christmas model, but only on Christmas
day:
// If today is Dec 25
if (date(m/d) === 12/25) {
// Our model needs to behave as a Christmas model
$this->Category->Behaviors->load(Christmas);
}
Theres also a method to obtain the list of behaviors a model has attached. If we pass the name of a behavior
to the method, it will tell us if that behavior is attached to the model, otherwise it will give us the list of
attached behaviors:
// If the Translate behavior is not attached
if (!$this->Category->Behaviors->loaded(Translate)) {
386
Creating Behaviors Behaviors that are attached to Models get their callbacks called automatically.
The callbacks are similar to those found in Models: beforeFind, afterFind, beforeValidate,
afterValidate, beforeSave, afterSave, beforeDelete, afterDelete and onError see Callback Methods.
Your behaviors should be placed in app/Model/Behavior. They are named in CamelCase and postfixed
by Behavior, ex. NameBehavior.php. Its often helpful to use a core behavior as a template when creating
your own. Find them in lib/Cake/Model/Behavior/.
Every callback and behavior method takes a reference to the model it is being called from as the 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:
class Post extends AppModel {
public $actsAs = array(
YourBehavior => array(
option1_key => option1_value
)
);
}
Since behaviors are shared across all the model instances that use them, its a good practice to store the
settings per alias/model name that is using the behavior. When created behaviors will have their setup()
method called:
public function setup(Model $Model, $settings = array()) {
if (!isset($this->settings[$Model->alias])) {
$this->settings[$Model->alias] = array(
option1_key => option1_default_value,
option2_key => option2_default_value,
option3_key => option3_default_value,
);
}
$this->settings[$Model->alias] = array_merge(
$this->settings[$Model->alias], (array)$settings);
}
Creating behavior methods Behavior methods are automatically available on any model acting as the
behavior. For example if you had:
General Purpose
387
You would be able to call FlyingBehavior methods as if they were methods on your Duck model.
When creating behavior methods you automatically get passed a reference of the calling model as the first
parameter. All other supplied parameters are shifted one place to the right. For example:
$this->Duck->fly(toronto, montreal);
Although this method takes two parameters, the method signature should look like:
public function fly(Model $Model, $from, $to) {
// Do some flying.
}
Keep in mind that methods called in a $this->doIt() fashion from inside a behavior method will not
get the $model parameter automatically appended.
Mapped methods In addition to providing mixin methods, behaviors can also provide pattern matching
methods. Behaviors can also define mapped methods. Mapped methods use pattern matching for method
invocation. This allows you to create methods similar to Model::findAllByXXX methods on your
behaviors. Mapped methods need to be declared in your behaviors $mapMethods array. The method
signature for a mapped method is slightly different than a normal behavior mixin method:
class MyBehavior extends ModelBehavior {
public $mapMethods = array(/do(\w+)/ => doSomething);
public function doSomething(Model $model, $method, $arg1, $arg2) {
debug(func_get_args());
//do something
}
}
The above will map every doXXX() method call to the behavior. As you can see, the model is still the first
parameter, but the called method name will be the 2nd parameter. This allows you to munge the method
name for additional information, much like Model::findAllByXX. If the above behavior was attached
to a model the following would happen:
$model->doReleaseTheHounds(homer, lenny);
// would output
ReleaseTheHounds, homer, lenny
Behavior callbacks Model Behaviors can define a number of callbacks that are triggered before the model
callbacks of the same name. Behavior callbacks allow your behaviors to capture events in attached models
and augment the parameters or splice in additional behavior.
All behavior callbacks are fired before the model callbacks are:
beforeFind
388
afterFind
beforeValidate
afterValidate
beforeSave
afterSave
beforeDelete
afterDelete
Creating a behavior callback
class ModelBehavior
Model behavior callbacks are defined as simple methods in your behavior class. Much like regular behavior
methods, they receive a $Model parameter as the first argument. This parameter is the model that the
behavior method was invoked on.
ModelBehavior::setup(Model $Model, array $settings = array())
Called when a behavior is attached to a model. The settings come from the attached models
$actsAs property.
ModelBehavior::cleanup(Model $Model)
Called when a behavior is detached from a model. The base method removes model settings based on
$model->alias. You can override this method and provide custom cleanup functionality.
ModelBehavior::beforeFind(Model $Model, array $query)
If a behaviors beforeFind returns false it will abort the find(). Returning an array will augment the
query parameters used for the find operation.
ModelBehavior::afterFind(Model $Model, mixed $results, boolean $primary = false)
You can use the afterFind to augment the results of a find. The return value will be passed on as the
results to either the next behavior in the chain or the models afterFind.
ModelBehavior::beforeValidate(Model $Model, array $options = array())
You can use beforeValidate to modify a models validate array or handle any other pre-validation
logic. Returning false from a beforeValidate callback will abort the validation and cause it to fail.
ModelBehavior::afterValidate(Model $Model)
You can use afterValidate to perform any data cleanup or preparation if needed.
ModelBehavior::beforeSave(Model $Model, array $options = array())
You can return false from a behaviors beforeSave to abort the save. Return true to allow it continue.
ModelBehavior::afterSave(Model $Model, boolean $created, array $options = array())
You can use afterSave to perform clean up operations related to your behavior. $created will be true
when a record is created, and false when a record is updated.
ModelBehavior::beforeDelete(Model $Model, boolean $cascade = true)
You can return false from a behaviors beforeDelete to abort the delete. Return true to allow it continue.
General Purpose
389
ModelBehavior::afterDelete(Model $Model)
You can use afterDelete to perform clean up operations related to your behavior.
Components Components are packages of logic that are shared between controllers. CakePHP comes
with a fantastic set of core components you can use to aid in various common tasks. You can also create
your own components. If you find yourself wanting to copy and paste things between controllers, you should
consider creating your own component to contain the functionality. Creating components keeps controller
code clean and allows you to reuse code between projects.
Each of the core components is detailed in its own chapter. See Components. This section describes how to
configure and use components, and how to create your own components.
Configuring Components Many of the core components require configuration. Some examples of
components requiring configuration are Authentication and Cookie. Configuration for these components, and for components in general, is usually done in the $components array or your controllers
beforeFilter() method:
class PostsController extends AppController {
public $components = array(
Auth => array(
authorize => array(controller),
loginAction => array(
controller => users,
action => login
)
),
Cookie => array(name => CookieMonster)
);
The previous fragment of code would be an example of configuring a component with the $components
array. All core components allow their configuration settings to be set in this way. In addition, you can
configure components in your controllers beforeFilter() method. This is useful when you need to
assign the results of a function to a component property. The above could also be expressed as:
public function beforeFilter() {
$this->Auth->authorize = array(controller);
$this->Auth->loginAction = array(
controller => users,
action => login
);
$this->Cookie->name = CookieMonster;
}
Its possible, however, that a component requires certain configuration options to be set before the controllers beforeFilter() is run. To this end, some components allow configuration options be set in the
$components array:
public $components = array(
DebugKit.Toolbar => array(panels => array(history, session))
);
390
Consult the relevant documentation to determine what configuration options each component provides.
One common setting to use is the className option, which allows you to alias components. This feature
is useful when you want to replace $this->Auth or another common Component reference with a custom
implementation:
// app/Controller/PostsController.php
class PostsController extends AppController {
public $components = array(
Auth => array(
className => MyAuth
)
);
}
// app/Controller/Component/MyAuthComponent.php
App::uses(AuthComponent, Controller/Component);
class MyAuthComponent extends AuthComponent {
// Add your code to override the core AuthComponent
}
Using Components Once youve included some components in your controller, using them is pretty
simple. Each component you use is exposed as a property on your controller. If you had loaded up the
SessionComponent and the CookieComponent in your controller, you could access them like so:
class PostsController extends AppController {
public $components = array(Session, Cookie);
public function delete() {
if ($this->Post->delete($this->request->data(Post.id)) {
$this->Session->setFlash(Post deleted.);
return $this->redirect(array(action => index));
}
}
Note: Since both Models and Components are added to Controllers as properties they share the same
namespace. Be sure to not give a component and a model the same name.
Loading components on the fly You might not need all of your components available on every controller
action. In situations like this you can load a component at runtime using the Component Collection. From
inside a controllers method you can do the following:
$this->OneTimer = $this->Components->load(OneTimer);
$this->OneTimer->getTime();
General Purpose
391
Note: Keep in mind that loading a component on the fly will not call its initialize method. If the component
you are calling has this method you will need to call it manually after load.
Component Callbacks Components also offer a few request life-cycle callbacks that allow them to augment the request cycle. See the base Component API for more information on the callbacks components
offer.
Creating a Component Suppose our online application needs to perform a complex mathematical operation in many different parts of the application. We could create a component to house this shared logic for
use in many different controllers.
The first step is to create a new component file and class.
Create the file in
app/Controller/Component/MathComponent.php. The basic structure for the component
would look something like this:
App::uses(Component, Controller);
class MathComponent extends Component {
public function doComplexOperation($amount1, $amount2) {
return $amount1 + $amount2;
}
}
Note: All components must extend Component. Failing to do this will trigger an exception.
Including your component in your controllers Once our component is finished, we can use it in the applications controllers by placing the components name (without the Component part) in the controllers
$components array. The controller will automatically be given a new attribute named after the component, through which we can access an instance of it:
/* Make the new component available at $this->Math,
as well as the standard $this->Session */
public $components = array(Math, Session);
Components declared in AppController will be merged with those in your other controllers. So there is
no need to re-declare the same component twice.
When including Components in a Controller you can also declare a set of parameters that will be passed on
to the Components constructor. These parameters can then be handled by the Component:
public $components = array(
Math => array(
precision => 2,
randomGenerator => srand
),
Session, Auth
);
392
The above would pass the array containing precision and randomGenerator to
MathComponent::__construct() as the second parameter.
By convention, if array keys
match components public properties, the properties will be set to the values of these keys.
Using other Components in your Component Sometimes one of your components may need to use
another component. In this case you can include other components in your component the exact same way
you include them in controllers - using the $components var:
// app/Controller/Component/CustomComponent.php
App::uses(Component, Controller);
class CustomComponent extends Component {
// the other component your component uses
public $components = array(Existing);
public function initialize(Controller $controller) {
$this->Existing->foo();
}
public function bar() {
// ...
}
}
// app/Controller/Component/ExistingComponent.php
App::uses(Component, Controller);
class ExistingComponent extends Component {
public function foo() {
// ...
}
}
Component API
class Component
The base Component class offers a few methods for lazily loading other Components through
ComponentCollection as well as dealing with common handling of settings. It also provides
prototypes for all the component callbacks.
Component::__construct(ComponentCollection $collection, $settings = array())
Constructor for the base component class. All $settings that are also public properties will have
their values changed to the matching value in $settings.
Callbacks
Component::initialize(Controller $controller)
Is called before the controllers beforeFilter method.
General Purpose
393
Component::startup(Controller $controller)
Is called after the controllers beforeFilter method but before the controller executes the current action
handler.
Component::beforeRender(Controller $controller)
Is called after the controller executes the requested actions logic, but before the controllers renders
views and layout.
Component::shutdown(Controller $controller)
Is called before output is sent to the browser.
Component::beforeRedirect(Controller $controller, $url, $status=null, $exit=true)
Is invoked when the controllers redirect method is called but before any further action. If this method
returns false the controller will not continue on to redirect the request. The $url, $status and $exit
variables have same meaning as for the controllers method. You can also return a string which will
be interpreted as the URL to redirect to or return an associative array with the key url and optionally
status and exit.
Helpers Helpers are the component-like classes for the presentation layer of your application. They contain presentational logic that is shared between many views, elements, or layouts. This chapter will show
you how to create your own helpers, and outline the basic tasks CakePHPs core helpers can help you
accomplish.
CakePHP features a number of helpers that aid in view creation. They assist in creating well-formed markup
(including forms), aid in formatting text, times and numbers, and can even speed up AJAX functionality.
For more information on the helpers included in CakePHP, check out the chapter for each helper:
CacheHelper
class CacheHelper(View $view, array $settings = array())
The Cache helper assists in caching entire layouts and views, saving time repetitively retrieving data. View
Caching in CakePHP temporarily stores parsed layouts and views as simple PHP + HTML files. 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. The CacheHelper then uses helper callbacks to process the file and output to generate the cache file.
When a URL is requested, CakePHP 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 CakePHP doesnt find a cached view, or the cache has expired for the requested URL it
continues to process the request normally.
Using the Helper There are two steps you have to take before you can use the CacheHelper. First in
your APP/Config/core.php uncomment the Configure write call for Cache.check. This will tell
CakePHP to check for, and generate view cache files when handling requests.
Once youve uncommented the Cache.check line you will need to add the helper to your controllers
$helpers array:
394
You will also need to add the CacheDispatcher to your dispatcher filters in your bootstrap:
Configure::write(Dispatcher.filters, array(
CacheDispatcher
));
New in version 2.3: If you have a setup with multiple domains or languages you can use Configure::write(Cache.viewPrefix, YOURPREFIX); to store the view cache files prefixed.
Additional configuration options CacheHelper has a few additional configuration options you can use
to tune and tweak its behavior. This is done through the $cacheAction variable in your controllers.
$cacheAction should be set to an array which contains the actions you want cached, and the duration in
seconds you want those views cached. The time value can be expressed in a strtotime() format (e.g.
1 hour, or 3 minutes).
Using the example of an ArticlesController, that receives a lot of traffic that needs to be cached:
public $cacheAction = array(
view => 36000,
index => 48000
);
This will cache the view action 10 hours, and the index action 13 hours. By making $cacheAction a
strtotime() friendly value you can cache every action in the controller:
public $cacheAction = "1 hour";
You can also enable controller/component callbacks for cached views created with CacheHelper. To do
so you must use the array format for $cacheAction and create an array like the following:
public $cacheAction = array(
view => array(callbacks => true, duration => 21600),
add => array(callbacks => true, duration => 36000),
index => array(callbacks => true, duration => 48000)
);
By setting callbacks => true you tell CacheHelper that you want the generated files to create the
components and models for the controller. Additionally, fire the component initialize, controller beforeFilter,
and component startup callbacks.
Note: Setting callbacks => true partly defeats the purpose of caching. This is also the reason it is
disabled by default.
Marking Non-Cached Content in Views There will be times when you dont want an entire view cached.
For example, certain parts of the page may look different whether a user is currently logged in or browsing
your site as a guest.
General Purpose
395
To indicate blocks of content that are not to be cached, wrap them in <!--nocache-->
<!--/nocache--> like so:
<!--nocache-->
<?php if ($this->Session->check(User.name)): ?>
Welcome, <?php echo h($this->Session->read(User.name)); ?>.
<?php else: ?>
<?php echo $this->Html->link(Login, users/login); ?>
<?php endif; ?>
<!--/nocache-->
Note: You cannot use nocache tags in elements. Since there are no callbacks around elements, they
cannot be cached.
It should be noted that once an action is cached, the controller method for the action will not be called. When
a cache file is created, the request object, and view variables are serialized with PHPs serialize().
Warning: If you have view variables that contain un-serializable content such as SimpleXML objects,
resource handles, or closures you might not be able to use view caching.
Clearing the Cache It is important to remember that CakePHP will clear a cached view if a model used
in the cached view is modified. For example, if a cached view uses data from the Post model, and there has
been an INSERT, UPDATE, or DELETE query made to a Post, the cache for that view is cleared, and new
content is generated on the next request.
Note: This automatic cache clearing requires the controller/model name to be part of the URL. If youve
used routing to change your URLs this feature will not work.
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().
FormHelper
class FormHelper(View $view, array $settings = array())
The FormHelper does most of the heavy lifting in form creation. 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 using conventions, or you can use specific methods to get only what
you need.
Creating Forms The first method youll need to use in order to take advantage of the FormHelper is
create(). This special method outputs an opening form tag.
FormHelper::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 the current URL. The default method for
form submission is POST. The form element is also 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
396
create() inside a UsersController view, Id see something like the following output in the rendered
view:
<form id="UserAddForm" method="post" action="/users/add">
Note:
You can also pass false for $model.
This will place your form
data into the array:
$this->request->data (instead of in the sub-array:
$this->request->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 forms 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:
// If you are on /recipes/add
echo $this->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->request->data property to automatically detect whether to create an add or edit form. If $this->request->data
contains an array element named after the forms model, and that array contains a non-empty value of
the models primary key, then the FormHelper will create an edit form for that record. For example,
if we browse to http://site.com/recipes/edit/5, we would get the following:
// Controller/RecipesController.php:
public function edit($id = null) {
if (empty($this->request->data)) {
$this->request->data = $this->Recipe->findById($id);
} else {
// Save logic goes here
}
}
// View/Recipes/edit.ctp:
// Since $this->request->data[Recipe][id] = 5,
// we will get an edit form
<?php echo $this->Form->create(Recipe); ?>
Output:
<form id="RecipeEditForm" method="post" action="/recipes/edit/5">
<input type="hidden" name="_method" value="PUT" />
Note: Since this is an edit form, a hidden input field is generated to override the default HTTP
method.
General Purpose
397
When creating forms for models in plugins, you should always use plugin syntax when creating a
form. This will ensure the form is correctly generated:
echo $this->Form->create(ContactManager.Contact);
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.
Changed in version 2.0: The default URL for all forms, is now the current URL including passed,
named, and querystring parameters. You can override this default by supplying $options[url]
in the second parameter of $this->Form->create().
Options for create() There are a number of options for create():
$options[type] 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:
echo $this->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:
echo $this->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.
$options[action] The action key allows you to point the form to a specific action in your
current controller. For example, if youd like to point the form to the login() action of the current
controller, you would supply an $options array like the following:
echo $this->Form->create(User, array(action => login));
Output:
<form id="UserLoginForm" method="post" action="/users/login">
$options[url] If the desired form action isnt 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:
398
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.
$options[default] If default has been set to boolean false, the forms 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 forms default behavior so you can grab
the data and submit it via AJAX instead.
$options[inputDefaults] You can declare a set of default options for input() with the
inputDefaults key to customize your default input creation:
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:
echo $this->Form->input(password); // No div, no label
// has a label element
echo $this->Form->input(
username,
array(label => Username)
);
General Purpose
399
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:
<?php echo $this->Form->end(Finish); ?>
Will output:
<div class="submit">
<input type="submit" value="Finish" />
</div>
</form>
Will output:
<div class="glass-pill"><input type="submit" value="Update" name="Update">
</div>
http://api.cakephp.org/2.4/class-FormHelper.html
400
Input element(s)
Error element with message if applicable.
The type of input created depends on the column datatype:
Column Type Resulting Form Field
string (char, varchar, etc.) text
boolean, tinyint(1) checkbox
text textarea
text, with name of password, passwd, or psword password
text, with name of email email
text, with name of tel, telephone, or phone tel
date day, month, and year selects
datetime, timestamp day, month, year, hour, minute, and meridian selects
time hour, minute, and meridian selects
binary file
The $options parameter allows you to customize how input() works, and finely control what is
generated.
The wrapping div will have a required class name appended if the validation rules for the Models
field do not specify allowEmpty => true. One limitation of this behavior is the fields model
must have been loaded during this request. Or be directly associated to the model supplied to
create().
New in version 2.5: The binary type now maps to a file input.
New in version 2.3. Since 2.3 the HTML5 required attribute will also be added to the input based
on validation rules. You can explicitly set required key in options array to override it for a field. To
skip browser validation triggering for the whole form you can set option formnovalidate =>
true for the input button you generate using FormHelper::submit() or set novalidate
=> true in options for FormHelper::create().
For example, lets assume that your User model includes fields for a username (varchar), password
(varchar), approved (datetime) and quote (text). You can use the input() method of the FormHelper to
create appropriate inputs for all of these form fields:
echo $this->Form->create();
echo $this->Form->input(username);
echo $this->Form->input(password);
echo $this->Form->input(approved);
echo $this->Form->input(quote);
//text
//password
//day, month, year, hour, minute,
//meridian
//textarea
echo $this->Form->end(Add);
General Purpose
401
Besides the specific options for input() found below, you can specify any option for the input type & any HTML attribute (for instance onfocus). For more information on $options and
$htmlAttributes see HtmlHelper.
Assuming that User hasAndBelongsToMany Group. In your controller, set a camelCase plural variable (group -> groups in this case, or ExtraFunkyModel -> extraFunkyModels) with the select options.
In the controller action you would put the following:
$this->set(groups, $this->User->Group->find(list));
And in the view a multiple select can be created with this simple code:
echo $this->Form->input(Group);
If you want to create a select field while using a belongsTo - or hasOne - Relation, you can add the
following to your Users-controller (assuming your User belongsTo Group):
$this->set(groups, $this->User->Group->find(list));
If your model name consists of two or more words, e.g., UserGroup, when passing the data using
set() you should name your data in a pluralised and camelCased format as follows:
$this->set(userGroups, $this->UserGroup->find(list));
// or
$this->set(
reallyInappropriateModelNames,
$this->ReallyInappropriateModelName->find(list)
);
Note:
Try to avoid using FormHelper::input() to generate submit buttons.
FormHelper::submit() instead.
Use
echo $this->Form->inputs(array(
name => array(label => custom label)
));
In addition to fields control, inputs() allows you to use a few additional options.
fieldset Set to false to disable the fieldset. If a string is supplied it will be used as the class
name for the fieldset element.
legend Set to false to disable the legend for the generated input set. Or supply a string to
customize the legend text.
Field naming conventions The Form helper is pretty smart. Whenever you specify a field name with the
form helper methods, itll automatically use the current model name to build an input with a format like the
following:
<input type="text" id="ModelnameFieldname" name="data[Modelname][fieldname]">
This allows you to omit the model name when generating inputs for the model that the form was created for.
You can create inputs for associated models, or arbitrary models by passing in Modelname.fieldname as the
first parameter:
echo $this->Form->input(Modelname.fieldname);
If you need to specify multiple fields using the same field name, thus creating an array that can be saved in
one shot with saveAll(), use the following convention:
echo $this->Form->input(Modelname.0.fieldname);
echo $this->Form->input(Modelname.1.fieldname);
Output:
<input type="text" id="Modelname0Fieldname"
name="data[Modelname][0][fieldname]">
<input type="text" id="Modelname1Fieldname"
name="data[Modelname][1][fieldname]">
FormHelper uses several field-suffixes internally for datetime input creation. If you are using fields named
year, month, day, hour, minute, or meridian and having issues getting the correct input, you can
set the name attribute to override the default behavior:
echo $this->Form->input(Model.year, array(
type => text,
name => data[Model][year]
));
Options FormHelper::input() supports a large number of options. In addition to its own options
input() accepts options for the generated input types, as well as HTML attributes. The following will
cover the options specific to FormHelper::input().
General Purpose
403
$options[type] You can force the type of an input, overriding model introspection, by specifying a type. In addition to the field types found in the Creating form elements, you can also create
file, password, and any type supported by HTML5:
echo $this->Form->input(field, array(type => file));
echo $this->Form->input(email, array(type => email));
Output:
<div class="input file">
<label for="UserField">Field</label>
<input type="file" name="data[User][field]" value="" id="UserField" />
</div>
<div class="input email">
<label for="UserEmail">Email</label>
<input type="email" name="data[User][email]" value="" id="UserEmail" />
</div>
$options[div] Use this option to set attributes of the inputs containing div. Using a string
value will set the divs class name. An array will set the divs attributes to those specified by the
arrays keys/values. Alternatively, you can set this key to false to disable the output of the div.
Setting the class name:
echo $this->Form->input(User.name, array(
div => class_name
));
Output:
<div class="class_name">
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
Output:
<div class="input text" id="mainDiv" title="Div Title"
style="display:block">
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
404
Output:
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
$options[label] Set this key to the string you would like to be displayed within the label that
usually accompanies the input:
echo $this->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:
echo $this->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:
echo $this->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>
$options[error] 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.
To disable error message output & field classes set the error key to false:
General Purpose
405
To disable only the error message, but retain the field classes, set the errorMessage key to false:
$this->Form->input(Model.field, array(errorMessage => false));
To modify the wrapping element type and its class, use the following format:
$this->Form->input(Model.field, array(
error => array(
attributes => array(wrap => span, class => bzzz)
)
));
To prevent HTML being automatically escaped in the error message output, set the escape suboption
to false:
$this->Form->input(Model.field, array(
error => array(
attributes => array(escape => false)
)
));
To override the model error messages use an array with the keys matching the validation rule names:
$this->Form->input(Model.field, array(
error => array(tooShort => __(This is not long enough))
));
As seen above you can set the error message for each validation rule you have in your models. In
addition you can provide i18n messages for your forms.
New in version 2.3: Support for the errorMessage option was added in 2.3
$options[before],
$options[after]
$options[between],
$options[separator],
and
Use these keys if you need to inject some markup inside the output of the input() method:
echo $this->Form->input(field, array(
before => --before--,
after => --after--,
between => --between---
));
Output:
<div class="input">
--before-<label for="UserField">Field</label>
--between--<input name="data[User][field]" type="text" value="" id="UserField" />
--after-</div>
406
For radio inputs the separator attribute can be used to inject markup to separate each input/label pair:
echo $this->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 -.
$options[format] The ordering of the HTML generated by FormHelper is controllable as
well. The format options supports an array of strings describing the template you would like said
element to follow. The supported array keys are: array(before, input, between,
label, after,error).
$options[inputDefaults] If you find yourself repeating the same options in multiple input() calls, you can use inputDefaults to keep your code dry:
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:
// No div, no label
echo $this->Form->input(password);
// has a label element
echo $this->Form->input(username, array(label => Username));
If you need to later change the defaults you can use FormHelper::inputDefaults().
Generating specific types of inputs In addition to the generic input() method, FormHelper has
specific methods for generating a number of different types of inputs. These can be used to generate just
General Purpose
407
the input widget itself, and combined with other methods like label() and error() to generate fully
custom form layouts.
Common options Many of the various input element methods support a common set of options. All of
these options are also supported by input(). To reduce repetition the common options shared by all input
methods are as follows:
$options[class] You can set the class name for an input:
echo $this->Form->input(title, array(class => custom-class));
$options[id] Set this key to force the value of the DOM id for the input.
$options[default] 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:
echo $this->Form->input(ingredient, array(default => Sugar));
Note:
You cannot use default to check a checkbox - instead you might set the value in
$this->request->data in your controller, or set the input option checked to true.
Date and datetime fields default values can be set by using the selected key.
Beware of using false to assign a default value. A false value is used to disable/exclude options of
an input field, so default => false would not set any value at all. Instead use default
=> 0.
In addition to the above options, you can mixin any HTML attribute you wish to use. Any non-special
option name will be treated as an HTML attribute, and applied to the generated HTML input element.
Options for select, checkbox and radio inputs
$options[selected] 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:
echo $this->Form->input(close_time, array(
type => time,
selected => 13:30:00
));
408
Note: The selected key for date and datetime inputs may also be a UNIX timestamp.
$options[empty] 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:
echo $this->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>
Note: If you need to set the default value in a password field to blank, use value => instead.
Options can also supplied as key-value pairs.
$options[hiddenField] For certain input types (checkboxes, radios) a hidden input is created so that the key in $this->request->data will exist even without a value specified:
<input type="hidden" name="data[Post][Published]" id="PostPublished_"
value="0" />
<input type="checkbox" name="data[Post][Published]" value="1"
id="PostPublished" />
Which outputs:
<input type="checkbox" name="data[Post][Published]" value="1"
id="PostPublished" />
If you want to create multiple blocks of inputs on a form that are all grouped together, you should use
this parameter on all inputs except the first. If the hidden input is on the page in multiple places, only
the last group of inputs values will be saved
In this example, only the tertiary colors would be passed, and the primary colors would be overridden:
General Purpose
409
<h2>Primary Colors</h2>
<input type="hidden" name="data[Color][Color]" id="Colors_" value="0" />
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsRed" />
<label for="ColorsRed">Red</label>
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsBlue" />
<label for="ColorsBlue">Blue</label>
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsYellow" />
<label for="ColorsYellow">Yellow</label>
<h2>Tertiary Colors</h2>
<input type="hidden" name="data[Color][Color]" id="Colors_" value="0" />
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsGreen" />
<label for="ColorsGreen">Green</label>
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsPurple" />
<label for="ColorsPurple">Purple</label>
<input type="checkbox" name="data[Addon][Addon][]" value="5"
id="ColorsOrange" />
<label for="ColorsOrange">Orange</label>
Disabling the hiddenField on the second input group would prevent this behavior.
You can set a different hidden field value other than 0 such as N:
echo $this->Form->checkbox(published, array(
value => Y,
hiddenField => N,
));
Datetime options
$options[timeFormat] Used to specify the format of the select inputs for a time-related set
of inputs. Valid values include 12, 24, and null.
$options[dateFormat] Used to specify the format of the select inputs for a date-related set
of inputs. Valid values include any combination of D, M and Y or null. The inputs will be put
in the order defined by the dateFormat option.
$options[minYear], $options[maxYear] Used in combination with a
date/datetime input. Defines the lower and/or upper end of values shown in the years select
field.
$options[orderYear] Used in combination with a date/datetime input. Defines the order in
which the year values will be set. Valid values include asc, desc. The default value is desc.
$options[interval] This option specifies the number of minutes between each option in the
minutes select box:
410
Would create 4 options in the minute select. One for each 15 minutes.
$options[round] Can be set to up or down to force rounding in either direction. Defaults to
null which rounds half up according to interval.
New in version 2.4.
Form Element-Specific Methods All elements are created under a form for the User model as in the
examples above. For this reason, the HTML code generated will contain attributes that reference to the User
model. Ex: name=data[User][username], id=UserUsername
FormHelper::label(string $fieldName, string $text, array $options)
Create a label element. $fieldName is used for generating the DOM id. If $text is undefined,
$fieldName will be used to inflect the labels text:
echo $this->Form->label(User.name);
echo $this->Form->label(User.name, Your username);
Output:
<label for="UserName">Name</label>
<label for="UserName">Your username</label>
$options can either be an array of HTML attributes, or a string that will be used as a class name:
echo $this->Form->label(User.name, null, array(id => user-label));
echo $this->Form->label(User.name, Your username, highlight);
Output:
<label for="UserName" id="user-label">Name</label>
<label for="UserName" class="highlight">Your username</label>
Will output:
<input name="data[User][username]" type="text" class="users"
id="UserUsername" />
General Purpose
411
echo $this->Form->password(password);
Will output:
<input name="data[User][password]" value="" id="UserPassword"
type="password" />
Will output:
<input name="data[User][id]" id="UserId" type="hidden" />
If the form is edited (that is, the array $this->request->data will contain the information
saved for the User model), the value corresponding to id field will automatically be added to the
HTML generated. Example for data[User][id] = 10:
<input name="data[User][id]" id="UserId" type="hidden" />
Changed in version 2.0: Hidden fields no longer remove the class attribute. This means that if there
are validation errors on hidden fields, the error-field class name will be applied.
FormHelper::textarea(string $fieldName, array $options)
Creates a textarea input field.
echo $this->Form->textarea(notes);
Will output:
<textarea name="data[User][notes]" id="UserNotes"></textarea>
If the form is edited (that is, the array $this->request->data will contain the information
saved for the User model), the value corresponding to notes field will automatically be added to
the HTML generated. Example:
<textarea name="data[User][notes]" id="UserNotes">
This text is to be edited.
</textarea>
Note: The textarea input type allows for the $options attribute of escape which determines whether or not the contents of the textarea should be escaped. Defaults to true.
echo $this->Form->textarea(notes, array(escape => false);
// OR....
echo $this->Form->input(
notes,
array(type => textarea, escape => false)
);
Options
412
Output:
<textarea name="data[Form][textarea]" cols="5" rows="5" id="FormTextarea">
</textarea>
Will output:
<input type="hidden" name="data[User][done]" value="0" id="UserDone_" />
<input type="checkbox" name="data[User][done]" value="1" id="UserDone" />
It is possible to specify the value of the checkbox by using the $options array:
echo $this->Form->checkbox(done, array(value => 555));
Will output:
<input type="hidden" name="data[User][done]" value="0" id="UserDone_" />
<input type="checkbox" name="data[User][done]" value="555" id="UserDone" />
Will output:
<input type="checkbox" name="data[User][done]" value="1" id="UserDone" />
413
$attributes[legend] Radio elements are wrapped with a legend and fieldset by default. Set $attributes[legend] to false to remove them.
$options = array(M => Male, F => Female);
$attributes = array(legend => false);
echo $this->Form->radio(gender, $options, $attributes);
Will output:
<input name="data[User][gender]" id="UserGender_" value=""
type="hidden" />
<input name="data[User][gender]" id="UserGenderM" value="M"
type="radio" />
<label for="UserGenderM">Male</label>
<input name="data[User][gender]" id="UserGenderF" value="F"
type="radio" />
<label for="UserGenderF">Female</label>
If for some reason you dont want the hidden input, setting $attributes[value] to a selected
value or boolean false will do just that.
Changed in version 2.1: The $attributes[disabled] option was added in 2.1.
FormHelper::select(string $fieldName, array $options, array $attributes)
Creates a select element, populated with the items in $options, with the option specified
by $attributes[value] shown as selected by default. Set the empty key in the
$attributes variable to false to turn off the default empty option:
$options = array(M => Male, F => Female);
echo $this->Form->select(gender, $options);
Will output:
<select name="data[User][gender]" id="UserGender">
<option value=""></option>
<option value="M">Male</option>
<option value="F">Female</option>
</select>
The select input type allows for a special $option attribute called escape which accepts a
bool and determines whether to HTML entity encode the contents of the select options. Defaults to
true:
$options = array(M => Male, F => Female);
echo $this->Form->select(gender, $options, array(escape => false));
$attributes[options] 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:
echo $this->Form->select(field, array(1,2,3,4,5));
Output:
414
Output:
<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>
If you would like to generate a select with optgroups, just pass data in hierarchical format. This
works on multiple checkboxes and radio buttons too, but instead of optgroups wraps elements in
fieldsets:
$options = array(
Group 1 => array(
Value 1 => Label 1,
Value 2 => Label 2
),
Group 2 => array(
Value 3 => Label 3
)
);
echo $this->Form->select(field, $options);
Output:
<select name="data[User][field]" id="UserField">
<optgroup label="Group 1">
<option value="Value 1">Label 1</option>
<option value="Value 2">Label 2</option>
</optgroup>
<optgroup label="Group 2">
<option value="Value 3">Label 3</option>
</optgroup>
</select>
$attributes[multiple] If multiple has been set to true for an input that outputs a
select, the select will allow multiple selections:
General Purpose
415
echo $this->Form->select(
Model.field,
$options,
array(multiple => true)
);
Output:
<div class="input select">
<label for="ModelField">Field</label>
<input name="data[Model][field]" value="" id="ModelField"
type="hidden">
<div class="checkbox">
<input name="data[Model][field][]" value="Value 1"
id="ModelField1" type="checkbox">
<label for="ModelField1">Label 1</label>
</div>
<div class="checkbox">
<input name="data[Model][field][]" value="Value 2"
id="ModelField2" type="checkbox">
<label for="ModelField2">Label 2</label>
</div>
</div>
Output:
<div class="input select">
<label for="ModelField">Field</label>
<input name="data[Model][field]" value="" id="ModelField"
type="hidden">
<div class="checkbox">
<input name="data[Model][field][]" disabled="disabled"
416
Changed in version 2.3: Support for arrays in $attributes[disabled] was added in 2.3.
FormHelper::file(string $fieldName, array $options)
To add a file upload field to a form, you must first make sure that the form enctype is set to
multipart/form-data, so start off with a create function such as the following:
echo $this->Form->create(Document, array(
enctype => multipart/form-data
));
// OR
echo $this->Form->create(Document, array(type => file));
Next add either of the two lines to your form view file:
echo $this->Form->input(Document.submittedfile, array(
between => <br />,
type => file
));
// OR
echo $this->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:
$this->request->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 uploads4 .
4
http://php.net/features.file-upload
General Purpose
417
Validating Uploads Below is an example validation method you could define in your model to validate
whether a file has been successfully uploaded:
public 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;
}
Will output:
<form enctype="multipart/form-data" method="post" action="/users/add">
<input name="data[User][avatar]" value="" id="UserAvatar" type="file">
Note: When using $this->Form->file(), remember to set the form encoding-type, by setting the
type option to file in $this->Form->create()
Will output:
<div class="submit"><input value="Submit" type="submit"></div>
You can also pass a relative or absolute URL to an image for the caption parameter instead of caption
text.
echo $this->Form->submit(ok.png);
Will output:
<div class="submit"><input type="image" src="/img/ok.png"></div>
418
Setting
$this->Form->button(A Button);
$this->Form->button(Another Button, array(type => button));
$this->Form->button(Reset the Form, array(type => reset));
$this->Form->button(Submit Form, array(type => submit));
Will output:
<button
<button
<button
<button
type="submit">A Button</button>
type="button">Another Button</button>
type="reset">Reset the Form</button>
type="submit">Submit Form</button>
The button input type supports the escape option, which accepts a bool and determines whether
to HTML entity encode the $title of the button. Defaults to false:
echo $this->Form->button(Submit Form, array(
type => submit,
escape => true
));
General Purpose
419
Will output:
<select name="data[User][purchased][year]" id="UserPurchasedYear">
<option value=""></option>
<option value="2009">2009</option>
<option value="2008">2008</option>
<option value="2007">2007</option>
<option value="2006">2006</option>
<option value="2005">2005</option>
<option value="2004">2004</option>
<option value="2003">2003</option>
<option value="2002">2002</option>
<option value="2001">2001</option>
<option value="2000">2000</option>
</select>
Will output:
<select name="data[User][mob][month]" id="UserMobMonth">
<option value=""></option>
<option value="01">January</option>
<option value="02">February</option>
<option value="03">March</option>
<option value="04">April</option>
<option value="05">May</option>
<option value="06">June</option>
<option value="07">July</option>
<option value="08">August</option>
<option value="09">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
You can pass in your own array of months to be used by setting the monthNames attribute, or have
420
months displayed as numbers by passing false. (Note: the default months are internationalized and
can be translated using localization.):
echo $this->Form->month(mob, array(monthNames => false));
Will output:
<select name="data[User][created][day]" id="UserCreatedDay">
<option value=""></option>
<option value="01">1</option>
<option value="02">2</option>
<option value="03">3</option>
...
<option value="31">31</option>
</select>
421
FormHelper::tagIsInvalid()
Returns false if given form field described by the current entity has no errors. Otherwise it returns the
validation message.
Setting Defaults for all fields New in version 2.2.
You can declare a set of default options for input() using FormHelper::inputDefaults().
Changing the default options allows you to consolidate repeated options into a single method call:
$this->Form->inputDefaults(array(
label => false,
div => false,
class => fancy
)
);
All inputs created from that point forward will inherit the options declared in inputDefaults. You can override
the default options by declaring the option in the input() call:
echo $this->Form->input(password); // No div, no label with class fancy
// has a label element same defaults
echo $this->Form->input(
username,
array(label => Username)
);
Working with SecurityComponent SecurityComponent offers several features that make your
forms safer and more secure. By simply including the SecurityComponent in your controller, youll
automatically benefit from CSRF and form tampering features.
As mentioned previously when using SecurityComponent, you should always close your forms using
FormHelper::end(). This will ensure that the special _Token inputs are generated.
FormHelper::unlockField($name)
Unlocks a field making it exempt from the SecurityComponent field hashing. This also allows
the fields to be manipulated by JavaScript. The $name parameter should be the entity name for the
input:
$this->Form->unlockField(User.id);
FormHelper::select()
FormHelper::dateTime()
FormHelper::year()
FormHelper::month()
FormHelper::day()
FormHelper::hour()
FormHelper::minute()
FormHelper::meridian()
Default URLs on forms is the current action
The default URL for all forms, is now the current URL including passed, named, and querystring parameters. You can override this default by supplying $options[url] in the second parameter of
$this->Form->create()
FormHelper::hidden()
Hidden fields no longer remove the class attribute. This means that if there are validation errors on hidden
fields, the error-field class name will be applied.
HtmlHelper
class HtmlHelper(View $view, array $settings = array())
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.
Many HtmlHelper methods 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:
Desired attributes: <tag class="someClass" />
Array parameter: array(class => someClass)
Desired attributes: <tag name="foo" value="bar" />
Array parameter: array(name => foo, value => bar)
Note: The HtmlHelper is available in all views by default. If youre getting an error informing you that
it isnt there, its usually due to its name being missing from a manually configured $helpers controller
variable.
Inserting Well-Formatted elements The most important task the HtmlHelper accomplishes is creating
well formed markup. Dont be afraid to use it often - you can cache views in CakePHP in order to save some
CPU cycles when views are being rendered and delivered. This section will cover some of the methods of
the HtmlHelper and how to use them.
HtmlHelper::charset($charset=null)
General Purpose
423
Parameters
$charset (string) Desired character set. If null, the value of App.encoding
will be used.
Used to create a meta tag specifying the documents character. Defaults to UTF-8
Example use:
echo $this->Html->charset();
Will output:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
Alternatively,
echo $this->Html->charset(ISO-8859-1);
Will output:
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
Will output:
<link rel="stylesheet" type="text/css" href="/css/forms.css" />
Will output:
424
You can include CSS files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/css/toolbar.css You could use the following:
echo $this->Html->css(DebugKit.toolbar.css);
If you want to include a CSS file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/css/Blog.common.css, you would:
.. versionchanged:: 2.4
translated value
text/html
application/rss+xml
application/atom+xml
image/x-icon
<?php
echo $this->Html->meta(
favicon.ico,
/favicon.ico,
array(type => icon)
);
?>
// Output (line breaks added)
<link
href="http://example.com/favicon.ico"
title="favicon.ico" type="image/x-icon"
rel="alternate"
/>
General Purpose
425
<?php
echo $this->Html->meta(
Comments,
/comments/index.rss,
array(type => rss)
);
?>
// Output (line breaks added)
<link
href="http://example.com/comments/index.rss"
title="Comments"
type="application/rss+xml"
rel="alternate"
/>
This method can also be used to add the meta keywords and descriptions. Example:
<?php
echo $this->Html->meta(
keywords,
enter any meta keyword here
);
?>
// Output
<meta name="keywords" content="enter any meta keyword here" />
<?php
echo $this->Html->meta(
description,
enter any meta description here
);
?>
// Output
<meta name="description" content="enter any meta description here" />
If you want to add a custom meta tag then the first parameter should be set to an array. To output a
robots noindex tag use the following code:
echo $this->Html->meta(array(name => robots, content => noindex));
426
type
html4-strict
html4-trans
html4-frame
html5
xhtml-strict
xhtml-trans
xhtml-frame
xhtml11
translated value
HTML4 Strict
HTML4 Transitional
HTML4 Frameset
HTML5
XHTML1 Strict
XHTML1 Transitional
XHTML1 Frameset
XHTML1.1
echo $this->Html->docType();
// Outputs:
// <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
//
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
echo $this->Html->docType(html5);
// Outputs: <!DOCTYPE html>
echo $this->Html->docType(html4-trans);
// Outputs:
// <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
//
"http://www.w3.org/TR/html4/loose.dtd">
Will output:
background:#633; border-bottom:1px solid #000; padding:10px;
General Purpose
427
Will output:
<img src="/img/cake_logo.png" alt="CakePHP" />
To create an image link specify the link destination using the url option in $htmlAttributes.
echo $this->Html->image("recipes/6.jpg", array(
"alt" => "Brownies",
url => array(controller => recipes, action => view, 6)
));
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
If you are creating images in emails, or want absolute paths to images you can use the fullBase
option:
echo $this->Html->image("logo.png", array(fullBase => true));
Will output:
<img src="http://example.com/img/logo.jpg" alt="" />
You can include image files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/img/icon.png You could use the following:
echo $this->Html->image(DebugKit.icon.png);
If you want to include a image file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/js/Blog.icon.png, you would:
echo $this->Html->image(Blog.icon.png, array(plugin => false));
Changed in version 2.1: The fullBase option was added. Support for plugin syntax was added.
HtmlHelper::link(string $title, mixed $url = null, array $options = array(), string $confirmMessage = false)
param string $title The text to display as the body of the link.
param mixed $url Either the string location, or a routing array.
param array $options An array of html attributes.
General purpose method for creating HTML links. Use $options to specify attributes
for the element and whether or not the $title should be escaped.
echo $this->Html->link(
Enter,
/pages/home,
428
Will output:
<a href="/pages/home" class="button" target="_blank">Enter</a>
Will output:
<a href="http://www.yourdomain.com/dashboards/index">Dashboard</a>
Will output:
<a href="/recipes/delete/6"
onclick="return confirm(
Are you sure you wish to delete this recipe?
);">
Delete
</a>
Will output:
<a href="/images/view/1?height=400&width=500">View image</a>
When using named parameters, use the array syntax and include names for ALL parameters
in the URL. Using the string syntax for paramters (i.e. recipes/view/6/comments:false
General Purpose
429
will result in the colon characters being HTML escaped and the link will not work as
desired.
<?php
echo $this->Html->link(
$this->Html->image("recipes/6.jpg", array("alt" => "Brownies")),
array(
controller => recipes,
action => view,
id => 6,
comments => false
)
);
Will output:
<a href="/recipes/view/id:6/comments:false">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
HTML special characters in $title will be converted to HTML entities. To disable this
conversion, set the escape option to false in the $options array.
<?php
echo $this->Html->link(
$this->Html->image("recipes/6.jpg", array("alt" => "Brownies")),
"recipes/view/6",
array(escape => false)
);
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
Setting escape to false will also disable escaping of attributes of the link. As of 2.4 you
can use the option escapeTitle to disable just escaping of title and not the attributes.
<?php
echo $this->Html->link(
$this->Html->image(recipes/6.jpg, array(alt => Brownies)),
recipes/view/6,
array(escapeTitle => false, title => hi "howdy")
);
Will output:
<a href="/recipes/view/6" title="hi "howdy"">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
$options instead.
Also check HtmlHelper::url method for more examples of different types of URLs.
HtmlHelper::media(string|array $path, array $options)
Parameters
$path (string|array) Path to the video file, relative to the webroot/{$options[pathPrefix]} directory. Or an array where each item itself can
be a path string or an associate array containing keys src and type.
$options (array) Array of HTML attributes, and special options.
Options:
type Type of media element to generate, valid values are audio or video. If
type is not provided media type is guessed based on files mime type.
text Text to include inside the video tag
pathPrefix Path prefix to use for relative URLs, defaults to files/
fullBase If provided the src attribute will get a full address including domain
name
New in version 2.1.
Returns a formatted audio/video tag:
<?php echo $this->Html->media(audio.mp3); ?>
// Output
<audio src="/files/audio.mp3"></audio>
<?php echo $this->Html->media(video.mp4, array(
fullBase => true,
text => Fallback text
)); ?>
// Output
<video src="http://www.somehost.com/files/video.mp4">Fallback text</video>
<?php echo $this->Html->media(
array(
video.mp4,
array(
src => video.ogg,
type => "video/ogg; codecs=theora, vorbis"
)
),
array(autoplay)
); ?>
// Output
<video autoplay="autoplay">
<source src="/files/video.mp4" type="video/mp4"/>
General Purpose
431
Note: Text is not escaped by default but you may use $htmlOptions[escape] = true
to escape your text. This replaces a fourth parameter boolean $escape = false that was
available in previous versions.
HtmlHelper::div(string $class, string $text, array $options)
Parameters
$class (string) The class name for the div.
$text (string) The content inside the div.
$options (array) An array of html attributes.
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.
If no text is specified, only an opening div tag is returned.:
<?php
echo $this->Html->div(error, Please enter your credit card number.);
?>
432
// Output
<div class="error">Please enter your credit card number.</div>
Will output:
<script type="text/javascript" href="/js/scripts.js"></script>
You can link to files with absolute paths as well to link files that are not in app/webroot/js:
General Purpose
433
echo $this->Html->script(/otherdir/script_file);
Will output:
<script type="text/javascript" href="http://code.jquery.com/jquery.min.js">
</script>
Will output:
<script type="text/javascript" href="/js/jquery.js"></script>
<script type="text/javascript" href="/js/wysiwyg.js"></script>
<script type="text/javascript" href="/js/scripts.js"></script>
You can append the script tag to a specific block using the block option:
echo $this->Html->script(wysiwyg, array(block => scriptBottom));
In your layout you can output all the script tags added to scriptBottom:
echo $this->fetch(scriptBottom);
You can include script files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/js/toolbar.js You could use the following:
echo $this->Html->script(DebugKit.toolbar.js);
If you want to include a script file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/js/Blog.plugins.js, you would:
echo $this->Html->script(Blog.plugins.js, array(plugin => false));
Changed in version 2.1: The block option was added. Support for plugin syntax was added.
HtmlHelper::scriptBlock($code, $options = array())
Parameters
$code (string) The code to go in the script tag.
$options (array) An array of html attributes.
Generate a code block containing $code set $options[inline] to false to have the script
block appear in the script view block. Other options defined will be added as attributes to
script tags. $this->Html->scriptBlock(stuff, array(defer => true));
will create a script tag with defer="defer" attribute.
HtmlHelper::scriptStart($options = array())
434
Parameters
$options (array) An array of html attributes to be used when scriptEnd is called.
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 as scriptBlock()
HtmlHelper::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:
$this->Html->scriptStart(array(inline => false));
echo $this->Js->alert(I am in the javascript);
$this->Html->scriptEnd();
HtmlHelper::nestedList(array $list, array $options = array(), array $itemOptions = array(), string $tag = ul)
Parameters
$list (array) Set of elements to list.
$options (array) Additional HTML attributes of the list (ol/ul) tag or if ul/ol use
that as tag.
$itemOptions (array) Additional HTML attributes of the list item (LI) tag.
$tag (string) Type of list tag to use (ol/ul).
Build a nested list (UL/OL) out of an associative array:
$list = array(
Languages => array(
English => array(
American,
Canadian,
British,
),
Spanish,
German,
)
);
echo $this->Html->nestedList($list);
Output:
// Output (minus the whitespace)
<ul>
<li>Languages
<ul>
<li>English
<ul>
<li>American</li>
General Purpose
435
<li>Canadian</li>
<li>British</li>
</ul>
</li>
<li>Spanish</li>
<li>German</li>
</ul>
</li>
</ul>
Output:
<tr>
<th>Date</th>
<th>Title</th>
<th>Active</th>
</tr>
echo $this->Html->tableHeaders(
array(Date,Title,Active),
array(class => status),
array(class => product_table)
);
Output:
<tr class="status">
<th class="product_table">Date</th>
<th class="product_table">Title</th>
<th class="product_table">Active</th>
</tr>
Changed in version 2.2: tableHeaders() now accepts attributes per cell, see below.
As of 2.2 you can set attributes per column, these are used instead of the defaults provided in the
$thOptions:
echo $this->Html->tableHeaders(array(
id,
array(Name => array(class => highlight)),
436
Output:
<tr>
<th>id</th>
<th class="highlight">Name</th>
<th class="sortable">Date</th>
</tr>
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>
echo $this->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))
General Purpose
437
),
));
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>
echo $this->Html->tableCells(
array(
array(Red, Apple),
array(Orange, Orange),
array(Yellow, Banana),
),
array(class => darker)
);
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>
438
General Purpose
439
echo $this->Html->url(array(
"controller" => "posts",
"action" => "search",
"?" => array("foo" => "bar"),
"#" => "first"
));
// Output
/posts/search?foo=bar#first
Output:
<form action="http://example.com" method="post" class="myform">
http://api.cakephp.org/2.4/class-Router.html#_url
440
The $startText option can also accept an array. This gives more control over the generated first
link:
echo $this->Html->getCrumbs( > , array(
text => $this->Html->image(home.png),
url => array(controller => pages, action => display, home),
escape => false
));
Any keys that are not text or url will be passed to link() as the $options parameter.
Changed in version 2.1: The $startText parameter now accepts an array.
HtmlHelper::addCrumb(string $name, string $link = null, mixed $options = null)
Now, in your view youll want to add the following to start the breadcrumb trails on each of the pages:
$this->Html->addCrumb(Users, /users);
$this->Html->addCrumb(Add User, array(controller => users, action => add));
This will add the output of Home > Users > Add User in your layout where getCrumbs was added.
HtmlHelper::getCrumbList(array $options = array(), mixed $startText)
Parameters
$options (array) An array of html attributes for the containing <ul> element.
Can also contain the separator, firstClass, lastClass and escape options.
$startText (string|array) The text or element that precedes the ul.
Returns breadcrumbs as a (x)html list.
This method uses HtmlHelper::tag() to generate list and its elements. Works similar
to getCrumbs(), so it uses options which every crumb was added with. You can use the
$startText parameter to provide the first breadcrumb link/text. This is useful when you always want to include a root link. This option works the same as the $startText option for
getCrumbs().
Changed in version 2.1: The $startText parameter was added.
Changed in version 2.3: The separator, firstClass and lastClass options were added.
Changed in version 2.5: The escape option was added.
JsHelper
class JsHelper(View $view, array $settings = array())
Warning: The JsHelper is currently deprecated and completely removed in 3.x. We recommend using
regular JavaScript and directly interacting with JavaScript libraries where possible.
Since the beginning CakePHPs support for JavaScript has been with Prototype/Scriptaculous. While we
still think these are excellent JavaScript libraries, the community has been asking for support for other
libraries. Rather than drop Prototype in favour of another JavaScript library. We created an Adapter based
General Purpose
441
Replace jquery with the name of your library file (.js will be added to the name).
By default scripts are cached, and you must explicitly print out the cache. To do this at the end of each page,
include this line just before the ending </body> tag:
echo $this->Js->writeBuffer(); // Write cached scripts
Warning: You must include the library in your page and print the cache for the helper to function.
JavaScript engine selection is declared when you include the helper in your controller:
public $helpers = array(Js => array(Jquery));
The above would use the Jquery Engine in the instances of JsHelper in your views. If you do not declare a
specific engine, the jQuery engine will be used as the default. As mentioned before, there are three engines
implemented in the core, but we encourage the community to expand the library compatibility.
Using jQuery with other libraries The jQuery library, and virtually all of its plugins are constrained
within the jQuery namespace. As a general rule, global objects are stored inside the jQuery namespace as
well, so you shouldnt get a clash between jQuery and any other library (like Prototype, MooTools, or YUI).
That said, there is one caveat: By default, jQuery uses $ as a shortcut for jQuery
To override the $ shortcut, use the jQueryObject variable:
$this->Js->JqueryEngine->jQueryObject = $j;
echo $this->Html->scriptBlock(
var $j = jQuery.noConflict();,
array(inline => false)
);
// Tell jQuery to go into noconflict mode
442
Using the JsHelper inside customHelpers Declare the JsHelper in the $helpers array in your customHelper:
public $helpers = array(Js);
Note: It is not possible to declare a JavaScript engine inside a custom helper. Doing that will have no effect.
If you are willing to use an other JavaScript engine than the default, do the helper setup in your controller as
follows:
public $helpers = array(
Js => array(Prototype),
CustomHelper
);
Warning:
controller.
Be sure to declare the JsHelper and its engine on top of the $helpers array in your
The selected JavaScript engine may disappear (replaced by the default) from the JsHelper object in your
helper, if you miss to do so and you will get code that does not fit your JavaScript library.
Creating a JavaScript Engine JavaScript engine helpers follow normal helper conventions, with
a few additional restrictions. They must have the Engine suffix. DojoHelper is not good,
DojoEngineHelper is correct. Furthermore, they should extend JsBaseEngineHelper in order
to leverage the most of the new API.
JavaScript engine usage The JsHelper provides a few methods, and acts as a facade for the the Engine
helper. You should not directly access the Engine helper except in rare occasions. Using the facade features
of the JsHelper allows you to leverage the buffering and method chaining features built-in; (method
chaining only works in PHP5).
The JsHelper by default buffers almost all script code generated, allowing you to collect scripts throughout the view, elements and layout, and output it in one place. Outputting buffered scripts is done with
$this->Js->writeBuffer(); this will return the buffer contents in a script tag. You can disable
buffering wholesale with the $bufferScripts property or setting buffer => false in methods
taking $options.
Since most methods in JavaScript begin with a selection of elements in the DOM, $this->Js->get()
returns a $this, allowing you to chain the methods using the selection. Method chaining allows you to write
shorter, more expressive code:
$this->Js->get(#foo)->event(click, $eventCode);
Is an example of method chaining. Method chaining is not possible in PHP4 and the above sample would
be written like:
$this->Js->get(#foo);
$this->Js->event(click, $eventCode);
General Purpose
443
Common options In attempts to simplify development where JavaScript 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 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.
Working with buffered scripts One drawback to previous implementation of Ajax type features was
the scattering of script tags throughout your document, and the inability to buffer scripts added by elements
in the layout. The new JsHelper if used correctly avoids both of those issues. It is recommended that you
place $this->Js->writeBuffer() at the bottom of your layout file above the </body> tag. This
will allow all scripts generated in layout elements to be output in one place. It should be noted that buffered
scripts are handled separately from included script files.
JsHelper::writeBuffer($options = array())
Writes all JavaScript generated so far to a code block or caches them to a file and returns a linked script.
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.
JsHelper::buffer($content)
Add $content to the internal script buffer.
JsHelper::getBuffer($clear = true)
Get the contents of the current buffer. Pass in false to not clear the buffer at the same time.
Buffering methods that are not normally buffered
Some methods in the helpers are buffered by default. The engines buffer the following methods by default:
event
sortable
drag
drop
444
slider
Additionally you can force any other method in JsHelper to use the buffering. By appending an boolean
to the end of the arguments you can force other methods to go into the buffer. For example the each()
method does not normally buffer:
$this->Js->each(alert("whoa!");, true);
The above would force the each() method to use the buffer. Conversely if you want a method that does
buffer to not buffer, you can pass a false in as the last argument:
$this->Js->event(click, alert("whoa!");, false);
This would force the event function which normally buffers to return its result.
Other Methods 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.
JsHelper::object($data, $options = array())
Serializes $data into JSON. This method is a proxy for json_encode() with a few extra features
added via the $options parameter.
Options:
prefix - String prepended to the returned data.
postfix - String appended to the returned data.
Example Use:
$json = $this->Js->object($data);
JsHelper::sortable($options = array())
Sortable generates a JavaScript snippet to make a set of elements (usually a list) drag and drop sortable.
The normalized options are:
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
General Purpose
445
Assuming you were using the jQuery engine, you would get the following code in your generated
JavaScript block
$("#myList").sortable({
containment:"parent",
distance:5,
sort:onSort,
start:onStart,
stop:onStop
});
Example use:
$this->Js->event(
click,
$this->Js->request(
array(action => foo, param1),
array(async => true, update => #element)
)
);
JsHelper::get($selector)
Set the internal selection to a CSS selector. The active selection is used in subsequent operations
until a new selection is made:
$this->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.
JsHelper::set(mixed $one, mixed $two = null)
Pass variables into JavaScript. Allows you to set variables that will be output when the buffer is
fetched with JsHelper::getBuffer() or JsHelper::writeBuffer(). The JavaScript
variable used to output set variables can be controlled with JsHelper::$setVariable.
JsHelper::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:
$this->Js->get(#element);
$this->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
General Purpose
447
$("#element").draggable({
containment:"#content",
drag:onDrag,
grid:[10,10],
start:onStart,
stop:onStop
});
JsHelper::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:
$this->Js->get(#element);
$this->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
$("#element").droppable({
accept:".items",
drop:onDrop,
out:onExit,
over:onHover
});
Note: Droppables in Mootools function differently from other libraries. Droppables are implemented
as an extension of Drag. So in addition 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.
JsHelper::slider($options = array())
Create snippet of JavaScript that converts an element into a slider ui widget. See your libraries implementation for additional usage and features.
Options
448
If you were using the jQuery engine the following code would be added to the buffer
$("#element").slider({
change:onChange,
max:10,
min:0,
orientation:"vertical",
stop:onComplete,
value:2
});
General Purpose
449
If you were using the jQuery library you would get the following JavaScript code:
$(#some-link).bind(click, function (event) {
alert(hey you!);
return false;
});
You can remove the return false; by passing setting the stop option to false:
$this->Js->get(#some-link);
$this->Js->event(
click,
$this->Js->alert(hey you!),
array(stop => false)
);
If you were using the jQuery library you would the following JavaScript code would be added to the
buffer. Note that the default browser event is not cancelled:
$(#some-link).bind(click, function (event) {
alert(hey you!);
});
450
JsHelper::domReady($callback)
Creates the special DOM ready event. JsHelper::writeBuffer() automatically wraps the
buffered scripts in a domReady method.
JsHelper::each($callback)
Create a snippet that iterates over the currently selected elements, and inserts $callback.
Example:
$this->Js->get(div.message);
$this->Js->each($(this).css({color: "red"}););
JsHelper::alert($message)
Create a JavaScript snippet containing an alert() snippet. By default, alert does not buffer, and
returns the script snippet.
$alert = $this->Js->alert(Hey there);
JsHelper::confirm($message)
Create a JavaScript snippet containing a confirm() snippet. By default, confirm does not buffer,
and returns the script snippet.
$alert = $this->Js->confirm(Are you sure?);
JsHelper::prompt($message, $default)
Create a JavaScript snippet containing a prompt() snippet. By default, prompt does not buffer,
and returns the script snippet.
$prompt = $this->Js->prompt(What is your favorite color?, blue);
General Purpose
451
Will create a submit button with an attached onclick event. The click event will be buffered by default.
echo $this->Js->submit(Save, array(
update => #content,
div => false,
type => json,
async => false
));
both
FormHelper::submit()
and
Will create a link pointing to /page:2 and updating #content with the response.
You can use the htmlAttributes option to add in additional custom attributes.
echo $this->Js->link(Page 2, array(page => 2), array(
update => #content,
htmlAttributes => array(other => value)
));
JsHelper::serializeForm($options = array())
Serialize the form attached to $selector. Pass true for $isForm if the current selection is a form
452
element. Converts the form or the form element attached to the current selection into a string/json
object (depending on the library implementation) for use with XHR operations.
Options
isForm - is the current selection a form, or an input? (defaults to false)
inline - is the rendered statement going to be used inside another JS statement? (defaults to
false)
Setting inline == false allows you to remove the trailing ;. This is useful when you need to serialize a
form element as part of another JavaScript operation, or use the serialize method in an Object literal.
JsHelper::redirect($url)
Redirect the page to $url using window.location.
JsHelper::value($value)
Converts a PHP-native variable of any type to a JSON-equivalent representation. Escapes any string
values into JSON compatible strings. UTF-8 characters will be escaped.
AJAX Pagination Much like AJAX Pagination in 1.2, you can use the JsHelper to handle the creation of
AJAX pagination links instead of plain HTML links.
Making AJAX Links Before you can create AJAX links you must include the JavaScript library
that matches the adapter you are using with JsHelper. By default the JsHelper uses jQuery.
So in your layout include jQuery (or whichever library you are using). Also make sure to include
RequestHandlerComponent in your components. Add the following to your controller:
public $components = array(RequestHandler);
public $helpers = array(Js);
Next link in the JavaScript library you want to use. For this example well be using jQuery:
echo $this->Html->script(jquery);
Similar to 1.2 you need to tell the PaginatorHelper that you want to make JavaScript enhanced links
instead of plain HTML ones. To do so, call the options() at the top of your view:
$this->Paginator->options(array(
update => #content,
evalScripts => true
));
The PaginatorHelper now knows to make JavaScript enhanced links, and that those links should
update the #content element. Of course this element must exist, and often times you want to wrap
$content_for_layout with a div matching the id used for the update option. You also should set
evalScripts to true if you are using the Mootools or Prototype adapters, without evalScripts these
libraries will not be able to chain requests together. The indicator option is not supported by JsHelper
and will be ignored.
You then create all the links as needed for your pagination features. Since the JsHelper automatically
buffers all generated script content to reduce the number of <script> tags in your source code you must
General Purpose
453
write the buffer out. At the bottom of your view file. Be sure to include:
echo $this->Js->writeBuffer();
If you omit this you will not be able to chain AJAX pagination links. When you write the buffer, it is also
cleared, so you dont have worry about the same JavaScript being output twice.
Adding effects and transitions Since indicator is no longer supported, you must add any indicator
effects yourself:
<!DOCTYPE html>
<html>
<head>
<?php echo $this->Html->script(jquery); ?>
//more stuff here.
</head>
<body>
<div id="content">
<?php echo $this->fetch(content); ?>
</div>
<?php
echo $this->Html->image(
indicator.gif,
array(id => busy-indicator)
);
?>
</body>
</html>
Remember to place the indicator.gif file inside app/webroot/img folder. You may see a situation where the
indicator.gif displays immediately upon the page load. You need to put in this CSS #busy-indicator
{ display:none; } in your main CSS file.
With the above layout, weve included an indicator image file, that will display a busy indicator animation
that we will show and hide with the JsHelper. To do that we need to update our options() function:
$this->Paginator->options(array(
update => #content,
evalScripts => true,
before => $this->Js->get(#busy-indicator)->effect(
fadeIn,
array(buffer => false)
),
complete => $this->Js->get(#busy-indicator)->effect(
fadeOut,
array(buffer => false)
),
));
This will show/hide the busy-indicator element before and after the #content div is updated. Although
indicator has been removed, the new features offered by JsHelper allow for more control and more
complex effects to be created.
454
NumberHelper
class NumberHelper(View $view, array $settings = array())
The NumberHelper contains convenient 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 formatting numbers.
Changed in version 2.1: NumberHelper have been refactored into CakeNumber class to allow easier
use outside of the View layer. Within a view, these methods are accessible via the NumberHelper class
and you can call it as you would call a normal helper method: $this->Number->method($args);.
All of these functions return the formatted number; They do not automatically echo the output into the view.
NumberHelper::currency(mixed $number, string $currency = USD, array $options = array())
Parameters
$number (float) The value to covert.
$currency (string) The known currency format to use.
$options (array) Options, see below.
This method is used to display a number in common currency formats (EUR,GBP,USD). Usage in a
view looks like:
// called as NumberHelper
echo $this->Number->currency($number, $currency);
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::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:
$currency
EUR
GBP
USD
The third parameter is an array of options for further defining the output. The following options are
available:
General Purpose
455
Option
before
after
zero
places
thousands
decimals
negative
escape
wholeSymbol
wholePosition
fractionSymbol
fractionPosition
fractionExponent
Description
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 no decimal symbol. eg. 0.35 => $0.35.
The text to use for zero values, can be a string or a number. ie. 0, Free!
Number of decimal places to use. ie. 2
Thousands separator ie. ,
Decimal separator symbol ie. .
Symbol for negative numbers. If equal to (), the number will be wrapped with (
and )
Should the output be htmlentity escaped? Defaults to true
String to use for whole numbers ie. dollars
Either before or after to place the whole symbol
String to use for fraction numbers ie. cents
Either before or after to place the fraction symbol
Fraction exponent of this specific currency. Defaults to 2.
456
Add a currency format to the Number helper. Makes reusing currency formats easier:
// called as NumberHelper
$this->Number->addFormat(BRL, array(before => R$, thousands => ., decimals
// called as CakeNumber
App::uses(CakeNumber, Utility);
CakeNumber::addFormat(BRL, array(before => R$, thousands => ., decimals =>
You can now use BRL as a short form when formatting currency amounts:
// called as NumberHelper
echo $this->Number->currency($value, BRL);
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::currency($value, BRL);
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
,
before,
false,
after,
0,
2,
,,
.,
(),
true,
2
General Purpose
457
App::uses(CakeNumber, Utility);
echo CakeNumber::precision(456.91873645, 2);
Description
Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal
percentages.
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.
// Called as NumberHelper. Output: 45.69%
echo $this->Number->toPercentage(45.691873645);
// Called as CakeNumber. Output: 45.69%
App::uses(CakeNumber, Utility);
echo CakeNumber::toPercentage(45.691873645);
// Called with multiply. Output: 45.69%
echo CakeNumber::toPercentage(0.45691, 2, array(
multiply => true
));
New in version 2.4: The $options argument with the multiply option was added.
NumberHelper::fromReadableSize(string $size, $default)
Parameters
$size (string) The formatted human readable value.
This method unformats a number from a human readable byte size to an integer number of bytes.
New in version 2.3: This method was added in 2.3
NumberHelper::toReadableSize(string $dataSize)
Parameters
$dataSize (string) The number of bytes to make readable.
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):
458
// called as NumberHelper
echo $this->Number->toReadableSize(0); // 0 Bytes
echo $this->Number->toReadableSize(1024); // 1 KB
echo $this->Number->toReadableSize(1321205.76); // 1.26 MB
echo $this->Number->toReadableSize(5368709120); // 5.00 GB
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::toReadableSize(0); // 0 Bytes
echo CakeNumber::toReadableSize(1024); // 1 KB
echo CakeNumber::toReadableSize(1321205.76); // 1.26 MB
echo CakeNumber::toReadableSize(5368709120); // 5.00 GB
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
Example:
// called as NumberHelper
echo $this->Number->format(123456.7890, array(
places => 2,
before => ,
escape => false,
decimals => .,
thousands => ,
));
// output 123,456.79
General Purpose
459
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::format(123456.7890, array(
places => 2,
before => ,
escape => false,
decimals => .,
thousands => ,
));
// output 123,456.79
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 takes the same keys as CakeNumber::format() itself:
places (integer): the amount of desired precision
before (string): to be put before the outputted number
after (string): to be put after the outputted number
decimals (string): used to delimit the decimal places in a number
thousands (string): used to mark off thousand, millions, . . . places
Example:
// called as NumberHelper
echo $this->Number->formatDelta(123456.7890, array(
places => 2,
decimals => .,
thousands => ,
));
// output +123,456.79
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::formatDelta(123456.7890, array(
places => 2,
decimals => .,
thousands => ,
));
// output +123,456.79
Warning: Since 2.4 the symbols are now UTF-8. Please see the migration guide for details if you run a
non-UTF-8 app.
Paginator
class PaginatorHelper(View $view, array $settings = array())
The Pagination helper is used to output pagination controls such as page numbers and next/previous links.
It works in tandem with PaginatorComponent.
See also Pagination for information on how to create paginated datasets and do paginated queries.
Creating sort links
PaginatorHelper::sort($key, $title = null, $options = array())
Parameters
$key (string) The name of the key that the recordset should be sorted.
$title (string) Title for the link. If $title is null $key will be used for the title and
will be generated by inflection.
$options (array) Options for sorting link.
Generates a sorting link. Sets named or querystring parameters for the sort and direction. Links will default
to sorting by asc. After the first click, links generated with sort() will handle direction switching automatically. Link sorting default by asc. If the resultset is sorted asc by the specified key the returned link
will sort by desc.
Accepted keys for $options:
escape Whether you want the contents HTML entity encoded, defaults to true.
model The model to use, defaults to PaginatorHelper::defaultModel().
direction The default direction to use when this link isnt active.
lock Lock direction. Will only use the default direction then, defaults to false.
New in version 2.5: You can now set the lock option to true in order to lock the sorting direction into
the specified direction.
Assuming you are paginating some posts, and are on page one:
echo $this->Paginator->sort(user_id);
Output:
<a href="/posts/index/page:1/sort:user_id/direction:asc/">User Id</a>
You can use the title parameter to create custom text for your link:
echo $this->Paginator->sort(user_id, User account);
Output:
General Purpose
461
If you are using HTML like images in your links remember to set escaping off:
echo $this->Paginator->sort(
user_id,
<em>User account</em>,
array(escape => false)
);
Output:
<a href="/posts/index/page:1/sort:user_id/direction:asc/">
<em>User account</em>
</a>
The direction option can be used to set the default direction for a link. Once a link is active, it will automatically switch directions like normal:
echo $this->Paginator->sort(user_id, null, array(direction => desc));
Output:
<a href="/posts/index/page:1/sort:user_id/direction:desc/">User Id</a>
The lock option can be used to lock sorting into the specified direction:
echo $this->Paginator->sort(user_id, null, array(direction => asc, lock => true));
462
first Whether you want first links generated, set to an integer to define the number of first links
to generate. Defaults to false. If a string is set a link to the first page will be generated with the value
as the title:
echo $this->Paginator->numbers(array(first => First page));
last Whether you want last links generated, set to an integer to define the number of last links
to generate. Defaults to false. Follows the same logic as the first option. There is a last()
method to be used separately as well if you wish.
ellipsis Ellipsis content, defaults to ...
class The class name used on the wrapping tag.
currentClass The class name to use on the current/active link. Defaults to current.
currentTag Tag to use for current page number, defaults to null. This allows you to generate for
example Twitter Bootstrap like links with the current page number wrapped in extra a or span tag.
While this method allows a lot of customization for its output. It is also ok to just call the method without
any params.
echo $this->Paginator->numbers();
Using the first and last options you can create links to the beginning and end of the page set. The following
would create a set of page links that include links to the first 2 and last 2 pages in the paged results:
echo $this->Paginator->numbers(array(first => 2, last => 2));
General Purpose
463
escape Whether you want the contents HTML entity encoded, defaults to true.
model The model to use, defaults to PaginatorHelper::defaultModel().
disabledTag Tag to use instead of A tag when there is no previous page
A simple example would be:
echo $this->Paginator->prev(
<< . __(previous),
array(),
null,
array(class => prev disabled)
);
If you were currently on the second page of posts, you would get the following:
<span class="prev">
<a rel="prev" href="/posts/index/page:1/sort:title/order:desc">
<< previous
</a>
</span>
You can change the wrapping tag using the tag option:
echo $this->Paginator->prev(__(previous), array(tag => li));
Output:
<li class="prev">
<a rel="prev" href="/posts/index/page:1/sort:title/order:desc">
previous
</a>
</li>
Output:
<a class="prev" rel="prev"
href="/posts/index/page:1/sort:title/order:desc">
previous
</a>
Changed
in
version
2.3:
For
methods:
PaginatorHelper::prev()
and
PaginatorHelper::next() it is now possible to set the tag option to false to disable the
wrapper. New options disabledTag has been added.
If you leave the $disabledOptions empty the $options parameter will be used. This can save some
additional typing if both sets of options are the same.
464
The above creates a single link for the first page. Will output nothing if you are on the first page. You
can also use an integer to indicate how many first paging links you want generated:
echo $this->Paginator->first(3);
The above will create links for the first 3 pages, once you get to the third or greater page. Prior to that
nothing will be output.
The options parameter accepts the following:
tag The tag wrapping tag you want to use, defaults to span
after Content to insert after the link/tag
model The model to use defaults to PaginatorHelper::defaultModel()
separator Content between the generated links, defaults to |
ellipsis Content for ellipsis, defaults to ...
PaginatorHelper::last($last = last >>, $options = array())
This method works very much like the first() method. It has a few differences though. It will not
generate any links if you are on the last page for a string values of $last. For an integer value of
$last no links will be generated once the user is inside the range of last pages.
PaginatorHelper::current(string $model = null)
Gets the current page of the recordset for the given model:
// Our URL is: http://example.com/comments/view/page:3
echo $this->Paginator->current(Comment);
// Output is 3
General Purpose
465
Returns a counter string for the paged result set. Using a provided format string and a number of options
you can create localized and application specific indicators of where a user is in the paged data set.
There are a number of options for counter(). The supported ones are:
format Format of the counter. Supported formats are range, pages and custom. Defaults to pages
which would output like 1 of 10. In the custom mode the supplied string is parsed and tokens are
replaced with actual values. The available tokens are:
{:page} - the current page displayed.
{:pages} - total number of pages.
{:current} - current number of records being shown.
{:count} - the total number of records in the result set.
{:start} - number of the first record being displayed.
{:end} - number of the last record being displayed.
{:model} - The pluralized human form of the model name. If your model was RecipePage,
{:model} would be recipe pages. This option was added in 2.0.
You could also supply only a string to the counter method using the tokens available. For example:
echo $this->Paginator->counter(
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 which is format default value:
echo $this->Paginator->counter(array(
separator => of a total of
));
model
The
name
of
the
model
being
paginated,
defaults
to
PaginatorHelper::defaultModel(). This is used in conjunction with the custom
string on format option.
Modifying the options PaginatorHelper uses
PaginatorHelper::options($options = array())
Parameters
$options (mixed) Default options for pagination links. If a string is supplied - it
is used as the DOM id element to update.
Sets all the options for the Paginator Helper. Supported options are:
466
url The URL of the paginating action. url has a few sub options as well:
sort The key that the records are sorted by.
direction The direction of the sorting. Defaults to ASC.
page The page number to display.
The above mentioned options can be used to force particular pages/directions. You can also append
additional URL content into all URLs generated in the helper:
$this->Paginator->options(array(
url => array(
sort => email, direction => desc, page => 6,
lang => en
)
));
The above adds the en route parameter to all links the helper will generate. It will also create links
with specific sort, direction and page values. By default PaginatorHelper will merge in all of the
current pass and named parameters. So you dont have to do that in each view file.
escape Defines if the title field for links should be HTML escaped. Defaults to true.
update The CSS selector of the element to update with the results of AJAX pagination calls. If not
specified, regular links will be created:
$this->Paginator->options(array(update => #content));
This is useful when doing AJAX Pagination. Keep in mind that the value of update can be any valid
CSS selector, but most often is simpler to use an id selector.
model
The
name
of
the
model
PaginatorHelper::defaultModel().
being
paginated,
defaults
to
Using GET parameters for pagination Normally Pagination in CakePHP uses Named Parameters. There
are times you want to use GET parameters instead. While the main configuration option for this feature is
in PaginatorComponent, you have some additional control in the view. You can use options() to
indicate that you want other named parameters to be converted:
$this->Paginator->options(array(
convertKeys => array(your, keys, here)
));
Configuring the PaginatorHelper to use a JavaScript helper By default the PaginatorHelper uses
JsHelper to do AJAX features. However, if you dont want that and want to use a custom helper for AJAX
links, you can do so by changing the $helpers array in your controller. After running paginate() do
the following:
// In your controller action.
$this->set(posts, $this->paginate());
$this->helpers[Paginator] = array(ajax => CustomJs);
General Purpose
467
Will change the PaginatorHelper to use the CustomJs for AJAX operations. You could also set
the ajax key to be any helper, as long as that class implements a link() method that behaves like
HtmlHelper::link()
Pagination in Views Its up to you to decide how to show records to the user, but most often this will be
done inside HTML tables. The examples below assume a tabular layout, but the PaginatorHelper available
in views doesnt always need to be restricted as such.
See the details on PaginatorHelper6 in the API. As mentioned, the PaginatorHelper also offers sorting features which can be easily integrated into your table column headers:
// app/View/Posts/index.ctp
<table>
<tr>
<th><?php echo $this->Paginator->sort(id, ID); ?></th>
<th><?php echo $this->Paginator->sort(title, Title); ?></th>
</tr>
<?php foreach ($data as $recipe): ?>
<tr>
<td><?php echo $recipe[Recipe][id]; ?> </td>
<td><?php echo h($recipe[Recipe][title]); ?> </td>
</tr>
<?php endforeach; ?>
</table>
The links output from the sort() method of the PaginatorHelper allow users to click on table headers
to toggle the sorting of the data by a given field.
It is also possible to sort a column based on associations:
<table>
<tr>
<th><?php echo $this->Paginator->sort(title, Title); ?></th>
<th><?php echo $this->Paginator->sort(Author.name, Author); ?></th>
</tr>
<?php foreach ($data as $recipe): ?>
<tr>
<td><?php echo h($recipe[Recipe][title]); ?> </td>
<td><?php echo h($recipe[Author][name]); ?> </td>
</tr>
<?php endforeach; ?>
</table>
The final ingredient to pagination display in views is the addition of page navigation, also supplied by the
PaginationHelper:
// Shows the page numbers
echo $this->Paginator->numbers();
// Shows the next and previous links
echo $this->Paginator->prev(
6
http://api.cakephp.org/2.4/class-PaginatorHelper.html
468
Previous,
null,
null,
array(class => disabled)
);
echo $this->Paginator->next(
Next ,
null,
null,
array(class => disabled)
);
// prints X of Y, where X is current page and Y is number of pages
echo $this->Paginator->counter();
The wording output by the counter() method can also be customized using special markers:
echo $this->Paginator->counter(array(
format => Page {:page} of {:pages}, showing {:current} records out of
{:count} total, starting on record {:start}, ending on {:end}
));
Other Methods
PaginatorHelper::link($title, $url = array(), $options = array())
Parameters
$title (string) Title for the link.
$url (mixed) Url for the action. See Router::url()
$options (array) Options for the link. See options() for list of keys.
Accepted keys for $options:
update The Id of the DOM element you wish to update. Creates AJAX enabled links.
escape Whether you want the contents HTML entity encoded, defaults to true.
model The model to use, defaults to PaginatorHelper::defaultModel().
Creates a regular or AJAX link with pagination parameters:
echo $this->Paginator->link(Sort by title on page 5,
array(sort => title, page => 5, direction => desc));
pointing
at
Parameters
$options (array) Pagination/URL options array. As used on options() or
link() method.
General Purpose
469
PaginatorHelper::defaultModel()
Gets the default model of the paged sets or null if pagination is not initialized.
PaginatorHelper::params(string $model = null)
Gets the current paging parameters from the resultset for the given model:
debug($this->Paginator->params());
/*
Array
(
[page] => 2
[current] => 2
[count] => 43
[prevPage] => 1
[nextPage] => 3
[pageCount] => 3
[order] =>
[limit] => 20
[options] => Array
(
[page] => 2
[conditions] => Array
(
)
)
[paramType] => named
)
*/
470
You can also append the output of the meta function to the named block:
$this->Paginator->meta(array(block => true));
In the call above weve activated the .rss extension. When using Router::parseExtensions() you
can pass as many arguments or extensions as you want. This will activate each extension/content-type for
use in your application. Now when the address posts/index.rss is requested you will get an xml
version of your posts/index. However, first we need to edit the controller to add in the rss-specific
code.
Controller Code It is a good idea to add RequestHandler to your PostsControllers $components array.
This will allow a lot of automagic to occur:
public $components = array(RequestHandler);
Our view will also use the TextHelper for formatting, so that should be added to the controller as well:
public $helpers = array(Text);
Before we can make an RSS version of our posts/index we need to get a few things in order. It may
be tempting to put the channel metadata in the controller action and pass it to your view using the
Controller::set() method but this is inappropriate. That information can also go in the view. That
will come later though, for now if you have a different set of logic for the data used to make the RSS feed
and the data for the HTML view you can use the RequestHandler::isRss() method, otherwise your
controller can stay the same:
// Modify the Posts Controller action that corresponds to
// the action which deliver the rss feed, which is the
// index action in our example
public function index() {
if ($this->RequestHandler->isRss() ) {
General Purpose
471
$posts = $this->Post->find(
all,
array(limit => 20, order => Post.created DESC)
);
return $this->set(compact(posts));
}
// this is not an Rss request, so deliver
// data used by websites interface
$this->paginate[Post] = array(
order => Post.created DESC,
limit => 10
);
$posts = $this->paginate();
$this->set(compact(posts));
}
With all the View variables set we need to create an rss layout.
Layout An
Rss
layout
is
very
simple,
app/View/Layouts/rss/default.ctp:
put
the
following
contents
in
if (!isset($documentData)) {
$documentData = array();
}
if (!isset($channelData)) {
$channelData = array();
}
if (!isset($channelData[title])) {
$channelData[title] = $this->fetch(title);
}
$channel = $this->Rss->channel(array(), $channelData, $this->fetch(content));
echo $this->Rss->document($documentData, $channel);
It doesnt look like much but thanks to the power in the RssHelper its doing a lot of lifting for us. We
havent set $documentData or $channelData in the controller, however in CakePHP 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
View/Posts/rss/ directory and create a new index.ctp inside that folder. The contents of the file
are below.
View Our view, located at app/View/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 channels metadata back to the layout:
$this->set(channelData, array(
title => __("Most Recent Posts"),
472
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.
Note: You will need to modify the $postLink variable as appropriate to your application.
foreach ($posts as $post) {
$postTime = strtotime($post[Post][created]);
$postLink = array(
controller => posts,
action => view,
year => date(Y, $postTime),
month => date(m, $postTime),
day => date(d, $postTime),
$post[Post][slug]
);
// Remove & escape any HTML to make sure the feed content will validate.
$bodyText = h(strip_tags($post[Post][body]));
$bodyText = $this->Text->truncate($bodyText, 400, array(
ending => ...,
exact => true,
html
=> true,
));
echo
$this->Rss->item(array(), array(
title => $post[Post][title],
link => $postLink,
guid => array(url => $postLink, isPermaLink => true),
description => $bodyText,
pubDate => $post[Post][created]
));
}
You can see above that we can use the loop to prepare the data to be transformed into XML elements. It
is important to filter out any non-plain text characters out of the description, especially if you are using
a rich text editor for the body of your blog. In the code above we used strip_tags() and h() to
remove/escape any XML special characaters from the content, as they could cause validation errors. Once
we have set up the data for the feed, we can then use the RssHelper::item() method to create the
General Purpose
473
XML in RSS format. Once you have all this setup, you can test your RSS feed by going to your site
/posts/index.rss and you will see your new feed. It is always important that you validate your RSS
feed before making it live. This can be done by visiting sites that validate the XML such as Feed Validator
or the w3c site at http://validator.w3.org/feed/.
Note: 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.
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.
SessionHelper::read(string $key)
Return type mixed
Read from the Session. Returns a string or array depending on the contents of the session.
SessionHelper::check(string $key)
Return type boolean
Check to see whether a key is in the Session. Returns a boolean representing the keys existence.
SessionHelper::error()
Return type string
Returns last error encountered in a session.
SessionHelper::valid()
Return type boolean
Used to check whether a session is valid in a view.
General Purpose
475
The above will output a simple message with the following HTML:
<div id="flashMessage" class="message">
Your stuff has been saved.
</div>
As with the component method, you can set additional properties and customize which element is
used. In the controller, you might have code like:
// in a controller
$this->Session->setFlash(The user could not be deleted.);
When outputting this message, you can choose the element used to display the message:
// in a layout.
echo $this->Session->flash(flash, array(element => failure));
This would use View/Elements/failure.ctp to render the message. The message text would
be available as $message in the element.
The failure element would contain something like this:
<div class="flash flash-failure">
<?php echo h($message); ?>
</div>
You can also pass additional parameters into the flash() method, which allows you to generate
customized messages:
// In the controller
$this->Session->setFlash(Thanks for your payment.);
// In the layout.
echo $this->Session->flash(flash, array(
params => array(name => $user[User][name])
element => payment
));
// View/Elements/payment.ctp
<div class="flash payment">
<?php printf($message, h($name)); ?>
</div>
Note:
476
By default, CakePHP does not escape the HTML in flash messages. If you are using any
Chapter 7. Core Libraries
request or user data in your flash messages, you should escape it with h when formatting your messages.
TextHelper
class TextHelper(View $view, array $settings = array())
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 gracefully truncating long stretches of text.
Changed in version 2.1: Several TextHelper methods have been moved into the String class to allow
easier use outside of the View layer. Within a view, these methods are accessible via the TextHelper class.
You can call one as you would call a normal helper method: $this->Text->method($args);.
TextHelper::autoLinkEmails(string $text, array $options=array())
Parameters
$text (string) The text to convert.
$options (array) An array of html attributes for the generated links.
Adds links to the well-formed email addresses in $text, according to any options defined in
$htmlOptions (see HtmlHelper::link()).
$myText = For more information regarding our world-famous .
pastries and desserts, contact [email protected];
$linkedText = $this->Text->autoLinkEmails($myText);
Output:
For more information regarding our world-famous pastries and desserts,
contact <a href="mailto:[email protected]">[email protected]</a>
Changed in version 2.1: In 2.1 this method automatically escapes its input. Use the escape option
to disable this if necessary.
TextHelper::autoLinkUrls(string $text, array $htmlOptions=array())
Parameters
$text (string) The text to convert.
$htmlOptions (array) An array html attributes for the generated links
Same as autoLinkEmails(), only this method searches for strings that start with https, http, ftp,
or nntp and links them appropriately.
Changed in version 2.1: In 2.1 this method automatically escapes its input. Use the escape option
to disable this if necessary.
TextHelper::autoLink(string $text, array $htmlOptions=array())
Parameters
$text (string) The text to autolink.
General Purpose
477
Output:
<p>For more information<br />
regarding our world-famous pastries and desserts.<p>
<p>contact [email protected]</p>
478
// called as String
App::uses(String, Utility);
echo String::highlight(
$lastSentence,
using,
array(format => <span class="highlight">\1</span>)
);
Output:
Highlights $needle in $haystack <span class="highlight">using</span>
the $options[format] string specified or a default string.
TextHelper::stripLinks($text)
Strips the supplied $text of any HTML links.
TextHelper::truncate(string $text, int $length=100, array $options)
Parameters
$text (string) The text to truncate.
$length (int) The length, in characters, beyond which the text should be truncated.
$options (array) An array of options to use.
If $text is longer than $length characters, this method truncates it at $length and adds a prefix
consisting of ellipsis, if defined. If exact is passed as false, the truncation will occur
at the first whitespace after the point at which $length is exceeded. If html is passed as true,
HTML tags will be respected and will not be cut off.
$options is used to pass all extra parameters, and has the following possible keys by default, all of
which are optional:
array(
ellipsis => ...,
exact => true,
html => false
)
Example:
// called as TextHelper
echo $this->Text->truncate(
The killer crept forward and tripped on the rug.,
22,
array(
ellipsis => ...,
exact => false
)
);
// called as String
App::uses(String, Utility);
General Purpose
479
echo String::truncate(
The killer crept forward and tripped on the rug.,
22,
array(
ellipsis => ...,
exact => false
)
);
Output:
The killer crept...
Changed in version 2.3: ending has been replaced by ellipsis. ending is still used in 2.2.1
TextHelper::tail(string $text, int $length=100, array $options)
Parameters
$text (string) The text to truncate.
$length (int) The length, in characters, beyond which the text should be truncated.
$options (array) An array of options to use.
If $text is longer than $length characters, this method removes an initial substring with length
consisting of the difference and prepends a suffix consisting of ellipsis, if defined. If
exact is passed as false, the truncation will occur at the first whitespace prior to the point
at which truncation would otherwise take place.
$options is used to pass all extra parameters, and has the following possible keys by default, all of
which are optional:
array(
ellipsis => ...,
exact => true
)
480
// called as String
App::uses(String, Utility);
echo String::tail(
$sampleText,
70,
array(
ellipsis => ...,
exact => false
)
);
Output:
...a TV, a C# program that can divide by zero, death metal t-shirts
Output:
... by $radius, and prefix/suffix with $ellipsis. This method is
especially handy for search results. The query...
General Purpose
481
// called as TextHelper
echo $this->Text->toList($colors);
// called as String
App::uses(String, Utility);
echo String::toList($colors);
Output:
red, orange, yellow, green, blue, indigo and violet
TimeHelper
class TimeHelper(View $view, array $settings = array())
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:
1. It can format time strings.
2. It can test time (but cannot bend time, sorry).
Changed in version 2.1: TimeHelper has been refactored into the CakeTime class to allow easier use
outside of the View layer. Within a view, these methods are accessible via the TimeHelper class and you
can call it as you would call a normal helper method: $this->Time->method($args);.
Using the Helper A common use of the Time Helper is to offset the date and time to match a users time
zone. Lets use a forum as an example. Your forum has many users who may post messages at any time from
any part of the world. An easy way to manage the time is to save all dates and times as GMT+0 or UTC. Uncomment the line date_default_timezone_set(UTC); in app/Config/core.php to ensure your applications time zone is set to GMT+0.
Next add a time zone field to your users table and make the necessary modifications to allow your users to
set their time zone. Now that we know the time zone of the logged in user we can correct the date and time
on our posts using the Time Helper:
echo $this->Time->format(
F jS, Y h:i A,
$post[Post][created],
null,
$user[User][time_zone]
);
// Will display August 22nd, 2011 11:53 PM for a user in GMT+0
// August 22nd, 2011 03:53 PM for a user in GMT-8
// and August 23rd, 2011 09:53 AM GMT+10
Most of the Time Helper methods have a $timezone parameter. The $timezone parameter accepts a valid
timezone identifier string or an instance of DateTimeZone class.
Formatting
TimeHelper::convert($serverTime, $timezone = NULL)
482
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
TimeHelper::convertSpecifiers($format, $time = NULL)
Return type string
Converts a string representing the format for the function strftime and returns a windows safe and
i18n aware format.
TimeHelper::dayAsSql($dateString, $field_name, $timezone = NULL)
Return type string
Creates a string in the same format as daysAsSql but only needs a single date object:
// called via TimeHelper
echo $this->Time->dayAsSql(Aug 22, 2011, modified);
// (modified >= 2011-08-22 00:00:00) AND
// (modified <= 2011-08-22 23:59:59)
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::dayAsSql(Aug 22, 2011, modified);
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::daysAsSql($begin, $end, $fieldName, $timezone = NULL)
Return type string
Returns a string in the format ($field_name >= 2008-01-21 00:00:00) AND ($field_name <= 200801-25 23:59:59). This is handy if you need to search for records between two dates inclusively:
// called via TimeHelper
echo $this->Time->daysAsSql(Aug 22, 2011, Aug 25, 2011, created);
// (created >= 2011-08-22 00:00:00) AND
// (created <= 2011-08-25 23:59:59)
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::daysAsSql(Aug 22, 2011, Aug 25, 2011, created);
General Purpose
483
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::format($date, $format = NULL, $default = false, $timezone = NULL)
Return type string
Will return a string formatted to the given format using the PHP strftime() formatting options7 :
// called via TimeHelper
echo $this->Time->format(2011-08-22 11:53:00, %B %e, %Y %H:%M %p);
// August 22, 2011 11:53 AM
echo $this->Time->format(+2 days, %c);
// 2 days from now formatted as Sun, 13 Nov 2011 03:36:10 AM EET
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::format(2011-08-22 11:53:00, %B %e, %Y %H:%M %p);
echo CakeTime::format(+2 days, %c);
You can also provide the date/time as the first argument. When doing this you should use strftime
compatible formatting. This call signature allows you to leverage locale aware date formatting which
is not possible using date() compatible formatting:
// called via TimeHelper
echo $this->Time->format(2012-01-13, %d-%m-%Y, invalid);
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::format(2011-08-22, %d-%m-%Y);
Changed in version 2.2: $format and $date parameters are in opposite order as used in 2.1 and below. $timezone parameter replaces $userOffset parameter used in 2.1 and below. $default
parameter replaces $invalid parameter used in 2.1 and below.
New in version 2.2: $date parameter now also accepts a DateTime object.
TimeHelper::fromString($dateString, $timezone = NULL)
Return type string
Takes a string and uses strtotime8 to convert it into a date integer:
// called via TimeHelper
echo $this->Time->fromString(Aug 22, 2011);
// 1313971200
echo $this->Time->fromString(+1 days);
// 1321074066 (+1 day from current date)
7
8
http://www.php.net/manual/en/function.strftime.php
http://us.php.net/manual/en/function.date.php
484
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::fromString(Aug 22, 2011);
echo CakeTime::fromString(+1 days);
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::gmt($dateString = NULL)
Return type integer
Will return the date as an integer set to Greenwich Mean Time (GMT).
// called via TimeHelper
echo $this->Time->gmt(Aug 22, 2011);
// 1313971200
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::gmt(Aug 22, 2011);
General Purpose
485
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::serverOffset()
Return type integer
Returns servers offset from GMT in seconds.
TimeHelper::timeAgoInWords($dateString, $options = array())
Return type string
Will take a datetime string (anything that is parsable by PHPs strtotime() function or MySQLs datetime format) and convert it into a friendly word format like, 3 weeks, 3 days ago:
// called via TimeHelper
echo $this->Time->timeAgoInWords(Aug 22, 2011);
// on 22/8/11
// on August 22nd, 2011
echo $this->Time->timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y)
);
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::timeAgoInWords(Aug 22, 2011);
echo CakeTime::timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y)
);
Use the end option to determine the cutoff point to no longer will use words; default +1 month:
// called via TimeHelper
echo $this->Time->timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y, end => +1 year)
);
// On Nov 10th, 2011 it would display: 2 months, 2 weeks, 6 days ago
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::timeAgoInWords(
486
Use the accuracy option to determine how precise the output should be. You can use this to limit the
output:
// If $timestamp is 1 month, 1 week, 5 days and 6 hours ago
echo CakeTime::timeAgoInWords($timestamp, array(
accuracy => array(month => month),
end => 1 year
));
// Outputs 1 month ago
New in version 2.2: $dateString parameter now also accepts a DateTime object.
General Purpose
487
New in version 2.4: The new option parameters relativeString (defaults to %s ago) and
absoluteString (defaults to on %s) to allow customization of the resulting output string are
now available.
TimeHelper::toRSS($dateString, $timezone = NULL)
Return type string
Will return a date string in the RSS format Sat, 12 Jan 2008 00:00:00 -0500
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::toUnix($dateString, $timezone = NULL)
Return type integer
A wrapper for fromString.
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::toServer($dateString, $timezone = NULL, $format = Y-m-d H:i:s)
Return type mixed
New in version 2.2: Returns a formatted date in servers timezone.
TimeHelper::timezone($timezone = NULL)
Return type DateTimeZone
New in version 2.2: Returns a timezone object from a string or the users timezone object. If the
function is called without a parameter it tries to get timezone from Config.timezone configuration
variable.
TimeHelper::listTimezones($filter = null, $country = null, $group = true)
Return type array
New in version 2.2: Returns a list of timezone identifiers.
Testing Time
TimeHelper::isToday($dateString, $timezone = NULL)
TimeHelper::isThisWeek($dateString, $timezone = NULL)
TimeHelper::isThisMonth($dateString, $timezone = NULL)
TimeHelper::isThisYear($dateString, $timezone = NULL)
TimeHelper::wasYesterday($dateString, $timezone = NULL)
TimeHelper::isTomorrow($dateString, $timezone = NULL)
488
wasWithinLast takes a time interval which is a string in the format 3 months and accepts a time
interval of seconds, minutes, hours, days, weeks, months and years (plural and not). If a time interval
is not recognized (for example, if it is mistyped) then it will default to days.
Using and Configuring Helpers You enable helpers in CakePHP by making a controller aware of them.
Each controller has a $helpers property that lists the helpers to be made available in the view. To enable
a helper in your view, add the name of the helper to the controllers $helpers array:
class BakeriesController extends AppController {
public $helpers = array(Form, Html, Js, Time);
}
Adding helpers from plugins uses the plugin syntax used elsewhere in CakePHP:
class BakeriesController extends AppController {
public $helpers = array(Blog.Comment);
}
You can also add helpers from within an action, so they will only be available to that action and not to the
other actions in the controller. This saves processing power for the other actions that do not use the helper
and helps keep the controller better organized:
class BakeriesController extends AppController {
public function bake() {
$this->helpers[] = Time;
}
public function mix() {
// The Time helper is not loaded here and thus not available
}
}
General Purpose
489
If you need to enable a helper for all controllers, add the name of the helper to the $helpers array in
/app/Controller/AppController.php (or create it if not present). Remember to include the
default Html and Form helpers:
class AppController extends Controller {
public $helpers = array(Form, Html, Js, Time);
}
You can pass options to helpers. These options can be used to set attribute values or modify behavior of a
helper:
class AwesomeHelper extends AppHelper {
public function __construct(View $view, $settings = array()) {
parent::__construct($view, $settings);
debug($settings);
}
}
class AwesomeController extends AppController {
public $helpers = array(Awesome => array(option1 => value1));
}
As of 2.3, the options are merged with the Helper::$settings property of the helper.
One common setting to use is the className option, which allows you to create aliased helpers in your
views. This feature is useful when you want to replace $this->Html or another common Helper reference
with a custom implementation:
// app/Controller/PostsController.php
class PostsController extends AppController {
public $helpers = array(
Html => array(
className => MyHtml
)
);
}
// app/View/Helper/MyHtmlHelper.php
App::uses(HtmlHelper, View/Helper);
class MyHtmlHelper extends HtmlHelper {
// Add your code to override the core HtmlHelper
}
490
parent::beforeRender();
$this->helpers[CustomStuff] = $this->_getCustomStuffSettings();
}
}
Using Helpers Once youve configured which helpers you want to use in your controller, each helper is
exposed as a public property in the view. For example, if you were using the HtmlHelper you would be
able to access it by doing the following:
echo $this->Html->css(styles);
The above would call the css method on the HtmlHelper. You can access any loaded helper using
$this->{$helperName}. There may come a time where you need to dynamically load a helper from
inside a view. You can use the views HelperCollection to do this:
$mediaHelper = $this->Helpers->load(Media, $mediaSettings);
The HelperCollection is a collection and supports the collection API used elsewhere in CakePHP.
Callback methods Helpers feature several callbacks that allow you to augment the view rendering process. See the Helper API and the Collections documentation for more information.
Creating Helpers If a core helper (or one showcased on GitHub or in the Bakery) doesnt fit your needs,
helpers are easy to create.
Lets 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 into CakePHPs existing helper
structure, youll need to create a new class in /app/View/Helper. Lets call our helper LinkHelper. The
actual PHP class file would look something like this:
/* /app/View/Helper/LinkHelper.php */
App::uses(AppHelper, View/Helper);
class LinkHelper extends AppHelper {
public function makeEdit($title, $url) {
// Logic to create specially formatted link goes here...
}
}
Note: Helpers must extend either AppHelper or Helper or implement all the callbacks in the Helper
API.
Including other Helpers You may wish to use some functionality already existing in another helper. To
do so, you can specify helpers you wish to use with a $helpers array, formatted just as you would in a
controller:
General Purpose
491
Using your Helper Once youve created your helper and placed it in /app/View/Helper/, youll be
able to include it in your controllers using the special variable $helpers:
class PostsController extends AppController {
public $helpers = array(Link);
}
Once your controller has been made aware of this new class, you can use it in your views by accessing an
object named after the helper:
<!-- make a link using the new helper -->
<?php echo $this->Link->makeEdit(Change this Recipe, /recipes/edit/5); ?>
Creating Functionality for All Helpers All helpers extend a special class, AppHelper (just like models
extend AppModel and controllers extend AppController). To create functionality that would be available to
all helpers, create /app/View/Helper/AppHelper.php:
App::uses(Helper, View);
class AppHelper extends Helper {
public function customMethod() {
}
}
Helper API
class Helper
The base class for Helpers. It provides a number of utility methods and features for loading other
helpers.
Helper::webroot($file)
Resolve a file name to the webroot of the application. If a theme is active and the file exists in the
current themes webroot, the path to the themed file will be returned.
Helper::url($url, $full = false)
Generates an HTML escaped URL, delegates to Router::url().
492
Loading objects on every kind of collection can be done using the load() method:
$this->Prg = $this->Components->load(Prg);
$this->Prg->process();
When loading a component, if the component is not currently loaded into the collection, a new instance
will be created. If the component is already loaded, another instance will not be created. When loading
components, you can also provide additional configuration for them:
General Purpose
493
Any keys & values provided will be passed to the Components constructor. The one exception to this rule
is className. ClassName is a special key that is used to alias objects in a collection. This allows you
to have component names that do not reflect the classnames, which can be helpful when extending core
components:
$this->Auth = $this->Components->load(
Auth,
array(className => MyCustomAuth)
);
$this->Auth->user(); // Actually using MyCustomAuth::user();
The inverse of loading an object, is unloading it. Unloaded objects are removed from memory, and will not
have additional callbacks triggered on them:
$this->Components->unload(Cookie);
$this->Cookie->read(); // Fatal error.
Triggering callbacks
Callbacks are supported by collection objects. When a collection has a callback triggered, that method will
be called on all enabled objects in the collection. You can pass parameters to the callback loop as well:
$this->Behaviors->trigger(afterFind, array($this, $results, $primary));
In the above $this would be passed as the first argument to every behaviors afterFind method. There are
several options that can be used to control how callbacks are fired:
breakOn Set to the value or values you want the callback propagation to stop on. Can either be a
scalar value, or an array of values to break on. Defaults to false.
break Set to true to enabled breaking. When a trigger is broken, the last returned value will be returned. If used in combination with collectReturn the collected results will be returned. Defaults
to false.
collectReturn Set to true to collect the return of each object into an array. This array of return
values will be returned from the trigger() call. Defaults to false.
triggerDisabled Will trigger the callback on all objects in the collection even the non-enabled
objects. Defaults to false.
modParams Allows each object the callback gets called on to modify the parameters to the next
object. Setting modParams to an integer value will allow you to modify the parameter with that index.
Any non-null value will modify the parameter index indicated. Defaults to false.
Canceling a callback loop Using the break and breakOn options you can cancel a callback loop
midway similar to stopping event propagation in JavaScript:
494
$this->Behaviors->trigger(
beforeFind,
array($this, $query),
array(break => true, breakOn => false)
);
In the above example, if any behavior returns false from its beforeFind method, no further callbacks will
be called. In addition, the return of trigger() will be false.
Enabling and disabling objects
Once an object is loaded into a collection you may need to disable it. Disabling an object in a collection
prevents future callbacks from being fired on that object unless the triggerDisabled option is used:
// Disable the HtmlHelper
$this->Helpers->disable(Html);
// Re-enable the helper later on
$this->Helpers->enable(Html);
Disabled objects can still have their normal methods and properties used. The primary difference between an
enabled and disabled object is with regards to callbacks. You can interrogate a collection about the enabled
objects, or check if a specific object is still enabled using enabled():
// Check whether or not a specific helper is enabled.
$this->Helpers->enabled(Html);
// $enabled will contain an array of helper currently enabled.
$enabled = $this->Helpers->enabled();
You can prioritize the triggering object callbacks similar to event callbacks. The handling of priority values
and order of triggering is the same as explained here. Heres how you can specify priority at declaration
time:
class SomeController {
public $components = array(
Foo, //Foo gets default priority 10
// Bars callbacks are triggered before Foos
Bar => array(priority => 9)
);
public $helpers = array(
// Caches callbacks will be triggered last
Cache => array(priority => 12),
Asset,
Utility //Utility has priority 10 same as Asset and its callbacks
//are triggered after Assets
);
General Purpose
495
class Post {
public $actsAs = array(
DoFirst => array(priority => 1),
Media
);
}
When dynamically loading objects to a collection you can specify the priority like this:
$this->MyComponent = $this->Components->load(
MyComponent,
array(priority => 9)
);
You can also change priorities at run time using the ObjectCollection::setPriority() function:
//For a single object
$this->Components->setPriority(Foo, 2);
//For multiple objects
$this->Behaviors->setPriority(array(Object1 => 8, Object2 => 9));
Behaviors
Behaviors add extra functionality to your models. CakePHP comes with a number of built-in behaviors such
as TreeBehavior and ContainableBehavior.
To learn about creating and using behaviors, read the section on Behaviors.
Behaviors
Behaviors add extra functionality to your models. CakePHP comes with a number of built-in behaviors such
as TreeBehavior and ContainableBehavior.
To learn about creating and using behaviors, read the section on Behaviors.
ACL
class AclBehavior
The Acl behavior provides a way to seamlessly integrate a model with your ACL system. It can create both
AROs or ACOs transparently.
To use the new behavior, you can add it to the $actsAs property of your model. When adding it to the actsAs
array you choose to make the related Acl entry an ARO or an ACO. The default is to create ACOs:
496
This would attach the Acl behavior in ARO mode. To join the ACL behavior in ACO mode use:
class Post extends AppModel {
public $actsAs = array(Acl => array(type => controlled));
}
For User and Group models it is common to have both ACO and ARO nodes, to achieve this use:
class User extends AppModel {
public $actsAs = array(Acl => array(type => both));
}
You can also attach the behavior on the fly like so:
$this->Post->Behaviors->load(Acl, array(type => controlled));
Changed in version 2.1: You can now safely attach AclBehavior to AppModel. Aco, Aro and AclNode now
extend Model instead of AppModel, which would cause an infinite loop. If your application depends on
having those models to extend AppModel for some reason, then copy AclNode to your application and have
it extend AppModel again.
Using the AclBehavior
Most of the AclBehavior works transparently on your Models afterSave(). However, using it requires that
your Model has a parentNode() method defined. This is used by the AclBehavior to determine parent->child
relationships. A models parentNode() method must return null or return a parent Model reference:
public function parentNode() {
return null;
}
If you want to set an ACO or ARO node as the parent for your Model, parentNode() must return the alias of
the ACO or ARO node:
public function parentNode() {
return root_node;
}
A more complete example. Using an example User Model, where User belongsTo Group:
public function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
$data = $this->data;
if (empty($this->data)) {
$data = $this->read();
}
Behaviors
497
if (!$data[User][group_id]) {
return null;
}
return array(Group => array(id => $data[User][group_id]));
}
In the above example the return is an array that looks similar to the results of a model find. It is important
to have the id value set or the parentNode relation will fail. The AclBehavior uses this data to construct its
tree structure.
node()
The AclBehavior also allows you to retrieve the Acl node associated with a model record. After setting
$model->id. You can use $model->node() to retrieve the associated Acl node.
You can also retrieve the Acl Node for any row, by passing in a data array:
$this->User->id = 1;
$node = $this->User->node();
$user = array(User => array(
id => 1
));
$node = $this->User->node($user);
Containable
class ContainableBehavior
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. Since Containable only modifies existing
498
relationships it will not allow you to restrict results by distant associations. Instead you should refer to
Joining tables.
To use the new behavior, you can add it to the $actsAs property of your model:
class Post extends AppModel {
public $actsAs = array(Containable);
}
Using Containable
To see how Containable works, lets look at a few examples. First, well start off with a find() call on a
model named Post. Lets say that Post hasMany Comment, and Post hasAndBelongsToMany Tag.
The amount of data fetched in a normal find() call is rather extensive:
debug($this->Post->find(all));
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[author] => Daniel
[email] => [email protected]
[website] => http://example.com
[comment] => First comment
[created] => 2008-05-18 00:00:00
)
[1] => Array
(
[id] => 2
[post_id] => 1
[author] => Sam
[email] => [email protected]
[website] => http://example.net
[comment] => Second comment
[created] => 2008-05-18 00:00:00
)
Behaviors
499
)
[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:
$this->Post->contain();
$this->Post->find(all);
You can also invoke Containables magic from inside the find() call:
$this->Post->find(all, array(contain => false));
Having done that, you end up with something a lot more concise:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[title] => Second article
[content] => bbb
[created] => 2008-05-19 00:00:00
)
)
500
This sort of help isnt new: in fact, you can do that without the ContainableBehavior doing something
like this:
$this->Post->recursive = -1;
$this->Post->find(all);
Containable really shines when you have complex associations, and you want to pare down things that sit
at the same level. The models $recursive property is helpful if you want to hack off an entire level of
recursion, but not when you want to pick and choose what to keep at each level. Lets see how it works by
using the contain() method.
The contain methods first argument accepts the name, or an array of names, of the models to keep in the
find operation. If we wanted to fetch all posts and their related tags (without any comment information),
wed try something like this:
$this->Post->contain(Tag);
$this->Post->find(all);
Without Containable, youd end up needing to use the unbindModel() method of the model, multiple
times if youre paring off multiple models. Containable creates a cleaner way to accomplish this same task.
Containing deeper associations
Containable also goes a step deeper: you can filter the data of the associated models. If you look at the
results of the original find() call, notice the author field in the Comment model. If you are interested in
the posts and the names of the comment authors and nothing else you could do something like the
following:
$this->Post->contain(Comment.author);
$this->Post->find(all);
// or..
$this->Post->find(all, array(contain => Comment.author));
Here, weve told Containable to give us our post information, and just the author field of the associated
Comment model. The output of the find call might look something like this:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
Behaviors
501
(
[0] => Array
(
[author] => Daniel
[post_id] => 1
)
[1] => Array
(
[author] => Sam
[post_id] => 1
)
)
)
[1] => Array
(...
As you can see, the Comment arrays only contain the author field (plus the post_id which is needed by
CakePHP to map the results).
You can also filter the associated Comment data by specifying a condition:
$this->Post->contain(Comment.author = "Daniel");
$this->Post->find(all);
//or...
$this->Post->find(all, array(contain => Comment.author = "Daniel"));
This gives us a result that gives us posts with comments authored by Daniel:
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[author] => Daniel
[email] => [email protected]
[website] => http://example.com
[comment] => First comment
[created] => 2008-05-18 00:00:00
)
)
)
502
There is an important caveat to using Containable when filtering on a deeper association. In the previous
example, assume you had 3 posts in your database and Daniel had commented on 2 of those posts. The
operation $this->Post->find(all, array(contain => Comment.author = Daniel)); would return ALL
3 posts, not just the 2 posts that Daniel had commented on. It wont return all comments however, just
comments by Daniel.
[0] => Array
(
[Post] => Array
(
[id] => 1
[title] => First article
[content] => aaa
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
[0] => Array
(
[id] => 1
[post_id] => 1
[author] => Daniel
[email] => [email protected]
[website] => http://example.com
[comment] => First comment
[created] => 2008-05-18 00:00:00
)
)
)
[1] => Array
(
[Post] => Array
(
[id] => 2
[title] => Second article
[content] => bbb
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
)
)
[2] => Array
(
[Post] => Array
(
[id] => 3
[title] => Third article
[content] => ccc
[created] => 2008-05-18 00:00:00
)
[Comment] => Array
(
Behaviors
503
If you want to filter the posts by the comments, so that posts without a comment by Daniel wont be returned,
the easiest way is to find all the comments by Daniel and contain the Posts.
$this->Comment->find(all, array(
conditions => Comment.author = "Daniel",
contain => Post
));
Heres an example of using the ContainableBehavior when youve got deep and complex model
relationships.
Lets consider the following model associations:
User->Profile
User->Account->AccountSummary
User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes
User->Post->Tag
504
)
)
),
Tag => array(
conditions => array(Tag.name LIKE => %happy%)
)
)
)
));
Keep in mind that contain key is only used once in the main model, you dont need to use contain again
for related models.
Note: When using 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.
ContainableBehavior options
The ContainableBehavior has a number of options that can be set when the Behavior is attached to a
model. The settings allow you to 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.
order: (string, optional) the order of how the contained elements are sorted.
From the previous example, this is an example of how to force the posts to be ordered by the date when they
were last updated:
$this->User->find(all, array(
contain => array(
Profile,
Post => array(
order => Post.updated DESC
)
)
));
You can change ContainableBehavior settings at run time by reattaching the behavior as seen in Behaviors
(Using Behaviors).
Behaviors
505
ContainableBehavior can sometimes cause issues with other behaviors or queries that use aggregate functions and/or GROUP BY statements. If you get invalid SQL errors due to mixing of aggregate and nonaggregate fields, try disabling the autoFields setting.
$this->Post->Behaviors->load(Containable, array(autoFields => false));
Using Containable with pagination By including the contain parameter in the $paginate property
it will apply to both the find(count) and the find(all) done on the model.
See the section Using Containable for further details.
Heres an example of how to contain associations when paginating:
$this->paginate[User] = array(
contain => array(Profile, Account),
order => User.username
);
$users = $this->paginate(User);
Note: If you contained the associations through the model instead, it will not honor Containables recursive
option. So if you set recursive to -1 for example for the model, it wont work:
$this->User->recursive = -1;
$this->User->contain(array(Profile, Account));
$users = $this->paginate(User);
Translate
class TranslateBehavior
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.
Initializing the i18n Database Tables
You can either use the CakePHP console or you can manually create it. It is advised to use the console for
this, because it might happen that the layout changes in future versions of CakePHP. Sticking to the console
will make sure that you have the correct layout.
./cake i18n
Select [I] which will run the i18n database initialization script. You will be asked if you want to drop any
existing and if you want to create it. Answer with yes if you are sure there is no i18n table already, and
answer with yes again to create the table.
506
Add it to your model by using the $actsAs property like in the following example.
class Post extends AppModel {
public $actsAs = array(
Translate
);
}
This will do nothing yet, because it expects a couple of options before it begins to work. You need to define
which fields of the current model should be tracked in the translation table weve created in the first step.
Defining the Fields
You can set the fields by simply extending the Translate value with another array, like so:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
fieldOne, fieldTwo, and_so_on
)
);
}
After you have done that (for example putting title as one of the fields) you already finished the basic
setup. Great! According to our current example the model should now look something like this:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title
)
);
}
When defining fields for TranslateBehavior to translate, be sure to omit those fields from the translated
models schema. If you leave the fields in, there can be issues when retrieving data with fallback locales.
Note: If all the fields in your model are translated be sure to add created and modified columns to
your table. CakePHP requires at least one non primary key field before it will save a record.
Conclusion
From now on each record update/creation will cause TranslateBehavior to copy the value of title to the
translation table (default: i18n) along with the current locale. A locale is the identifier of the language, so to
speak.
Behaviors
507
By default the TranslateBehavior will automatically fetch and add in data based on the current locale.
The current locale is read from Configure::read(Config.language) which is assigned by
the L10n class. You can override this default on the fly using $Model->locale.
Retrieve translated fields in a specific locale By setting $Model->locale you can read translations
for a specific locale:
// Read the spanish locale data.
$this->Post->locale = es;
$results = $this->Post->find(first, array(
conditions => array(Post.id => $id)
));
// $results will contain the spanish translation.
Retrieve all translation records for a field If you want to have all translation records attached to the
current model record you simply extend the field array in your behavior setup as shown below. The naming
is completely up to you.
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title => titleTranslation
)
);
}
With this setup the result of $this->Post->find() should look something like this:
Array
(
[Post] => Array
(
[id] => 1
[title] => Beispiel Eintrag
[body] => lorem ipsum...
[locale] => de_de
)
[titleTranslation] => Array
(
[0] => Array
(
[id] => 1
[locale] => en_us
[model] => Post
[foreign_key] => 1
[field] => title
[content] => Example entry
)
508
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 wont be translated because triggering callbacks on associated models is currently not supported.
Using the bindTranslation method You can also retrieve all translations, only when you need them, using
the bindTranslation method
TranslateBehavior::bindTranslation($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.
$this->Post->bindTranslation(array(title => titleTranslation));
// need at least recursive 1 for this to work.
$this->Post->find(all, array(recursive => 1));
With this setup the result of your find() should look something like this:
Array
(
[Post] => Array
(
[id] => 1
[title] => Beispiel Eintrag
[body] => lorem ipsum...
[locale] => de_de
)
[titleTranslation] => Array
(
[0] => Array
(
[id] => 1
[locale] => en_us
[model] => Post
[foreign_key] => 1
Behaviors
509
You can force the model which is using the TranslateBehavior to save in a language other than the one
detected.
To tell a model in what language the content is going to be you simply change the value of the $locale
property on the model before you save the data to the database. You can do that either in your controller or
you can define it directly in the model.
Example A: In your controller:
class PostsController extends AppController {
public function add() {
if (!empty($this->request->data)) {
// we are going to save the german version
$this->Post->locale = de_de;
$this->Post->create();
if ($this->Post->save($this->request->data)) {
return $this->redirect(array(action => index));
}
}
}
}
510
If you expect a lot entries you probably wonder how to deal with a rapidly growing database table. There are
two properties introduced by TranslateBehavior that allow to specify which Model to bind as the model
containing the translations.
These are $translateModel and $translateTable.
Lets say we want to save our translations for all posts in the table post_i18ns instead of the default i18n
table. To do so you need to setup your model like this:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title
)
);
// Use a different model (and table)
public $translateModel = PostI18n;
}
Note: It is important that you to pluralize the table. It is now a usual model and can be treated as such and
thus comes with the conventions involved. The table schema itself must be identical with the one generated
by the CakePHP console script. To make sure it fits one could just initialize a empty i18n table using the
console and rename the table afterwards.
Create the TranslateModel For this to work you need to create the actual model file in your models
folder. Reason is that there is no property to set the displayField directly in the model using this behavior
yet.
Make sure that you change the $displayField to field.
class PostI18n extends AppModel {
public $displayField = field; // important
}
// filename: PostI18n.php
Thats all it takes. You can also add all other model stuff here like $useTable. But for better consistency we could do that in the model which actually uses this translation model. This is where the optional
$translateTable comes into play.
Behaviors
511
Changing the Table If you want to change the name of the table you simply define $translateTable in
your model, like so:
class Post extends AppModel {
public $actsAs = array(
Translate => array(
title
)
);
// Use a different model
public $translateModel = PostI18n;
// Use a different table for translateModel
public $translateTable = post_translations;
}
To use the tree behavior, your database table needs 3 fields as listed below (all are ints):
parent - default fieldname is parent_id, to store the id of the parent object
left - default fieldname is lft, to store the lft value of the current row.
right - default fieldname is rght, to store the rght value of the current row.
If you are familiar with MPTT logic you may wonder why a parent field exists - quite simply its easier to
do certain tasks if a direct parent link is stored on the database - such as finding direct children.
Note: The parent field must be able to have a NULL value! It might seem to work if you just give the
top elements a parent value of zero, but reordering the tree (and possible other operations) will fail.
9
http://www.sitepoint.com/hierarchical-data-database-2/
512
Basic Usage
The tree behavior has a lot packed into it, but lets start with a simple example - create the following database
table and put some data in it:
CREATE TABLE categories (
id INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT,
parent_id INTEGER(10) DEFAULT NULL,
lft INTEGER(10) DEFAULT NULL,
rght INTEGER(10) DEFAULT NULL,
name VARCHAR(255) DEFAULT ,
PRIMARY KEY (id)
);
INSERT INTO
categories (id, name, parent_id,
VALUES
(1, My Categories, NULL, 1, 30);
INSERT INTO
categories (id, name, parent_id,
VALUES
(2, Fun, 1, 2, 15);
INSERT INTO
categories (id, name, parent_id,
VALUES
(3, Sport, 2, 3, 8);
INSERT INTO
categories (id, name, parent_id,
VALUES
(4, Surfing, 3, 4, 5);
INSERT INTO
categories (id, name, parent_id,
VALUES
(5, Extreme knitting, 3, 6, 7);
INSERT INTO
categories (id, name, parent_id,
VALUES
(6, Friends, 2, 9, 14);
INSERT INTO
categories (id, name, parent_id,
VALUES
(7, Gerald, 6, 10, 11);
INSERT INTO
categories (id, name, parent_id,
VALUES
(8, Gwendolyn, 6, 12, 13);
INSERT INTO
categories (id, name, parent_id,
VALUES
(9, Work, 1, 16, 29);
INSERT INTO
categories (id, name, parent_id,
VALUES
Behaviors
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
513
lft, rght)
lft, rght)
lft, rght)
lft, rght)
lft, rght)
For the purpose of checking that everything is setup correctly, we can create a test method and output the
contents of our category tree to see what it looks like. With a simple controller:
class CategoriesController extends AppController {
public function index() {
$data = $this->Category->generateTreeList(
null,
null,
null,
);
debug($data); die;
}
}
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
514
* Annual
* Status
* Trips - National - International
Adding data In the previous section, we used existing data and checked that it looked hierarchal via the
method generateTreeList. However, usually you would add your data in exactly the same way as you
would for any model. For example:
// pseudo controller code
$data[Category][parent_id] = 3;
$data[Category][name] = Skating;
$this->Category->save($data);
When using the tree behavior its not necessary to do any more than set the parent_id, and the tree behavior
will take care of the rest. If you dont set the parent_id, the tree behavior will add to the tree making your
new addition a new top level entry:
// pseudo controller code
$data = array();
$data[Category][name] = Other People\s Categories;
$this->Category->save($data);
Running the above two code snippets would alter your tree as follows:
My Categories - Fun
Sport - Surfing - Extreme knitting - Skating New
Friends - Gerald - Gwendolyn
Work - Reports
* Annual
* Status
* Trips - National - International
Other Peoples Categories New
Modifying data Modifying data is as transparent as adding new data. If you modify something, but do
not change the parent_id field - the structure of your data will remain unchanged. For example:
// pseudo controller code
$this->Category->id = 5; // id of Extreme knitting
$this->Category->save(array(name => Extreme fishing));
The above code did not affect the parent_id field - even if the parent_id is included in the data that is passed
to save if the value doesnt change, neither does the data structure. Therefore the tree of data would now
look like:
My Categories
Behaviors
515
Fun
Sport - Surfing - Extreme fishing Updated - Skating
Friends - Gerald - Gwendolyn
Work
Reports - Annual - Status
Trips - National - International
Other Peoples Categories
Moving data around in your tree is also a simple affair. Lets say that Extreme fishing does not belong under
Sport, but instead should be located under Other Peoples Categories. With the following code:
// pseudo controller code
$this->Category->id = 5; // id of Extreme fishing
$newParentId = $this->Category->field(
id,
array(name => Other People\s Categories)
);
$this->Category->save(array(parent_id => $newParentId));
516
My Categories
Fun - Sport
Surfing
Skating
Friends - Gerald - Gwendolyn
Work - Trips
National
International
Other Peoples Categories
Extreme fishing
Querying and using your data Using and manipulating hierarchical data can be a tricky business. In
addition to the core find methods, with the tree behavior there are a few more tree-orientated permutations
at your disposal.
Note: Most tree behavior methods return and rely on data being sorted by the lft 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.
class TreeBehavior
children($id = null, $direct = false, $fields = null, $order = null, $limit = null, $page = 1,
$recursive = null)
Parameters
$id The ID of the record to look up
$direct Set to true to return only the direct descendants
$fields Single string field name or array of fields to include in the return
$order SQL string of ORDER BY conditions
$limit SQL LIMIT statement
$page for accessing paged results
$recursive Number of levels deep for recursive associated Models
The children method takes the primary key value (the id) of a row and returns the children, by
default in the order they appear in the tree. The second optional parameter defines whether or not only
direct children should be returned. Using the example data from the previous section:
Behaviors
517
Output:
array(
[1] =>
[2] =>
[3] =>
[4] =>
[16] =>
518
"My Categories",
"_Fun",
"__Sport",
"___Surfing",
"___Skating",
[6] =>
[7] =>
[8] =>
[9] =>
[13] =>
[14] =>
[15] =>
[17] =>
[5] =>
"__Friends",
"___Gerald",
"___Gwendolyn",
"_Work",
"__Trips",
"___National",
"___International",
"Other Peoples Categories",
"_Extreme fishing"
getParentNode()
This convenience function will, as the name suggests, return the parent node for any node, or false if
the node has no parent (its the root node). For example:
$parent = $this->Category->getParentNode(2); //<- id for fun
// $parent contains All categories
Behaviors
519
Advanced Usage
The tree behavior doesnt only work in the background, there are a number of specific methods defined in the behavior to cater for all your hierarchical data needs, and any unexpected
problems that might arise in the process.
TreeBehavior::moveDown()
Used to move a single node down the tree. You need to provide the ID of the element to be
moved and a positive number of how many positions the node should be moved down. All
child nodes for the 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:
public function movedown($id = null, $delta = null) {
$this->Category->id = $id;
if (!$this->Category->exists()) {
throw new NotFoundException(__(Invalid category));
}
if ($delta > 0) {
$this->Category->moveDown($this->Category->id, abs($delta));
} else {
$this->Session->setFlash(
Please provide the number of positions the field should be .
moved down.
);
}
return $this->redirect(array(action => index));
}
For example, if youd like to move the Sport ( id of 3 ) category one position down, you would
request: /categories/movedown/3/1.
TreeBehavior::moveUp()
Used to move a single node up the tree. You need to provide the ID of the element to be moved
and a positive number of how many positions the node should be moved up. All child nodes
will also be moved.
Heres an example of a controller action (in a controller named Categories) that moves a node
up the tree:
public function moveup($id = null, $delta = null) {
$this->Category->id = $id;
if (!$this->Category->exists()) {
throw new NotFoundException(__(Invalid category));
}
if ($delta > 0) {
$this->Category->moveUp($this->Category->id, abs($delta));
} else {
520
$this->Session->setFlash(
Please provide a number of positions the category should .
be moved up.
);
}
return $this->redirect(array(action => index));
}
For example, if you would like to move the category Gwendolyn ( id of 8 ) up one position you
would request /categories/moveup/8/1. Now the order of Friends will be Gwendolyn, Gerald.
TreeBehavior::removeFromTree($id = null, $delete = false)
Using this method will either delete or move a node but retain its sub-tree, which will be reparented one level higher. It offers more control than delete, which for a model using the tree
behavior will remove the specified node and all of its children.
Taking the following tree as a starting point:
My Categories - Fun
Sport - Surfing - Extreme knitting - Skating
Running the following code with the id for Sport:
$this->Node->removeFromTree($id);
521
Note: If you have saved your data or made other operations on the model, you might want
to set $model->id = null before calling reorder. Otherwise only the current node and
its children will be reordered.
Data Integrity
Due to the nature of complex self referential data structures such as trees and linked lists, they
can occasionally become broken by a careless call. Take heart, for all is not lost! The Tree
Behavior contains several previously undocumented features designed to recover from such
situations.
TreeBehavior::recover($mode = parent, $missingParentAction = null)
The mode parameter is used to specify the source of info that is valid/correct. The opposite
source of data will be populated based upon that source of info. E.g. if the MPTT 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 missingParentAction parameter 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_ids 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
Example:
522
// Rebuild all the left and right fields based on the parent_id
$this->Category->recover();
// or
$this->Category->recover(parent);
// Rebuild all the parent_ids based on the lft and rght fields
$this->Category->recover(tree);
TreeBehavior::reorder($options = array())
Reorders the nodes (and child nodes) of the tree according to the 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:
id - only reorder nodes below this node.
field - field to use for sorting, default is the displayField for the model.
order - ASC for ascending, DESC for descending sort.
verify - whether or not to verify the tree prior to resorting.
$options is used to pass all extra parameters, and has the following possible keys by default,
all of which are optional:
array(
id => null,
field => $model->displayField,
order => ASC,
verify => true
)
TreeBehavior::verify()
Returns true if the tree is valid otherwise an array of errors, with fields for type, incorrect
index and message.
Each record in the output array is an array of the form (type, id, message)
type is either index or node
id is the id of the erroneous node.
message depends on the error
Example Use:
$this->Category->verify();
Example output:
Array
(
[0] => Array
(
Behaviors
523
node
2
The parent node 999 doesn t exist
index
123
missing
node
163
left greater than right
Components
CakePHP has a selection of components to help take care of basic tasks in your controllers. See the section
on Components for how to configure and use components.
Components
CakePHP has a selection of components to help take care of basic tasks in your controllers. See the section
on Components for how to configure and use components.
Pagination
class PaginatorComponent(ComponentCollection $collection, array $settings = array())
One of the main obstacles of creating flexible and user-friendly web applications is designing an intuitive
user interface. 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.
524
Pagination in CakePHP is offered by a Component in the controller, to make building paginated queries
easier. In the View PaginatorHelper is used to make the generation of pagination links & buttons
simple.
Query Setup
In the controller, we start by defining the query conditions pagination will use by default in the $paginate
controller variable. These conditions, serve as the basis of your pagination queries. They are augmented by
the sort, direction limit, and page parameters passed in from the URL. It is important to note here that the
order key must be defined in an array structure like below:
class PostsController extends AppController {
public $components = array(Paginator);
public $paginate = array(
limit => 25,
order => array(
Post.title => asc
)
);
}
Other keys that can be included in the $paginate array are similar to the parameters of the
Model->find(all) method, that is: conditions, fields, order, limit, page, contain,
joins, and recursive. In addition to the aforementioned keys, any additional keys will also
be passed directly to the model find methods. This makes it very simple to use behaviors like
ContainableBehavior with pagination:
class RecipesController extends AppController {
public $components = array(Paginator);
public $paginate = array(
limit => 25,
contain => array(Article)
Components
525
);
}
In addition to defining general pagination values, you can define more than one set of pagination defaults in
the controller, you just name the keys of the array after the model you wish to configure:
class PostsController extends AppController {
public $paginate = array(
Post => array (...),
Author => array (...)
);
}
The values of the Post and Author keys could contain all the properties that a model/key less
$paginate array could.
Once the $paginate variable has been defined, we can use the PaginatorComponents
paginate() method from our controller action. This will return find() results from the model. It
also sets some additional paging parameters, which are added to the request object. The additional information is set to $this->request->params[paging], and is used by PaginatorHelper for
creating links. PaginatorComponent::paginate() also adds PaginatorHelper to the list of
helpers in your controller, if it has not been added already:
public function list_recipes() {
$this->Paginator->settings = $this->paginate;
// similar to findAll(), but fetches paged results
$data = $this->Paginator->paginate(Recipe);
$this->set(data, $data);
}
You can filter the records by passing conditions as second parameter to the paginate() function:
$data = $this->Paginator->paginate(
Recipe,
array(Recipe.title LIKE => a%)
);
Or you can also set conditions and other pagination settings array inside your action:
public function list_recipes() {
$this->Paginator->settings = array(
conditions => array(Recipe.title LIKE => a%),
limit => 10
);
$data = $this->Paginator->paginate(Recipe);
$this->set(compact(data));
}
526
If youre not able to use the standard find options to create the query you need to display your data,
there are a few options. You can use a custom find type. You can also implement the paginate()
and paginateCount() methods on your model, or include them in a behavior attached to your model.
Behaviors implementing paginate and/or paginateCount should implement the method signatures
defined below with the normal additional first parameter of $model:
// paginate and paginateCount implemented on a behavior.
public function paginate(Model $model, $conditions, $fields, $order, $limit,
$page = 1, $recursive = null, $extra = array()) {
// method content
}
public function paginateCount(Model $model, $conditions = null, $recursive = 0,
$extra = array()) {
// method body
}
Its seldom youll need to implement paginate() and paginateCount(). You should make sure you cant
achieve your goal with the core model methods, or a custom finder. To paginate with a custom find type,
you should set the 0th element, or the findType key as of 2.3:
public $paginate = array(
popular
);
Since the 0th index is difficult to manage, in 2.3 the findType option was added:
public $paginate = array(
findType => popular
);
/**
* Overridden paginate method - group by week, away_team_id and home_team_id
*/
public 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:
Components
527
/**
* Overridden paginateCount method
*/
public 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 weve defined wasnt actually necessary All you have to do is add the keyword in controllers $paginate class variable:
/**
* Add GROUP BY clause
*/
public $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
*/
public function index() {
$this->Paginator->settings = array(
MyModel => array(
limit => 20,
order => array(week => desc),
group => array(week, home_team_id, away_team_id)
)
);
}
In CakePHP 2.0, you no longer need to implement paginateCount() when using group clauses. The
core find(count) will correctly count the total number of rows.
Control which fields used for ordering
By default sorting can be done with any column on a model. This is sometimes undesirable as it can allow
users to sort on un-indexed columns, or virtual fields that can be expensive to calculate. You can use the 3rd
parameter of PaginatorComponent::paginate() to restrict the columns that sorting will be done
on:
528
This would allow sorting on the title and slug columns only. A user that sets sort to any other value will be
ignored.
Limit the maximum number of rows that can be fetched
The number of results that are fetched is exposed to the user as the limit parameter. It is generally
undesirable to allow users to fetch all rows in a paginated set. By default CakePHP limits the maximum
number of rows that can be fetched to 100. If this default is not appropriate for your application, you can
adjust it as part of the pagination options:
public $paginate = array(
// other keys here.
maxLimit => 10
);
If the requests limit param is greater than this value, it will be reduced to the maxLimit value.
Pagination with GET parameters
In previous versions of CakePHP you could only generate pagination links using named parameters. But
if pages were requested with GET parameters they would still work. For 2.0, we decided to make how
you generate pagination parameters more controlled and consistent. You can choose to use either querystring or named parameters in the component. Incoming requests will accept only the chosen type, and the
PaginatorHelper will generate links with the chosen type of parameter:
public $paginate = array(
paramType => querystring
);
$this->Paginator->settings[paramType] = querystring;
By default all of the typical paging parameters will be converted into GET arguments.
Note: You can run into a situation where assigning a value to a nonexistent property will throw errors:
$this->paginate[limit] = 10;
will throw the error Notice: Indirect modification of overloaded property $paginate has no effect. Assigning an initial value to the property solves the issue:
$this->paginate = array();
$this->paginate[limit] = 10;
//or
$this->paginate = array(limit => 10);
Components
529
As of 2.3 the PaginatorComponent will throw a NotFoundException when trying to access a non-existent
page, i.e. page number requested is greater than total page count.
So you could either let the normal error page be rendered or use a try catch block and take appropriate action
when a NotFoundException is caught:
public function index() {
try {
$this->Paginator->paginate();
} catch (NotFoundException $e) {
//Do something here like redirecting to first or last page.
//$this->request->params[paging] will give you required info.
}
}
AJAX Pagination
Check the PaginatorHelper documentation for how to create links for pagination navigation.
Sessions
class SessionComponent(ComponentCollection $collection, array $settings = array())
The CakePHP SessionComponent provides a way to persist client data between page requests. It acts as
a wrapper for $_SESSION as well as providing convenience methods for several $_SESSION related
functions.
530
Sessions can be configured in a number of ways in CakePHP. For more information, you should see the
Session configuration documentation.
Interacting with Session data
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.
User.username would reference the following:
So
Dots are used to indicate nested arrays. This notation is used for all Session component methods wherever
a name/key is used.
SessionComponent::write($name, $value)
Write to the Session puts $value into $name. $name can be a dot separated array. For example:
$this->Session->write(Person.eyeColor, Green);
This writes the value Green to the session under Person => eyeColor.
SessionComponent::read($name)
Returns the value at $name in the Session. If $name is null the entire session will be returned. E.g:
$green = $this->Session->read(Person.eyeColor);
Retrieve the value Green from the session. Reading data that does not exist will return null.
SessionComponent::check($name)
Used to check if a Session variable has been set. Returns true on existence and false on non-existence.
SessionComponent::delete($name)
Clear the session data at $name. E.g:
$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:
$this->Session->delete(Person);
SessionComponent::destroy()
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:
$this->Session->destroy();
Components
531
This will create a one-time message that can be displayed to the user, using the SessionHelper:
// In the view.
echo $this->Session->flash();
// The above will output.
<div id="flashMessage" class="message">
Your stuff has been saved.
</div>
You can use the additional parameters of setFlash() to create different kinds of flash messages. For example, you may want error and positive notifications to look different from each other.
CakePHP gives you a way to do that. Using the $key parameter, you can store multiple messages,
which can be output separately:
// set a bad message.
$this->Session->setFlash(Something bad., default, array(), bad);
// set a good message.
$this->Session->setFlash(Something good., default, array(), good);
The $element parameter allows you to control the element (located in /app/View/Elements)
in which the message should be rendered. Within the element, the message is available as $message.
First we set the flash in our controller:
$this->Session->setFlash(Something custom!, flash_custom);
Then we create the file app/View/Elements/flash_custom.ctp and build our custom flash
element:
<div id="myCustomFlash"><?php echo h($message); ?></div>
532
$params allows you to pass additional view variables to the rendered layout. 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 $this->Session->flash() in your layout or view:
$this->Session->setFlash(
Example message text,
default,
array(class => example_class)
);
The output from using $this->Session->flash() with the above example would be:
<div id="flashMessage" class="example_class">Example message text</div>
To use an element from a plugin just specify the plugin in the $params:
// Will use /app/Plugin/Comment/View/Elements/flash_no_spam.ctp
$this->Session->setFlash(
Message!,
flash_no_spam,
array(plugin => Comment)
);
Note: By default CakePHP does not escape the HTML in flash messages. If you are using any request
or user data in your flash messages, you should escape it with h when formatting your messages.
Authentication
class AuthComponent(ComponentCollection $collection, array $settings = array())
Identifying, authenticating and authorizing users is a common part of almost every web application. In
CakePHP AuthComponent provides a pluggable way to do these tasks. AuthComponent allows you to
combine authentication objects, and authorization objects to create flexible ways of identifying and checking
user authorization.
Suggested Reading Before Continuing
Configuring authentication requires several steps including defining a users table, creating a model, controller & views, etc.
This is all covered step by step in the Blog Tutorial.
Authentication
Authentication is the process of identifying users by provided credentials and ensuring that users are who
they say they are. Generally this is done through a username and password, that are checked against a known
list of users. In CakePHP, there are several built-in ways of authenticating users stored in your application.
Components
533
FormAuthenticate allows you to authenticate users based on form POST data. Usually this is a
login form that users enter information into.
BasicAuthenticate allows you to authenticate users using Basic HTTP authentication.
DigestAuthenticate allows you to authenticate users using Digest HTTP authentication.
By default AuthComponent uses FormAuthenticate.
Choosing an Authentication type Generally youll want to offer form based authentication. It is the
easiest for users using a web-browser to use. If you are building an API or webservice, you may want
to consider basic authentication or digest authentication. The key differences between digest and basic
authentication are mostly related to how passwords are handled. In basic authentication, the username
and password are transmitted as plain-text to the server. This makes basic authentication un-suitable for
applications without SSL, as you would end up exposing sensitive passwords. Digest authentication uses
a digest hash of the username, password, and a few other details. This makes digest authentication more
appropriate for applications without SSL encryption.
You can also use authentication systems like openid as well, however openid is not part of CakePHP core.
Configuring
Authentication
handlers You
configure
authentication
handlers
using
$this->Auth->authenticate. You can configure one or many handlers for authentication.
Using multiple handlers allows you to support different ways of logging users in. When logging users in,
authentication handlers are checked in the order they are declared. Once one handler is able to identify
the user, no other handlers will be checked. Conversely you can halt all authentication by throwing an
exception. You will need to catch any thrown exceptions, and handle them as needed.
You can configure authentication handlers in your controllers beforeFilter or, in the $components
array. You can pass configuration information into each authentication object, using an array:
// Basic setup
$this->Auth->authenticate = array(Form);
// Pass settings in
$this->Auth->authenticate = array(
Basic => array(userModel => Member),
Form => array(userModel => Member)
);
In the second example youll notice that we had to declare the userModel key twice. To help you keep
your code DRY, you can use the all key. This special key allows you to set settings that are passed to every
attached object. The all key is also exposed as AuthComponent::ALL:
// Pass settings in using all
$this->Auth->authenticate = array(
AuthComponent::ALL => array(userModel => Member),
Basic,
Form
);
534
In the above example, both Form and Basic will get the settings defined for the all key. Any settings
passed to a specific authentication object will override the matching key in the all key. The core authentication objects support the following configuration keys.
fields The fields to use to identify a user by.
userModel The model name of the User, defaults to User.
scope Additional conditions to use when looking up and authenticating users,
array(User.is_active => 1).
i.e.
contain Containable options for when the user record is loaded. If you want to use this option,
youll need to make sure your model has the containable behavior attached.
New in version 2.2.
passwordHasher Password hasher class. Defaults to Simple.
New in version 2.4.
userFields The list of fields to fetch from the userModel. This option is helpful when you have
a wide user table and do not need all the columns in the session. By default all fields are fetched.
New in version 2.6.
To configure different fields for user in $components array:
// Pass settings in $components array
public $components = array(
Auth => array(
authenticate => array(
Form => array(
fields => array(username => email)
)
)
)
);
Do not put other Auth configuration keys (like authError, loginAction etc) within the authenticate or Form
element. They should be at the same level as the authenticate key. The setup above with other Auth configuration should look like:
// Pass settings in $components array
public $components = array(
Auth => array(
loginAction => array(
controller => users,
action => login,
plugin => users
),
authError => Did you really think you are allowed to see that?,
authenticate => array(
Form => array(
fields => array(
username => my_user_model_username_field, //Default is username in
password => my_user_model_password_field //Default is password in
Components
535
)
)
)
)
);
In addition to the common configuration, Basic authentication supports the following keys:
realm The realm being authenticated. Defaults to env(SERVER_NAME).
In addition to the common configuration Digest authentication supports the following keys:
realm The realm authentication is for, Defaults to the servername.
nonce A nonce used for authentication. Defaults to uniqid().
qop Defaults to auth, no other values are supported at this time.
opaque A string that must
md5($settings[realm])
be
returned
unchanged
by
clients.
Defaults
to
Identifying users and logging them in In the past AuthComponent auto-magically logged users in.
This was confusing for many people, and made using AuthComponent a bit difficult at times. For 2.0, youll
need to manually call $this->Auth->login() to log a user in.
When authenticating users, attached authentication objects are checked in the order they are attached. Once
one of the objects can identify the user, no other objects are checked. A sample login function for working
with a login form could look like:
public function login() {
if ($this->request->is(post)) {
// Important: Use login() without arguments! See warning below.
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirectUrl());
// Prior to 2.3 use
// return $this->redirect($this->Auth->redirect());
}
$this->Session->setFlash(
__(Username or password is incorrect),
default,
array(),
auth
);
}
}
The above code (without any data passed to the login method), will attempt to log a user in using the POST data, and if successful redirect the user to either the last page they were visiting, or
AuthComponent::$loginRedirect. If the login is unsuccessful, a flash message is set.
Warning: In 2.x $this->Auth->login($this->request->data) will log the user in with
whatever data is posted, whereas in 1.3 $this->Auth->login($this->data) would try to identify the user first and only log in when successful.
536
Using Digest and Basic Authentication for logging in Because basic and digest authentication dont require an initial POST or a form so if using only basic / digest authenticators you dont require a login action
in your controller. Also you can set AuthComponent::$sessionKey to false to ensure AuthComponent doesnt try to read user info from session. Stateless authentication will re-verify the users credentials
on each request, this creates a small amount of additional overhead, but allows clients that to login in without
using cookies.
Note: Prior to 2.4 you still need the login action as you are redirected to login when an unauthenticated user tries to access a protected page even when using only basic or digest auth. Also setting
AuthComponent::$sessionKey to false will cause an error prior to 2.4.
Creating Custom Authentication objects Because authentication objects are pluggable, you can create
custom authentication objects in your application or plugins. If for example you wanted to create an OpenID
authentication object. In app/Controller/Component/Auth/OpenidAuthenticate.php you
could put the following:
App::uses(BaseAuthenticate, Controller/Component/Auth);
class OpenidAuthenticate extends BaseAuthenticate {
public function authenticate(CakeRequest $request, CakeResponse $response) {
// Do things for OpenID here.
// Return an array of user if they could authenticate the user,
// return false if not
}
}
Authentication objects should return false if they cannot identify the user. And an array of user information if they can. Its not required that you extend BaseAuthenticate, only that your authentication
object implements an authenticate() method. The BaseAuthenticate class provides a number
of helpful methods that are commonly used. You can also implement a getUser() method if your authentication object needs to support stateless or cookie-less authentication. See the sections on basic and digest
authentication below for more information.
Using custom authentication objects Once youve created your custom authentication object, you can
use them by including them in AuthComponents authenticate array:
$this->Auth->authenticate = array(
Openid, // app authentication object.
AuthBag.Combo, // plugin authentication object.
);
Creating stateless authentication systems Authentication objects can implement a getUser() method
that can be used to support user login systems that dont rely on cookies. A typical getUser method looks at
the request/environment and uses the information there to confirm the identity of the user. HTTP Basic authentication for example uses $_SERVER[PHP_AUTH_USER] and $_SERVER[PHP_AUTH_PW]
for the username and password fields. On each request, these values are used to re-identify the user and
Components
537
ensure they are valid user. As with authentication objects authenticate() method the getUser()
method should return an array of user information on success or false on failure.
public function getUser($request) {
$username = env(PHP_AUTH_USER);
$pass = env(PHP_AUTH_PW);
if (empty($username) || empty($pass)) {
return false;
}
return $this->_findUser($username, $pass);
}
The above is how you could implement getUser method for HTTP basic authentication.
The
_findUser() method is part of BaseAuthenticate and identifies a user based on a username and
password.
Handling unauthenticated requests When an unauthenticated user tries to access a protected page first
the unauthenticated() method of the last authenticator in the chain is called. The authenticate object can
handle sending response or redirection as appropriate and return true to indicate no further action is necessary. Due to this the order in which you specify the authenticate object in AuthComponent::$authenticate
property matters.
If authenticator returns null, AuthComponent redirects user to login action. If its an AJAX request and
AuthComponent::$ajaxLogin is specified that element is rendered else a 403 HTTP status code is returned.
Note: Prior to 2.4 the authenticate objects do not provide an unauthenticated() method.
Displaying auth related flash messages In order to display the session error messages that Auth
generates, you need to add the following code to your layout. Add the following two lines to the
app/View/Layouts/default.ctp file in the body section preferable before the content_for_layout
line.
echo $this->Session->flash();
echo $this->Session->flash(auth);
You can customize the error messages, and flash settings AuthComponent uses.
Using
$this->Auth->flash you can configure the parameters AuthComponent uses for setting flash messages. The available keys are
element - The element to use, defaults to default.
key - The key to use, defaults to auth
params - The array of additional params to use, defaults to array()
In addition to the flash message settings you can customize other error messages AuthComponent uses. In
your controllers beforeFilter, or component settings you can use authError to customize the error used
for when authorization fails:
538
Changed in version 2.4: Sometimes, you want to display the authorization error only after the user has
already logged-in. You can suppress this message by setting its value to boolean false
In your controllers beforeFilter(), or component settings:
if (!$this->Auth->loggedIn()) {
$this->Auth->authError = false;
}
Hashing passwords AuthComponent no longer automatically hashes every password it can find. This
was removed because it made a number of common tasks like validation difficult. You should never store
plain text passwords, and before saving a user record you should always hash the password.
As of 2.4 the generation and checking of password hashes has been delegated to password hasher classes.
Authenticating objects use a new setting passwordHasher which specifies the password hasher class to
use. It can be a string specifying class name or an array with key className stating the class name and
any extra keys will be passed to password hasher constructor as config. The default hasher class Simple
can be used for sha1, sha256, md5 hashing. By default the hash type set in Security class will be used. You
can use specific hash type like this:
public $components = array(
Auth => array(
authenticate => array(
Form => array(
passwordHasher => array(
className => Simple,
hashType => sha256
)
)
)
)
);
When creating new user records you can hash a password in the beforeSave callback of your model using
appropriate password hasher class:
App::uses(SimplePasswordHasher, Controller/Component/Auth);
class User extends AppModel {
public function beforeSave($options = array()) {
if (!empty($this->data[$this->alias][password])) {
$passwordHasher = new SimplePasswordHasher(array(hashType => sha256));
$this->data[$this->alias][password] = $passwordHasher->hash(
$this->data[$this->alias][password]
);
}
return true;
}
}
Components
539
You dont need to hash passwords before calling $this->Auth->login(). The various authentication
objects will hash passwords individually.
Using bcrypt for passwords In CakePHP 2.3 the BlowfishAuthenticate class was introduced to
allow using bcrypt10 a.k.a Blowfish for hash passwords. Bcrypt hashes are much harder to brute force
than passwords stored with sha1. But BlowfishAuthenticate has been deprecated in 2.4 and instead
BlowfishPasswordHasher has been added.
A blowfish password hasher can be used with any authentication class. All you have to do with specify
passwordHasher setting for the authenticating object:
public $components = array(
Auth => array(
authenticate => array(
Form => array(
passwordHasher => Blowfish
)
)
)
);
Hashing passwords for digest authentication Because Digest authentication requires a password hashed
in the format defined by the RFC, in order to correctly hash a password for use with Digest authentication
you should use the special password hashing function on DigestAuthenticate. If you are going to
be combining digest authentication with any other authentication strategies, its also recommended that you
store the digest password in a separate column, from the normal password hash:
class User extends AppModel {
public function beforeSave($options = array()) {
// make a password for digest auth.
$this->data[$this->alias][digest_hash] = DigestAuthenticate::password(
$this->data[$this->alias][username],
$this->data[$this->alias][password],
env(SERVER_NAME)
);
return true;
}
}
Passwords for digest authentication need a bit more information than other password hashes, based on the
RFC for digest authentication.
Note:
The third parameter of DigestAuthenticate::password() must match the realm config value
defined when DigestAuthentication was configured in AuthComponent::$authenticate. This defaults to
env(SCRIPT_NAME). You may wish to use a static string if you want consistent hashes in multiple
environments.
10
https://en.wikipedia.org/wiki/Bcrypt
540
Creating custom password hasher classes Custom password hasher classes need to extend the
AbstractPasswordHasher class and need to implement the abstract methods hash() and check().
In app/Controller/Component/Auth/CustomPasswordHasher.php you could put the following:
App::uses(AbstractPasswordHasher, Controller/Component/Auth);
class CustomPasswordHasher extends AbstractPasswordHasher {
public function hash($password) {
// stuff here
}
public function check($password, $hashedPassword) {
// stuff here
}
}
Manually logging users in Sometimes the need arises where you need to manually log a user in, such as
just after they registered for your application. You can do this by calling $this->Auth->login() with
the user data you want to login:
public function register() {
if ($this->User->save($this->request->data)) {
$id = $this->User->id;
$this->request->data[User] = array_merge(
$this->request->data[User],
array(id => $id)
);
unset($this->request->data[User][password]);
$this->Auth->login($this->request->data[User]);
return $this->redirect(/users/home);
}
}
Warning: Be sure to manually add the new User id to the array passed to the login method. Otherwise
you wont have the user id available.
Warning:
Be sure to unset password fields before manually passing data into
$this->Auth->login(), or it will get saved in the Session unhashed.
Accessing the logged in user Once a user is logged in, you will often need some particular information
about the current user. You can access the currently logged in user using AuthComponent::user().
This method is static, and can be used globally after the AuthComponent has been loaded. You can access
it both as an instance method or as a static method:
// Use anywhere
AuthComponent::user(id)
Components
541
Logging users out Eventually youll want 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:
public function logout() {
return $this->redirect($this->Auth->logout());
}
Logging out users that logged in with Digest or Basic auth is difficult to accomplish for all clients. Most
browsers will retain credentials for the duration they are still open. Some clients can be forced to logout
by sending a 401 status code. Changing the authentication realm is another solution that works for some
clients.
Authorization
Authorization is the process of ensuring that an identified/authenticated user is allowed to access the resources they are requesting. If enabled AuthComponent can automatically check authorization handlers
and ensure that logged in users are allowed to access the resources they are requesting. There are several
built-in authorization handlers, and you can create custom ones for your application, or as part of a plugin.
ActionsAuthorize Uses the AclComponent to check for permissions on an action level.
CrudAuthorize Uses the AclComponent and action -> CRUD mappings to check permissions for
resources.
ControllerAuthorize Calls isAuthorized() on the active controller, and uses the return
of that to authorize a user. This is often the most simple way to authorize users.
Configuring
Authorization
handlers You
configure
authorization
handlers
using
$this->Auth->authorize. You can configure one or many handlers for authorization. Using
multiple handlers allows you to support different ways of checking authorization. When authorization
handlers are checked, they will be called in the order they are declared. Handlers should return false, if they
are unable to check authorization, or the check has failed. Handlers should return true if they were able to
check authorization successfully. Handlers will be called in sequence until one passes. If all checks fail, the
user will be redirected to the page they came from. Additionally you can halt all authorization by throwing
an exception. You will need to catch any thrown exceptions, and handle them.
You can configure authorization handlers in your controllers beforeFilter or, in the $components
array. You can pass configuration information into each authorization object, using an array:
// Basic setup
$this->Auth->authorize = array(Controller);
// Pass settings in
$this->Auth->authorize = array(
Actions => array(actionPath => controllers/),
542
Controller
);
Much like Auth->authenticate, Auth->authorize, helps you keep your code DRY, by using the
all key. This special key allows you to set settings that are passed to every attached object. The all key is
also exposed as AuthComponent::ALL:
// Pass settings in using all
$this->Auth->authorize = array(
AuthComponent::ALL => array(actionPath => controllers/),
Actions,
Controller
);
In the above example, both the Actions and Controller will get the settings defined for the all key.
Any settings passed to a specific authorization object will override the matching key in the all key. The
core authorize objects support the following configuration keys.
actionPath Used by ActionsAuthorize to locate controller action ACOs in the ACO tree.
actionMap Action -> CRUD mappings. Used by CrudAuthorize and authorization objects that
want to map actions to CRUD roles.
userModel The name of the ARO/Model node user information can be found under. Used with
ActionsAuthorize.
Creating Custom Authorize objects Because authorize objects are pluggable, you can create custom authorize objects in your application or plugins. If for example you wanted to create an LDAP authorize object.
In app/Controller/Component/Auth/LdapAuthorize.php you could put the following:
App::uses(BaseAuthorize, Controller/Component/Auth);
class LdapAuthorize extends BaseAuthorize {
public function authorize($user, CakeRequest $request) {
// Do things for ldap here.
}
}
Authorize objects should return false if the user is denied access, or if the object is unable to perform a
check. If the object is able to verify the users access, true should be returned. Its not required that you
extend BaseAuthorize, only that your authorize object implements an authorize() method. The
BaseAuthorize class provides a number of helpful methods that are commonly used.
Using custom authorize objects Once youve created your custom authorize object, you can use them by
including them in your AuthComponents authorize array:
$this->Auth->authorize = array(
Ldap, // app authorize object.
AuthBag.Combo, // plugin authorize object.
);
Components
543
Using no authorization If youd like to not use any of the built-in authorization objects, and want to handle things entirely outside of AuthComponent you can set $this->Auth->authorize = false;.
By default AuthComponent starts off with authorize = false. If you dont use an authorization
scheme, make sure to check authorization yourself in your controllers beforeFilter, or with another component.
Making actions public There are often times controller actions that you wish to remain entirely public,
or that dont require users to be logged in. AuthComponent is pessimistic, and defaults to denying access.
You can mark actions as public actions by using AuthComponent::allow(). By marking actions as
public, AuthComponent, will not check for a logged in user, nor will authorize objects be checked:
// Allow all actions. CakePHP 2.0
$this->Auth->allow(*);
// Allow all actions. CakePHP 2.1
$this->Auth->allow();
// Allow only the view and index actions.
$this->Auth->allow(view, index);
// Allow only the view and index actions.
$this->Auth->allow(array(view, index));
Warning: If youre using scaffolding, allow all will not identify and allow the scaffolded methods. You
have to specify their action names.
You can provide as many action names as you need to allow(). You can also supply an array containing
all the action names.
Making actions require authorization By default all actions require authorization.
However,
after making actions public, you want to revoke the public access.
You can do so using
AuthComponent::deny():
// remove one action
$this->Auth->deny(add);
// remove all the actions.
$this->Auth->deny();
// remove a group of actions.
$this->Auth->deny(add, edit);
$this->Auth->deny(array(add, edit));
You can provide as many action names as you need to deny(). You can also supply an array containing all
the action names.
Using ControllerAuthorize ControllerAuthorize allows you to handle authorization checks in a controller
callback. This is ideal when you have very simple authorization, or you need to use a combination of models
+ components to do your authorization, and dont want to create a custom authorize object.
544
The callback is always called isAuthorized() and it should return a boolean as to whether or not the
user is allowed to access resources in the request. The callback is passed the active user, so it can be checked:
class AppController extends Controller {
public $components = array(
Auth => array(authorize => Controller),
);
public function isAuthorized($user = null) {
// Any registered user can access public functions
if (empty($this->request->params[admin])) {
return true;
}
// Only admins can access admin functions
if (isset($this->request->params[admin])) {
return (bool)($user[role] === admin);
}
// Default deny
return false;
}
}
The above callback would provide a very simple authorization system where, only users with role = admin
could access actions that were in the admin prefix.
Using ActionsAuthorize ActionsAuthorize integrates with the AclComponent, and provides a fine
grained per action ACL check on each request. ActionsAuthorize is often paired with DbAcl to give dynamic
and flexible permission systems that can be edited by admin users through the application. It can however,
be combined with other Acl implementations such as IniAcl and custom application Acl backends.
Using CrudAuthorize CrudAuthorize integrates with AclComponent, and provides the ability to map
requests to CRUD operations. Provides the ability to authorize using CRUD mappings. These mapped
results are then checked in the AclComponent as specific permissions.
For example, taking /posts/index as the current request. The default mapping for index, is a read
permission check. The Acl check would then be for the posts controller with the read permission. This
allows you to create permission systems that focus more on what is being done to resources, rather than the
specific actions being visited.
Mapping actions when using CrudAuthorize When using CrudAuthorize or any other authorize objects
that use action mappings, it might be necessary to map additional methods. You can map actions -> CRUD
permissions using mapAction(). Calling this on AuthComponent will delegate to all the of the configured
authorize objects, so you can be sure the settings were applied every where:
$this->Auth->mapActions(array(
create => array(register),
view => array(show, display)
));
Components
545
The keys for mapActions should be the CRUD permissions you want to set, while the values should be an
array of all the actions that are mapped to the CRUD permission.
AuthComponent API
AuthComponent is the primary interface to the built-in authorization and authentication mechanics in
CakePHP.
property AuthComponent::$ajaxLogin
The name of an optional view element to render when an AJAX request is made with an invalid or
expired session.
property AuthComponent::$allowedActions
Controller actions for which user validation is not required.
property AuthComponent::$authenticate
Set to an array of Authentication objects you want to use when logging users in. There are several
core authentication objects, see the section on Suggested Reading Before Continuing.
property AuthComponent::$authError
Error to display when user attempts to access an object or action to which they do not have access.
Changed in version 2.4: You can suppress authError message from being displayed by setting this
value to boolean false.
property AuthComponent::$authorize
Set to an array of Authorization objects you want to use when authorizing users on each request, see
the section on Authorization.
property AuthComponent::$components
Other components utilized by AuthComponent
property AuthComponent::$flash
Settings to use when Auth needs to do a flash message with SessionComponent::setFlash().
Available keys are:
element - The element to use, defaults to default.
key - The key to use, defaults to auth
params - The array of additional params to use, defaults to array()
property AuthComponent::$loginAction
A URL (defined as a string or array) to the controller action that handles logins. Defaults to
/users/login
property AuthComponent::$loginRedirect
The URL (defined as a string or array) to the controller action users should be redirected to after
logging in. This value will be ignored if the user has an Auth.redirect value in their session.
property AuthComponent::$logoutRedirect
The default action to redirect to after the user is logged out. While AuthComponent does not handle post-logout redirection, a redirect URL will be returned from AuthComponent::logout().
Defaults to AuthComponent::$loginAction.
546
property AuthComponent::$unauthorizedRedirect
Controls handling of unauthorized access. By default unauthorized user is redirected to the referrer
URL or AuthComponent::$loginAction or /. If set to false a ForbiddenException exception
is thrown instead of redirecting.
property AuthComponent::$request
Request object
property AuthComponent::$response
Response object
property AuthComponent::$sessionKey
The session key name where the record of the current user is stored. If unspecified, it will be
Auth.User.
AuthComponent::allow($action[, $action, ... ])
Set one or more actions as public actions, this means that no authorization checks will be performed
for the specified actions. The special value of * will mark all the current controllers actions as
public. Best used in your controllers beforeFilter method.
AuthComponent::constructAuthenticate()
Loads the configured authentication objects.
AuthComponent::constructAuthorize()
Loads the authorization objects configured.
AuthComponent::deny($action[, $action, ... ])
Toggle one or more actions previously declared as public actions, as non-public methods. These
methods will now require authorization. Best used inside your controllers beforeFilter method.
AuthComponent::flash($message)
Set a flash message. Uses the Session component, and values from AuthComponent::$flash.
AuthComponent::identify($request, $response)
Parameters
$request (CakeRequest) The request to use.
$response (CakeResponse) The response to use, headers can be sent if authentication fails.
This method is used by AuthComponent to identify a user based on the information contained in the
current request.
AuthComponent::initialize($Controller)
Initializes AuthComponent for use in the controller.
AuthComponent::isAuthorized($user = null, $request = null)
Uses the configured Authorization adapters to check whether or not a user is authorized. Each adapter
will be checked in sequence, if any of them return true, then the user will be authorized for the request.
AuthComponent::loggedIn()
Returns true if the current client is a logged in user, or false if they are not.
AuthComponent::login($user)
Components
547
Parameters
$user (array) Array of logged in user data.
Takes an array of user data to login with. Allows for manual logging of users. Calling user() will
populate the session value with the provided information. If no user is provided, AuthComponent will
try to identify a user using the current request information. See AuthComponent::identify()
AuthComponent::logout()
Returns A string URL to redirect the logged out user to.
Logs out the current user.
AuthComponent::mapActions($map = array())
Maps action names to CRUD operations. Used for controller-based authentication. Make sure to
configure the authorize property before calling this method. As it delegates $map to all the attached
authorize objects.
static AuthComponent::password($pass)
Deprecated since version 2.4.
AuthComponent::redirect($url = null)
Deprecated since version 2.3.
AuthComponent::redirectUrl($url = null)
If no parameter is passed, gets the authentication redirect URL. Pass a URL in to
set the destination a user should be redirected to upon logging in.
Will fallback to
AuthComponent::$loginRedirect if there is no stored redirect value.
New in version 2.3.
AuthComponent::shutdown($Controller)
Component shutdown. If user is logged in, wipe out redirect.
AuthComponent::startup($Controller)
Main execution method. Handles redirecting of invalid users, and processing of login form data.
static AuthComponent::user($key = null)
Parameters
$key (string) The user data key you want to fetch. If null, all user data will be
returned. Can also be called as an instance method.
Get data concerning the currently logged in user, you can use a property key to fetch specific data
about the user:
$id = $this->Auth->user(id);
If the current user is not logged in or the key doesnt exist, null will be returned.
Security
class SecurityComponent(ComponentCollection $collection, array $settings = array())
548
The Security Component creates an easy way to integrate tighter security in your application. It provides
methods for various tasks like:
Restricting which HTTP methods your application accepts.
CSRF protection.
Form tampering protection
Requiring that SSL be used.
Limiting cross controller communication.
Like all components it is configured through several configurable parameters. All of these properties can be
set directly or through setter methods of the same name in your controllers beforeFilter.
By using the Security Component you automatically get CSRF11 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 is controlled by the
csrfExpires time.
If you are using Security components 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.
Note: When using the Security Component you must use the FormHelper to create your forms. In addition,
you must not override any of the fields name attributes. 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
$disabledFields configuration parameters.
http://en.wikipedia.org/wiki/Cross-site_request_forgery
Components
549
}
public function blackhole($type) {
// handle errors.
}
SecurityComponent::requirePost()
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.
SecurityComponent::requireGet()
Sets the actions that require a GET request. Takes any number of arguments. Can be called with no
arguments to force all actions to require a GET.
SecurityComponent::requirePut()
Sets the actions that require a PUT request. Takes any number of arguments. Can be called with no
arguments to force all actions to require a PUT.
SecurityComponent::requireDelete()
Sets the actions that require a DELETE request. Takes any number of arguments. Can be called with
no arguments to force all actions to require a DELETE.
Restrict actions to SSL
SecurityComponent::requireSecure()
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.
SecurityComponent::requireAuth()
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.
550
property SecurityComponent::$allowedControllers
A list of controllers which can send requests to this controller. This can be used to control cross
controller requests.
property SecurityComponent::$allowedActions
A list of actions which are allowed to send requests to this controllers actions. This can be used to
control cross controller requests.
Form tampering prevention
By default the SecurityComponent prevents users from tampering with forms in specific ways. The
SecurityComponent will prevent the following things:
Unknown fields cannot be added to the form.
Fields cannot be removed from the form.
Values in hidden inputs cannot be modified.
Preventing these types of tampering is accomplished by working with the FormHelper and tracking which
fields are in a form. The values for hidden fields are tracked as well. All of this data is combined and turned
into a hash. When a form is submitted, the SecurityComponent will use the POST data to build the
same structure and compare the hash.
Note: The SecurityComponent will not prevent select options from being added/changed. Nor will it
prevent radio options from being added/changed.
property SecurityComponent::$unlockedFields
Set to a list of form fields to exclude from POST validation. Fields can be unlocked either in the
Component, or with FormHelper::unlockField(). Fields that have been unlocked are not
required to be part of the POST and hidden unlocked fields do not have their values checked.
property SecurityComponent::$validatePost
Set to false to completely skip the validation of POST requests, essentially turning off form validation.
CSRF configuration
property SecurityComponent::$csrfCheck
Whether to use CSRF protected forms. Set to false to disable CSRF protection on forms.
property SecurityComponent::$csrfExpires
The duration from when a CSRF token is created that it will expire on. Each form/page request will
generate a new token that can only be submitted once unless it expires. Can be any value compatible
with strtotime(). The default is +30 minutes.
property SecurityComponent::$csrfUseOnce
Controls whether or not CSRF tokens are single use. Set to false to not generate new tokens on
Components
551
each request. One token will be reused until it expires. This reduces the chances of users getting
invalid requests because of token consumption. It has the side effect of making CSRF less secure, as
tokens are reusable.
Usage
Using the security component is generally done in the controllers beforeFilter(). You would specify
the security restrictions you want and the Security Component will enforce them on its startup:
class WidgetController extends AppController {
public $components = array(Security);
public function beforeFilter() {
$this->Security->requirePost(delete);
}
}
In this example the delete action can only be successfully triggered if it receives a POST request:
class WidgetController extends AppController {
public $components = array(Security);
public function beforeFilter() {
if (isset($this->request->params[admin])) {
$this->Security->requireSecure();
}
}
}
This example would force all actions that had admin routing to require secure SSL requests:
class WidgetController extends AppController {
public $components = array(Security);
public function beforeFilter() {
if (isset($this->params[admin])) {
$this->Security->blackHoleCallback = forceSSL;
$this->Security->requireSecure();
}
}
public function forceSSL() {
return $this->redirect(https:// . env(SERVER_NAME) . $this->here);
}
}
This example would force all actions that had admin routing to require 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.
552
CSRF protection
CSRF or Cross Site Request Forgery is a common vulnerability in web applications. It allows an attacker to
capture and replay a previous request, and sometimes submit data requests using image tags or resources on
other domains.
Double submission and replay attacks are handled by the SecurityComponent CSRF features. They
work by adding a special token to each form request. This token, once used, cannot be used again. If an
attempt is made to re-use an expired token the request will be blackholed.
Using CSRF protection Simply by adding the SecurityComponent to your components array, you
can benefit from the CSRF protection it provides. By default CSRF tokens are valid for 30 minutes and
expire on use. You can control how long tokens last by setting csrfExpires on the component.
public $components = array(
Security => array(
csrfExpires => +1 hour
)
);
The csrfExpires property can be any value that is compatible with strtotime()12 . By default the
FormHelper will add a data[_Token][key] containing the CSRF token to every form when the
component is enabled.
Handling missing or expired tokens Missing or expired tokens are handled similar to other security
violations. The SecurityComponent blackHoleCallback will be called with a csrf parameter.
This helps you filter out CSRF token failures, from other warnings.
Using per-session tokens instead of one-time use tokens By default a new CSRF token is generated
for each request, and each token can only be used once. If a token is used twice, the request will be
blackholed. Sometimes, this behaviour is not desirable, as it can create issues with single page applications.
You can toggle on longer, multi-use tokens by setting csrfUseOnce to false. This can be done in the
components array, or in the beforeFilter of your controller:
public $components = array(
Security => array(
csrfUseOnce => false
)
);
12
http://php.net/manual/en/function.strtotime.php
Components
553
This will tell the component that you want to re-use a CSRF token until it expires - which is controlled by
the csrfExpires value. If you are having issues with expired tokens, this is a good balance between
security and ease of use.
Disabling the CSRF protection There may be cases where you want to disable CSRF protection on your forms for some reason.
If you do want to disable this feature, you can set
$this->Security->csrfCheck = false; in your beforeFilter or use the components array. By default CSRF protection is enabled, and configured to use one-use tokens.
Disabling CSRF and Post Data Validation For Specific Actions
There may be cases where you want to disable all security checks for an action (ex. AJAX requests). You may unlock these actions by listing them in $this->Security->unlockedActions
in your beforeFilter. The unlockedActions property will not effect other features of
SecurityComponent.
New in version 2.3.
Request Handling
class RequestHandlerComponent(ComponentCollection $collection, array $settings = array())
The Request Handler component is used in CakePHP to obtain additional information about the HTTP
requests that are made to your applications. You can use it to inform your controllers about AJAX as well as
gain additional insight into content types that the client accepts and automatically changes to the appropriate
layout when file extensions are enabled.
By default RequestHandler will automatically detect AJAX requests based on the HTTP-XRequested-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/JSON data is POSTed to your
Controllers, it will be parsed into an array which is assigned to $this->request->data, and can then
be saved as model data. In order to make use of RequestHandler it must be included in your $components
array:
class WidgetController extends AppController {
public $components = array(RequestHandler);
// Rest of controller
}
Request Handler has several methods that provide information about the client and its request.
554
RequestHandlerComponent::accepts($type = null)
$type can be a string, or an array, or null. If a string, accepts will return true if the client accepts the
content type. If an array is specified, accepts return true if any one of the content types is accepted by
the client. If null returns an array of the content-types that the client accepts. For example:
class PostsController extends AppController {
public $components = array(RequestHandler);
public 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
}
}
}
Components
555
NetFront
Nokia
Opera Mini
Opera Mobi
PalmOS
PalmSource
portalmmm
Plucker
ReqwirelessWeb
SonyEricsson
Symbian
UP.Browser
webOS
Windows CE
Windows Phone OS
Xiino
RequestHandlerComponent::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:
if ($this->request->is(ajax)) {
$this->disableCache();
}
// Continue Controller action
RequestHandlerComponent::getAjaxVersion()
Gets Prototype version if call is AJAX, otherwise empty string. The Prototype library sets a special
Prototype version HTTP header.
Automatically decoding request data
RequestHandlerComponent::addInputType($type, $handler)
556
Parameters
$type (string) The content type alias this attached decoder is for. e.g. json or
xml
$handler (array) The handler information for the type.
Add a request data decoder. The handler should contain a callback, and any additional arguments for
the callback. The callback should return an array of data contained in the request input. For example
adding a CSV handler in your controllers beforeFilter could look like:
$parser = function ($data) {
$rows = str_getcsv($data, "\n");
foreach ($rows as &$row) {
$row = str_getcsv($row, ,);
}
return $rows;
};
$this->RequestHandler->addInputType(csv, array($parser));
The above example requires PHP 5.3, however you can use any callable13 for the handling function. You can also pass additional arguments to the callback, this is useful for callbacks like
json_decode:
$this->RequestHandler->addInputType(json, array(json_decode, true));
The above will make $this->request->data an array of the JSON input data, without the
additional true youd get a set of StdClass objects.
Responding To Requests
In addition to request detection RequestHandler also provides easy access to altering the output and content
type mappings for your application.
RequestHandlerComponent::setContent($name, $type = null)
Parameters
$name (string) The name or file extension of the Content-type ie. html, css,
json, xml.
$type (mixed) The mime-type(s) that the Content-type maps to.
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.
The default mappings are:
13
http://php.net/callback
Components
557
javascript text/javascript
js text/javascript
json application/json
css text/css
html text/html, */*
text text/plain
txt text/plain
csv application/vnd.ms-excel, text/plain
form application/x-www-form-urlencoded
file multipart/form-data
xhtml application/xhtml+xml, application/xhtml, text/xhtml
xhtml-mobile application/vnd.wap.xhtml+xml
xml application/xml, text/xml
rss application/rss+xml
atom application/atom+xml
amf application/x-amf
wap text/vnd.wap.wml, text/vnd.wap.wmlscript, image/vnd.wap.wbmp
wml text/vnd.wap.wml
wmlscript text/vnd.wap.wmlscript
wbmp image/vnd.wap.wbmp
pdf application/pdf
zip application/x-zip
tar application/x-tar
RequestHandlerComponent::prefers($type = null)
Determines which content-types the client prefers. If no parameter is given the most likely content
type is returned. If $type is an array the first type the client accepts will be returned. Preference is
determined primarily by the file extension parsed by Router if one has been provided, and secondly
by the list of content-types in HTTP_ACCEPT.
RequestHandlerComponent::renderAs($controller, $type)
Parameters
$controller (Controller) Controller Reference
$type (string) 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 controllers helper array if available and not already in the array.
558
RequestHandlerComponent::respondAs($type, $options)
Parameters
$type (string) Friendly content type name ex. xml, rss or a full content type like
application/x-shockwave
$options (array) 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.
RequestHandlerComponent::responseType()
Returns the current response type Content-type header or null if one has yet to be set.
Taking advantage of HTTP cache validation
559
$viewClass (string) The viewClass to be used for the type without View appended
You can also set this automatically by using the viewClassMap setting:
public $components = array(
RequestHandler => array(
viewClassMap => array(
json => ApiKit.MyJson,
xml => ApiKit.MyXml,
csv => ApiKit.Csv
)
));
Cookie
class CookieComponent(ComponentCollection $collection, array $settings = array())
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.
Controller Setup
There are a number of controller variables that allow you to configure the way cookies are created and
managed. Defining these special variables in the beforeFilter() method of your controller allows you to
define how the CookieComponent works.
560
Cookie
variable
string
$name
string
$key
dedescription
fault
Cake- The name of the cookie.
Cookie
null
This string is used to encrypt the value written to the cookie. The string should be
random and difficult to guess.
When using rijndael or aes encryption, this value must be longer than 32 bytes.
string
The domain name allowed to access the cookie. For example, use
$domain
.yourdomain.com to allow access from all your subdomains.
int or
5
The time when your cookie will expire. Integers are interpreted as seconds. A
string
Days
value of 0 is equivalent to a session cookie: i.e., the cookie expires when the
$time
browser is closed. If a string is set, this will be interpreted with PHP function
strtotime(). You can set this directly within the write() method.
string
/
The server path on which the cookie will be applied. If $path is set to /foo/, the
$path
cookie will only be available within the /foo/ directory and all sub-directories of
your domain, such as /foo/bar. The default value is the entire domain. You can set
this directly within the write() method.
boolean
false
Indicates that the cookie should only be transmitted over a secure HTTPS
$secure
connection. When set to true, the cookie will only be set if a secure connection
exists. You can set this directly within the write() method.
boolean
false
Set to true to make HTTP only cookies. Cookies that are HTTP only are not
$httpOnly
accessible in Javascript.
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/, expires in one hour and is HTTP only:
public $components = array(Cookie);
public function beforeFilter() {
parent::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!adre@34SAv!@*(XSL#$%)asGb$@11~_+!@#HKis~#^;
$this->Cookie->httpOnly = true;
$this->Cookie->type(aes);
}
Next, lets look at how to use the different methods of the Cookie Component.
Using the Component
561
You can also group your variables by using dot notation in the key parameter:
$this->Cookie->write(User.name, Larry);
$this->Cookie->write(User.role, Lead);
If you want to write more than one value to the cookie at a time, you can pass an array:
$this->Cookie->write(User,
array(name => Larry, role => Lead)
);
All values in the cookie are encrypted by default. If you want to store the values as plain text, set the
third parameter of the write() method to false. You should remember to set the encryption mode to
aes to ensure that values are securely encrypted:
$this->Cookie->write(name, Larry, false);
The last parameter to write is $expires the number of seconds until your cookie will expire. For convenience, this parameter can also be passed as a string that the php strtotime() function understands:
// Both cookies expire in one hour.
$this->Cookie->write(first_name, Larry, false, 3600);
$this->Cookie->write(last_name, Masters, false, 1 hour);
CookieComponent::check($key)
Parameters
$key (string) The key to check.
Used to check whether a key/path exists and has a non-null value.
New in version 2.3: CookieComponent::check() was added in 2.3
CookieComponent::delete(mixed $key)
Deletes a cookie variable of the name in $key. Works with dot notation:
562
// Delete a variable
$this->Cookie->delete(bar);
// Delete the cookie variable bar, but not everything under foo
$this->Cookie->delete(foo.bar);
CookieComponent::destroy()
Destroys the current cookie.
CookieComponent::type($type)
Allows you to change the encryption scheme. By default the cipher scheme is used for backwards
compatibility. However, you should always use either the rijndael or aes schemes.
Changed in version 2.2: The rijndael type was added.
New in version 2.5: The aes type was added.
Access Control Lists
class AclComponent(ComponentCollection $collection, array $settings = array())
Understanding How ACL Works
Powerful things require access control. Access control lists are a way to manage application permissions in
a fine-grained, yet easily maintainable and manageable way.
Access control lists, or ACL, handle two main things: things that want stuff, and things that are wanted.
In ACL lingo, things (most often users) that want to use stuff are represented by access request objects, or
AROs. Things in the system that are wanted (most often actions or data) are represented by access control
objects, or ACOs. The entities are called objects because sometimes the requesting object isnt a person.
Sometimes you might want to limit the ability of certain CakePHP controllers 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 in your grandmas online diary.
To review:
ACO - Access Control Object - Represents something that is wanted
ARO - Access Request Object - Represents something that wants something else
Essentially, ACLs are used to decide when an ARO can have access to an ACO.
In order to help you understand how everything works together, lets use a semi-practical example. Imagine,
for a moment, a computer system used by a familiar group of adventurers from the fantasy novel Lord of
the Rings. The leader of the group, Gandalf, wants to manage the partys assets while maintaining a healthy
amount of privacy and security for the other members of the party. The first thing he needs to do is create a
list of the AROs (requesters) involved:
Gandalf
Aragorn
Components
563
Bilbo
Frodo
Gollum
Legolas
Gimli
Pippin
Merry
Note: Realize that ACL is not the same as authentication. ACL is what happens after a user has been
authenticated. Although the two are usually used in concert, its important to realize the difference between
knowing who someone is (authentication) and knowing what they can do (ACL).
The next thing Gandalf needs to do is make an initial list of ACOs (resources) 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 this:
x
Gandalf
Aragorn
Bilbo
Frodo
Gollum
Legolas
Gimli
Pippin
Merry
Weapons
The Ring
Allow
Salted Pork
Allow
Allow
Diplomacy
Allow
Allow
Ale
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
Allow
At first glance, it seems that this sort of system could work rather well. Assignments can be made to protect
security (only Frodo can access the ring) and protect against accidents (keeping the hobbits out of the salted
pork and weapons). It seems sufficiently fine-grained and easy to read, right?
For a small system like this, maybe a matrix setup would work. But for a growing system, or a system
with a large number of resources (ACOs) and users (AROs), a table can quickly become unwieldy. Imagine
trying to control access to the hundreds of war encampments and trying to manage them by unit. Another
drawback to matrices is that you cant create logical groups 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
564
would be tedious and error prone. Making a cascading permissions change to all members of the hobbit
group at once would be easy.
ACL is most usually implemented in a tree structure, with a tree of AROs and a tree of ACOs. By organizing
your objects in trees, you can deal out permissions in a granular fashion while maintaining a good grip on
the big picture. Being the wise leader he is, Gandalf elects to use ACL in his new system, and organizes his
objects along the following lines:
Fellowship of the Ring - Warriors
Aragorn
Legolas
Gimli
Wizards - Gandalf
Hobbits - Frodo - Bilbo - Merry - Pippin
Visitors - Gollum
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:
Fellowship of the Ring (Deny: all) - Warriors
(Allow: Weapons, Ale, Elven Rations, Salted Pork) - Aragorn - Legolas - Gimli
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 whether Pippin was allowed to access the ale, wed first consult the tree to
retrieve his path through it, 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.
ARO Node
Fellowship of the Ring
Hobbits
Pippin
Permission Info
Deny all
Allow ale
Result
Denying access to ale.
Allowing access to ale!
Still allowing ale!
Note: Since the Pippin node in the ACL tree doesnt specifically deny access to the ale ACO, the final
result is that we allow access to that ACO.
The tree also allows us to make finer adjustments for more granular control, while still keeping the ability
to make sweeping changes to groups of AROs:
Fellowship of the Ring (Deny: all) - Warriors
(Allow: Weapons, Ale, Elven Rations, Salted Pork) - Aragorn
(Allow: Diplomacy)
Legolas
Components
565
Gimli
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 the ability to make both wide-reaching permissions changes and fine-grained adjustments. This allows us to say that all hobbits can have access to ale, with one exception: Merry. To see
whether Merry can access the Ale, wed find his path in the tree: Fellowship->Hobbits->Merry. Then wed
work our way down, keeping track of ale-related permissions:
ARO Node
Fellowship of the Ring
Hobbits
Merry
Permission Info
Deny all
Allow ale
Deny Ale
Result
Denying access to ale.
Allowing access to ale!
Denying ale.
CakePHPs first ACL implementation was based on INI files stored in the CakePHP installation. While
its useful and stable, we recommend that you use the database backed ACL solution, mostly because of
its ability to create new ACOs and AROs on the fly. We meant it for usage in simple applications - and
especially for those folks who for some reason might not be using a database.
By default, CakePHPs ACL is database-driven. To enable INI-based ACL, youll need to tell CakePHP
what system youre using by updating the following lines in app/Config/core.php
// Change these lines:
Configure::write(Acl.classname, DbAcl);
Configure::write(Acl.database, default);
// to look like this:
Configure::write(Acl.classname, IniAcl);
//Configure::write(Acl.database, default);
ARO/ACO permissions are specified in /app/Config/acl.ini.php. The basic idea is that AROs are specified
in an INI section that has three properties: groups, allow, and deny.
groups: names of ARO groups of which this ARO is a member
allow: names of ACOs to which this ARO has access
deny: names of ACOs to which this ARO should be denied access
ACOs are specified in INI sections that only include the allow and deny properties.
566
As an example, lets see how the Fellowship ARO structure weve been crafting would look 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 youve got your permissions defined via the INI mechanism, you can skip to the section on checking permissions using the ACL component. Alternatively, you can keep reading to see how you would define
the same permissions using a database ACL.
Components
567
Now that weve covered INI-based ACL permissions, lets move on to the (more commonly used) database
ACL.
Getting Started The default ACL permissions implementation is powered by a database. CakePHPs
database ACL consists of a set of core models and a console application that comes with your CakePHP
installation. The models are used by CakePHP to interact with your database in order to store and retrieve
nodes in tree format. The console application is used to initialize your database and interact with your ACO
and ARO trees.
To get started, first youll 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 youve done that, use the CakePHP console to create your ACL database tables:
$ cake schema create DbAcl
Running this command will drop and re-create the tables necessary to store ACO and ARO information in
tree format. The output of the console application should look something like the following:
--------------------------------------------------------------Cake Schema Shell
--------------------------------------------------------------The following tables will be dropped.
acos
aros
aros_acos
Are you sure you want to drop the tables? (y/n)
[n] > y
Dropping tables.
acos updated.
aros updated.
aros_acos updated.
The following tables will be created.
acos
aros
aros_acos
Are you sure you want to create the tables? (y/n)
[y] > y
Creating tables.
acos updated.
aros updated.
aros_acos updated.
End create.
568
You can also use the SQL file found in app/Config/Schema/db_acl.sql, but thats 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).
Note: If youre curious about how CakePHP stores tree information in these tables, read up on modified
database tree traversal. The ACL component uses CakePHPs Tree to manage the trees inheritances. The
model class files for ACL can be found in lib/Cake/Model/.
Now that were all set up, lets work on creating some ARO and ACO trees.
Creating Access Request Objects (AROs) and Access Control Objects (ACOs) When creating new
ACL objects (ACOs and AROs), realize that there are two main ways to name and access nodes. The first
method is to link an ACL object directly to a record in your database by specifying a model name and
foreign key value. The second can be used when an object has no direct relation to a record in your database
- you can provide a textual alias for the object.
Note: In general, when youre creating a group or higher-level object, use an alias. If youre managing
access to a specific item or record in the database, use the model/foreign key method.
You create new ACL objects using the core CakePHP ACL models. In doing so, there are a number of fields
youll want to use when saving data: model, foreign_key, alias, and parent_id.
The model and foreign_key fields for an ACL object allow you to link 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 AROs foreign_key to the Users ID will allow you to link up ARO and User
information with a single User model find() call if youve 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.
An alias is just a human-readable label you can use to identify an ACL object that has no direct model
record correlation. Aliases are generally 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, well need to load up their respective classes. The easiest way to do
this is to include CakePHPs ACL Component in your controllers $components array:
public $components = array(Acl);
Once weve got that done, lets see some examples of creating these objects. The following code could be
placed in a controller action:
Note: While the examples here focus on ARO creation, the same techniques can be used to create an ACO
tree.
Remaining with our Fellowship example, lets first create our ARO groups. Because they wont have specific
records tied to them, well use aliases to create the ACL objects. Here, we create them via a controller action,
Components
569
Once weve got the groups, we can use the ACL console application to verify the tree structure.
$ cake acl view aro
Aro tree:
--------------------------------------------------------------[1]warriors
[2]wizards
[3]hobbits
[4]visitors
---------------------------------------------------------------
The tree is still simple at this point, but at least weve got some verification that weve got four top-level
nodes. Lets add some children to those ARO nodes by putting our specific user AROs under these groups.
Every good citizen of Middle Earth has an account in our new system, so well tie these ARO records to
570
Components
571
Note: Typically you wont supply both an alias and a model/foreign_key, but were using both here to make
the structure of the tree easier to read for demonstration purposes.
The output of that console application command should now be a little more interesting. Lets give it a try:
$ cake acl view aro
Aro tree:
--------------------------------------------------------------[1]warriors
[5]Aragorn
[6]Legolas
[7]Gimli
[2]wizards
[8]Gandalf
[3]hobbits
[9]Frodo
572
[10]Bilbo
[11]Merry
[12]Pippin
[4]visitors
[13]Gollum
---------------------------------------------------------------
Now that weve got our ARO tree setup properly, lets discuss a possible approach for structuring an ACO
tree. While we can put together a more abstract representation of our ACOs, its often more practical to
model an ACO tree after CakePHPs Controller/Action setup. Weve got five main objects were handling
in this Fellowship scenario. The natural setup for this in a CakePHP application consists of a group of
models, and ultimately the controllers that manipulate them. Beyond the controllers themselves, well want
to control access to specific actions in those controllers.
Lets set up an ACO tree that will mimic a CakePHP app setup. Since we have five ACOs, well create an
ACO tree that should end up looking something like the following:
Weapons
Rings
PorkChops
DiplomaticEfforts
Ales
You can create children nodes under each of these five main ACOs, but using CakePHPs 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. Well see how these are used later on when we discuss how to assign
permissions.
Since youre 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.
Assigning Permissions After creating our ACOs and AROs, we can finally assign permissions between
the two groups. This is done using CakePHPs core Acl component. Lets continue with our example.
Here well work with Acl permisions in the context of a controller action. Lets set up some basic permissions using the AclComponent in an action inside our controller.
class SomethingsController extends AppController {
// You might want to place this in the AppController
// instead, but here works great too.
public $components = array(Acl);
public function index() {
// Allow warriors complete access to weapons
Components
573
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 were just addressing ACOs and AROs by their aliases.
Notice the usage of the third parameter? One nice thing about the CakePHP ACL setup is that permissions
contain four built-in properties related to CRUD (create, read, update, and delete) actions for convenience.
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 we want to deny other warriors in the group the ability to delete
Weapons records. Were using the alias syntax to address the AROs above, but you might want to use the
model/foreign key syntax yourself. What we have above is equivalent to this:
// 6342 = Legolas
// 1564 = Gimli
$this->Acl->deny(
array(model => User, foreign_key => 6342),
Weapons,
delete
);
$this->Acl->deny(
array(model => User, foreign_key => 1564),
Weapons,
delete
);
Note:
Addressing a node using the alias syntax uses a slash-delimited string
(/users/employees/developers). Addressing a node using model/foreign key syntax uses an array
with two parameters: array(model => User, foreign_key => 8282).
The next section will help us validate our setup by using the AclComponent to check the permissions weve
just set up.
Checking Permissions: The ACL Component Lets use the AclComponent to make sure dwarves and
elves cant remove things from the armory. At this point, we should be able to use the AclComponent to
make a check between the ACOs and AROs weve created. The basic syntax for making a permissions
check is:
574
Weapons);
Weapons, create);
Weapons, read);
Weapons, update);
Weapons, delete);
The usage here is for demonstration, but this type of checking can be used to decide whether to allow an
action, show an error message, or redirect the user to a login.
Helpers
CakePHP features a number of helpers that aid in view creation. They assist in creating well-formed markup
(including forms), aid in formatting text, times and numbers, and can even integrate with popular JavaScript
libraries. Here is a summary of the built-in helpers.
Read Helpers to learn more about helpers, their API, and how you can create and use your own helpers.
Helpers
CakePHP features a number of helpers that aid in view creation. They assist in creating well-formed markup
(including forms), aid in formatting text, times and numbers, and can even integrate with popular JavaScript
libraries. Here is a summary of the built-in helpers.
Read Helpers to learn more about helpers, their API, and how you can create and use your own helpers.
CacheHelper
class CacheHelper(View $view, array $settings = array())
Helpers
575
The Cache helper assists in caching entire layouts and views, saving time repetitively retrieving data. View
Caching in CakePHP temporarily stores parsed layouts and views as simple PHP + HTML files. 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. The CacheHelper then uses helper callbacks to process the file and output to generate the cache file.
When a URL is requested, CakePHP 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 CakePHP doesnt find a cached view, or the cache has expired for the requested URL it
continues to process the request normally.
Using the Helper
There are two steps you have to take before you can use the CacheHelper.
First in your
APP/Config/core.php uncomment the Configure write call for Cache.check. This will tell
CakePHP to check for, and generate view cache files when handling requests.
Once youve uncommented the Cache.check line you will need to add the helper to your controllers
$helpers array:
class PostsController extends AppController {
public $helpers = array(Cache);
}
You will also need to add the CacheDispatcher to your dispatcher filters in your bootstrap:
Configure::write(Dispatcher.filters, array(
CacheDispatcher
));
New in version 2.3: If you have a setup with multiple domains or languages you can use Configure::write(Cache.viewPrefix, YOURPREFIX); to store the view cache files prefixed.
Additional configuration options CacheHelper has a few additional configuration options you can use
to tune and tweak its behavior. This is done through the $cacheAction variable in your controllers.
$cacheAction should be set to an array which contains the actions you want cached, and the duration in
seconds you want those views cached. The time value can be expressed in a strtotime() format (e.g.
1 hour, or 3 minutes).
Using the example of an ArticlesController, that receives a lot of traffic that needs to be cached:
public $cacheAction = array(
view => 36000,
index => 48000
);
This will cache the view action 10 hours, and the index action 13 hours. By making $cacheAction a
strtotime() friendly value you can cache every action in the controller:
576
You can also enable controller/component callbacks for cached views created with CacheHelper. To do
so you must use the array format for $cacheAction and create an array like the following:
public $cacheAction = array(
view => array(callbacks => true, duration => 21600),
add => array(callbacks => true, duration => 36000),
index => array(callbacks => true, duration => 48000)
);
By setting callbacks => true you tell CacheHelper that you want the generated files to create the
components and models for the controller. Additionally, fire the component initialize, controller beforeFilter,
and component startup callbacks.
Note: Setting callbacks => true partly defeats the purpose of caching. This is also the reason it is
disabled by default.
There will be times when you dont want an entire view cached. For example, certain parts of the page may
look different whether a user is currently logged in or browsing your site as a guest.
To indicate blocks of content that are not to be cached, wrap them in <!--nocache-->
<!--/nocache--> like so:
<!--nocache-->
<?php if ($this->Session->check(User.name)): ?>
Welcome, <?php echo h($this->Session->read(User.name)); ?>.
<?php else: ?>
<?php echo $this->Html->link(Login, users/login); ?>
<?php endif; ?>
<!--/nocache-->
Note: You cannot use nocache tags in elements. Since there are no callbacks around elements, they
cannot be cached.
It should be noted that once an action is cached, the controller method for the action will not be called. When
a cache file is created, the request object, and view variables are serialized with PHPs serialize().
Warning: If you have view variables that contain un-serializable content such as SimpleXML objects,
resource handles, or closures you might not be able to use view caching.
It is important to remember that CakePHP will clear a cached view if a model used in the cached view is
modified. For example, if a cached view uses data from the Post model, and there has been an INSERT,
Helpers
577
UPDATE, or DELETE query made to a Post, the cache for that view is cleared, and new content is generated
on the next request.
Note: This automatic cache clearing requires the controller/model name to be part of the URL. If youve
used routing to change your URLs this feature will not work.
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().
FormHelper
class FormHelper(View $view, array $settings = array())
The FormHelper does most of the heavy lifting in form creation. 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 using conventions, or you can use specific methods to get only what
you need.
Creating Forms
The first method youll need to use in order to take advantage of the FormHelper is create(). This special
method outputs an opening form tag.
FormHelper::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 the current URL. The default method for
form submission is POST. The form element is also 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, Id see something like the following output in the rendered
view:
<form id="UserAddForm" method="post" action="/users/add">
Note:
You can also pass false for $model.
This will place your form
data into the array:
$this->request->data (instead of in the sub-array:
$this->request->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 forms 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:
// If you are on /recipes/add
echo $this->Form->create(Recipe);
Output:
578
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->request->data property to automatically detect whether to create an add or edit form. If $this->request->data
contains an array element named after the forms model, and that array contains a non-empty value of
the models primary key, then the FormHelper will create an edit form for that record. For example,
if we browse to http://site.com/recipes/edit/5, we would get the following:
// Controller/RecipesController.php:
public function edit($id = null) {
if (empty($this->request->data)) {
$this->request->data = $this->Recipe->findById($id);
} else {
// Save logic goes here
}
}
// View/Recipes/edit.ctp:
// Since $this->request->data[Recipe][id] = 5,
// we will get an edit form
<?php echo $this->Form->create(Recipe); ?>
Output:
<form id="RecipeEditForm" method="post" action="/recipes/edit/5">
<input type="hidden" name="_method" value="PUT" />
Note: Since this is an edit form, a hidden input field is generated to override the default HTTP
method.
When creating forms for models in plugins, you should always use plugin syntax when creating a
form. This will ensure the form is correctly generated:
echo $this->Form->create(ContactManager.Contact);
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.
Changed in version 2.0: The default URL for all forms, is now the current URL including passed,
named, and querystring parameters. You can override this default by supplying $options[url]
in the second parameter of $this->Form->create().
Options for create() There are a number of options for create():
$options[type] 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:
Helpers
579
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:
echo $this->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.
$options[action] The action key allows you to point the form to a specific action in your
current controller. For example, if youd like to point the form to the login() action of the current
controller, you would supply an $options array like the following:
echo $this->Form->create(User, array(action => login));
Output:
<form id="UserLoginForm" method="post" action="/users/login">
$options[url] If the desired form action isnt 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:
echo $this->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.
580
$options[default] If default has been set to boolean false, the forms 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 forms default behavior so you can grab
the data and submit it via AJAX instead.
$options[inputDefaults] You can declare a set of default options for input() with the
inputDefaults key to customize your default input creation:
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:
echo $this->Form->input(password); // No div, no label
// has a label element
echo $this->Form->input(
username,
array(label => Username)
);
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:
<?php echo $this->Form->end(Finish); ?>
Will output:
<div class="submit">
<input type="submit" value="Finish" />
</div>
</form>
Helpers
581
$options = array(
label => Update,
div => array(
class => glass-pill,
)
);
echo $this->Form->end($options);
Will output:
<div class="glass-pill"><input type="submit" value="Update" name="Update">
</div>
There are a few ways to create form inputs with the FormHelper. Well start by looking at input(). This
method will automatically inspect the model field it has been supplied in order to create an appropriate input
for that field. Internally input() delegates to other methods in FormHelper.
FormHelper::input(string $fieldName, array $options = array())
Creates the following elements given a particular Model.field:
Wrapping div.
Label element
Input element(s)
Error element with message if applicable.
The type of input created depends on the column datatype:
Column Type Resulting Form Field
string (char, varchar, etc.) text
boolean, tinyint(1) checkbox
text textarea
text, with name of password, passwd, or psword password
text, with name of email email
text, with name of tel, telephone, or phone tel
14
http://api.cakephp.org/2.4/class-FormHelper.html
582
//text
//password
//day, month, year, hour, minute,
//meridian
//textarea
echo $this->Form->end(Add);
Besides the specific options for input() found below, you can specify any option for the input type & any HTML attribute (for instance onfocus). For more information on $options and
$htmlAttributes see HtmlHelper.
Assuming that User hasAndBelongsToMany Group. In your controller, set a camelCase plural variable (group -> groups in this case, or ExtraFunkyModel -> extraFunkyModels) with the select options.
In the controller action you would put the following:
Helpers
583
$this->set(groups, $this->User->Group->find(list));
And in the view a multiple select can be created with this simple code:
echo $this->Form->input(Group);
If you want to create a select field while using a belongsTo - or hasOne - Relation, you can add the
following to your Users-controller (assuming your User belongsTo Group):
$this->set(groups, $this->User->Group->find(list));
If your model name consists of two or more words, e.g., UserGroup, when passing the data using
set() you should name your data in a pluralised and camelCased format as follows:
$this->set(userGroups, $this->UserGroup->find(list));
// or
$this->set(
reallyInappropriateModelNames,
$this->ReallyInappropriateModelName->find(list)
);
Note:
Try to avoid using FormHelper::input() to generate submit buttons.
FormHelper::submit() instead.
Use
In addition to fields control, inputs() allows you to use a few additional options.
fieldset Set to false to disable the fieldset. If a string is supplied it will be used as the class
name for the fieldset element.
legend Set to false to disable the legend for the generated input set. Or supply a string to
customize the legend text.
Field naming conventions The Form helper is pretty smart. Whenever you specify a field name with the
form helper methods, itll automatically use the current model name to build an input with a format like the
following:
584
This allows you to omit the model name when generating inputs for the model that the form was created for.
You can create inputs for associated models, or arbitrary models by passing in Modelname.fieldname as the
first parameter:
echo $this->Form->input(Modelname.fieldname);
If you need to specify multiple fields using the same field name, thus creating an array that can be saved in
one shot with saveAll(), use the following convention:
echo $this->Form->input(Modelname.0.fieldname);
echo $this->Form->input(Modelname.1.fieldname);
Output:
<input type="text" id="Modelname0Fieldname"
name="data[Modelname][0][fieldname]">
<input type="text" id="Modelname1Fieldname"
name="data[Modelname][1][fieldname]">
FormHelper uses several field-suffixes internally for datetime input creation. If you are using fields named
year, month, day, hour, minute, or meridian and having issues getting the correct input, you can
set the name attribute to override the default behavior:
echo $this->Form->input(Model.year, array(
type => text,
name => data[Model][year]
));
Options FormHelper::input() supports a large number of options. In addition to its own options
input() accepts options for the generated input types, as well as HTML attributes. The following will
cover the options specific to FormHelper::input().
$options[type] You can force the type of an input, overriding model introspection, by specifying a type. In addition to the field types found in the Creating form elements, you can also create
file, password, and any type supported by HTML5:
echo $this->Form->input(field, array(type => file));
echo $this->Form->input(email, array(type => email));
Output:
<div class="input file">
<label for="UserField">Field</label>
<input type="file" name="data[User][field]" value="" id="UserField" />
</div>
<div class="input email">
<label for="UserEmail">Email</label>
<input type="email" name="data[User][email]" value="" id="UserEmail" />
</div>
Helpers
585
$options[div] Use this option to set attributes of the inputs containing div. Using a string
value will set the divs class name. An array will set the divs attributes to those specified by the
arrays keys/values. Alternatively, you can set this key to false to disable the output of the div.
Setting the class name:
echo $this->Form->input(User.name, array(
div => class_name
));
Output:
<div class="class_name">
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
Output:
<div class="input text" id="mainDiv" title="Div Title"
style="display:block">
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
</div>
Output:
<label for="UserName">Name</label>
<input name="data[User][name]" type="text" value="" id="UserName" />
$options[label] Set this key to the string you would like to be displayed within the label that
usually accompanies the input:
echo $this->Form->input(User.name, array(
label => The User Alias
));
Output:
<div class="input">
<label for="UserName">The User Alias</label>
586
Alternatively, set this key to false to disable the output of the label:
echo $this->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:
echo $this->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>
$options[error] 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.
To disable error message output & field classes set the error key to false:
$this->Form->input(Model.field, array(error => false));
To disable only the error message, but retain the field classes, set the errorMessage key to false:
$this->Form->input(Model.field, array(errorMessage => false));
To modify the wrapping element type and its class, use the following format:
$this->Form->input(Model.field, array(
error => array(
attributes => array(wrap => span, class => bzzz)
)
));
To prevent HTML being automatically escaped in the error message output, set the escape suboption
to false:
Helpers
587
$this->Form->input(Model.field, array(
error => array(
attributes => array(escape => false)
)
));
To override the model error messages use an array with the keys matching the validation rule names:
$this->Form->input(Model.field, array(
error => array(tooShort => __(This is not long enough))
));
As seen above you can set the error message for each validation rule you have in your models. In
addition you can provide i18n messages for your forms.
New in version 2.3: Support for the errorMessage option was added in 2.3
$options[before],
$options[after]
$options[between],
$options[separator],
and
Use these keys if you need to inject some markup inside the output of the input() method:
echo $this->Form->input(field, array(
before => --before--,
after => --after--,
between => --between---
));
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 inputs the separator attribute can be used to inject markup to separate each input/label pair:
echo $this->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--
588
For date and datetime type elements the separator attribute can be used to change the string
between select elements. Defaults to -.
$options[format] The ordering of the HTML generated by FormHelper is controllable as
well. The format options supports an array of strings describing the template you would like said
element to follow. The supported array keys are: array(before, input, between,
label, after,error).
$options[inputDefaults] If you find yourself repeating the same options in multiple input() calls, you can use inputDefaults to keep your code dry:
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:
// No div, no label
echo $this->Form->input(password);
// has a label element
echo $this->Form->input(username, array(label => Username));
If you need to later change the defaults you can use FormHelper::inputDefaults().
Generating specific types of inputs
In addition to the generic input() method, FormHelper has specific methods for generating a number
of different types of inputs. These can be used to generate just the input widget itself, and combined with
other methods like label() and error() to generate fully custom form layouts.
Common options Many of the various input element methods support a common set of options. All of
these options are also supported by input(). To reduce repetition the common options shared by all input
methods are as follows:
$options[class] You can set the class name for an input:
echo $this->Form->input(title, array(class => custom-class));
$options[id] Set this key to force the value of the DOM id for the input.
Helpers
589
$options[default] 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:
echo $this->Form->input(ingredient, array(default => Sugar));
Note:
You cannot use default to check a checkbox - instead you might set the value in
$this->request->data in your controller, or set the input option checked to true.
Date and datetime fields default values can be set by using the selected key.
Beware of using false to assign a default value. A false value is used to disable/exclude options of
an input field, so default => false would not set any value at all. Instead use default
=> 0.
In addition to the above options, you can mixin any HTML attribute you wish to use. Any non-special
option name will be treated as an HTML attribute, and applied to the generated HTML input element.
Options for select, checkbox and radio inputs
$options[selected] 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:
echo $this->Form->input(close_time, array(
type => time,
selected => 13:30:00
));
Note: The selected key for date and datetime inputs may also be a UNIX timestamp.
$options[empty] 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:
echo $this->Form->input(field, array(
options => array(1, 2, 3, 4, 5),
empty => (choose one)
));
Output:
590
<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>
Note: If you need to set the default value in a password field to blank, use value => instead.
Options can also supplied as key-value pairs.
$options[hiddenField] For certain input types (checkboxes, radios) a hidden input is created so that the key in $this->request->data will exist even without a value specified:
<input type="hidden" name="data[Post][Published]" id="PostPublished_"
value="0" />
<input type="checkbox" name="data[Post][Published]" value="1"
id="PostPublished" />
Which outputs:
<input type="checkbox" name="data[Post][Published]" value="1"
id="PostPublished" />
If you want to create multiple blocks of inputs on a form that are all grouped together, you should use
this parameter on all inputs except the first. If the hidden input is on the page in multiple places, only
the last group of inputs values will be saved
In this example, only the tertiary colors would be passed, and the primary colors would be overridden:
<h2>Primary Colors</h2>
<input type="hidden" name="data[Color][Color]" id="Colors_" value="0" />
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsRed" />
<label for="ColorsRed">Red</label>
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsBlue" />
<label for="ColorsBlue">Blue</label>
<input type="checkbox" name="data[Color][Color][]" value="5"
id="ColorsYellow" />
<label for="ColorsYellow">Yellow</label>
<h2>Tertiary Colors</h2>
<input type="hidden" name="data[Color][Color]" id="Colors_" value="0" />
Helpers
591
Disabling the hiddenField on the second input group would prevent this behavior.
You can set a different hidden field value other than 0 such as N:
echo $this->Form->checkbox(published, array(
value => Y,
hiddenField => N,
));
Datetime options
$options[timeFormat] Used to specify the format of the select inputs for a time-related set
of inputs. Valid values include 12, 24, and null.
$options[dateFormat] Used to specify the format of the select inputs for a date-related set
of inputs. Valid values include any combination of D, M and Y or null. The inputs will be put
in the order defined by the dateFormat option.
$options[minYear], $options[maxYear] Used in combination with a
date/datetime input. Defines the lower and/or upper end of values shown in the years select
field.
$options[orderYear] Used in combination with a date/datetime input. Defines the order in
which the year values will be set. Valid values include asc, desc. The default value is desc.
$options[interval] This option specifies the number of minutes between each option in the
minutes select box:
echo $this->Form->input(Model.time, array(
type => time,
interval => 15
));
Would create 4 options in the minute select. One for each 15 minutes.
$options[round] Can be set to up or down to force rounding in either direction. Defaults to
null which rounds half up according to interval.
New in version 2.4.
592
All elements are created under a form for the User model as in the examples above. For this
reason, the HTML code generated will contain attributes that reference to the User model. Ex:
name=data[User][username], id=UserUsername
FormHelper::label(string $fieldName, string $text, array $options)
Create a label element. $fieldName is used for generating the DOM id. If $text is undefined,
$fieldName will be used to inflect the labels text:
echo $this->Form->label(User.name);
echo $this->Form->label(User.name, Your username);
Output:
<label for="UserName">Name</label>
<label for="UserName">Your username</label>
$options can either be an array of HTML attributes, or a string that will be used as a class name:
echo $this->Form->label(User.name, null, array(id => user-label));
echo $this->Form->label(User.name, Your username, highlight);
Output:
<label for="UserName" id="user-label">Name</label>
<label for="UserName" class="highlight">Your username</label>
Will output:
<input name="data[User][username]" type="text" class="users"
id="UserUsername" />
Will output:
<input name="data[User][password]" value="" id="UserPassword"
type="password" />
Helpers
593
echo $this->Form->hidden(id);
Will output:
<input name="data[User][id]" id="UserId" type="hidden" />
If the form is edited (that is, the array $this->request->data will contain the information
saved for the User model), the value corresponding to id field will automatically be added to the
HTML generated. Example for data[User][id] = 10:
<input name="data[User][id]" id="UserId" type="hidden" />
Changed in version 2.0: Hidden fields no longer remove the class attribute. This means that if there
are validation errors on hidden fields, the error-field class name will be applied.
FormHelper::textarea(string $fieldName, array $options)
Creates a textarea input field.
echo $this->Form->textarea(notes);
Will output:
<textarea name="data[User][notes]" id="UserNotes"></textarea>
If the form is edited (that is, the array $this->request->data will contain the information
saved for the User model), the value corresponding to notes field will automatically be added to
the HTML generated. Example:
<textarea name="data[User][notes]" id="UserNotes">
This text is to be edited.
</textarea>
Note: The textarea input type allows for the $options attribute of escape which determines whether or not the contents of the textarea should be escaped. Defaults to true.
echo $this->Form->textarea(notes, array(escape => false);
// OR....
echo $this->Form->input(
notes,
array(type => textarea, escape => false)
);
Options
In addition to the Common options, textarea() supports a few specific options:
$options[rows], $options[cols] These two keys specify the number of rows
and columns:
echo $this->Form->textarea(
textarea,
array(rows => 5, cols => 5)
);
594
Output:
<textarea name="data[Form][textarea]" cols="5" rows="5" id="FormTextarea">
</textarea>
Will output:
<input type="hidden" name="data[User][done]" value="0" id="UserDone_" />
<input type="checkbox" name="data[User][done]" value="1" id="UserDone" />
It is possible to specify the value of the checkbox by using the $options array:
echo $this->Form->checkbox(done, array(value => 555));
Will output:
<input type="hidden" name="data[User][done]" value="0" id="UserDone_" />
<input type="checkbox" name="data[User][done]" value="555" id="UserDone" />
Will output:
<input type="checkbox" name="data[User][done]" value="1" id="UserDone" />
Will output:
Helpers
595
If for some reason you dont want the hidden input, setting $attributes[value] to a selected
value or boolean false will do just that.
Changed in version 2.1: The $attributes[disabled] option was added in 2.1.
FormHelper::select(string $fieldName, array $options, array $attributes)
Creates a select element, populated with the items in $options, with the option specified
by $attributes[value] shown as selected by default. Set the empty key in the
$attributes variable to false to turn off the default empty option:
$options = array(M => Male, F => Female);
echo $this->Form->select(gender, $options);
Will output:
<select name="data[User][gender]" id="UserGender">
<option value=""></option>
<option value="M">Male</option>
<option value="F">Female</option>
</select>
The select input type allows for a special $option attribute called escape which accepts a
bool and determines whether to HTML entity encode the contents of the select options. Defaults to
true:
$options = array(M => Male, F => Female);
echo $this->Form->select(gender, $options, array(escape => false));
$attributes[options] 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:
echo $this->Form->select(field, array(1,2,3,4,5));
Output:
<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>
Output:
<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>
If you would like to generate a select with optgroups, just pass data in hierarchical format. This
works on multiple checkboxes and radio buttons too, but instead of optgroups wraps elements in
fieldsets:
$options = array(
Group 1 => array(
Value 1 => Label 1,
Value 2 => Label 2
),
Group 2 => array(
Value 3 => Label 3
)
);
echo $this->Form->select(field, $options);
Output:
<select name="data[User][field]" id="UserField">
<optgroup label="Group 1">
<option value="Value 1">Label 1</option>
<option value="Value 2">Label 2</option>
</optgroup>
<optgroup label="Group 2">
<option value="Value 3">Label 3</option>
</optgroup>
</select>
$attributes[multiple] If multiple has been set to true for an input that outputs a
select, the select will allow multiple selections:
echo $this->Form->select(
Model.field,
$options,
array(multiple => true)
);
Helpers
597
Output:
<div class="input select">
<label for="ModelField">Field</label>
<input name="data[Model][field]" value="" id="ModelField"
type="hidden">
<div class="checkbox">
<input name="data[Model][field][]" value="Value 1"
id="ModelField1" type="checkbox">
<label for="ModelField1">Label 1</label>
</div>
<div class="checkbox">
<input name="data[Model][field][]" value="Value 2"
id="ModelField2" type="checkbox">
<label for="ModelField2">Label 2</label>
</div>
</div>
Output:
<div class="input select">
<label for="ModelField">Field</label>
<input name="data[Model][field]" value="" id="ModelField"
type="hidden">
<div class="checkbox">
<input name="data[Model][field][]" disabled="disabled"
value="Value 1" id="ModelField1" type="checkbox">
<label for="ModelField1">Label 1</label>
</div>
<div class="checkbox">
<input name="data[Model][field][]" value="Value 2"
id="ModelField2" type="checkbox">
<label for="ModelField2">Label 2</label>
</div>
</div>
598
Changed in version 2.3: Support for arrays in $attributes[disabled] was added in 2.3.
FormHelper::file(string $fieldName, array $options)
To add a file upload field to a form, you must first make sure that the form enctype is set to
multipart/form-data, so start off with a create function such as the following:
echo $this->Form->create(Document, array(
enctype => multipart/form-data
));
// OR
echo $this->Form->create(Document, array(type => file));
Next add either of the two lines to your form view file:
echo $this->Form->input(Document.submittedfile, array(
between => <br />,
type => file
));
// OR
echo $this->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:
$this->request->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 uploads15 .
Validating Uploads Below is an example validation method you could define in your model to validate
whether a file has been successfully uploaded:
public 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]);
15
http://php.net/features.file-upload
Helpers
599
}
return false;
}
Will output:
<form enctype="multipart/form-data" method="post" action="/users/add">
<input name="data[User][avatar]" value="" id="UserAvatar" type="file">
Note: When using $this->Form->file(), remember to set the form encoding-type, by setting the
type option to file in $this->Form->create()
Will output:
<div class="submit"><input value="Submit" type="submit"></div>
You can also pass a relative or absolute URL to an image for the caption parameter instead of caption
text.
echo $this->Form->submit(ok.png);
Will output:
<div class="submit"><input type="image" src="/img/ok.png"></div>
Setting
600
echo
echo
echo
echo
$this->Form->button(A Button);
$this->Form->button(Another Button, array(type => button));
$this->Form->button(Reset the Form, array(type => reset));
$this->Form->button(Submit Form, array(type => submit));
Will output:
<button
<button
<button
<button
type="submit">A Button</button>
type="button">Another Button</button>
type="reset">Reset the Form</button>
type="submit">Submit Form</button>
The button input type supports the escape option, which accepts a bool and determines whether
to HTML entity encode the $title of the button. Defaults to false:
echo $this->Form->button(Submit Form, array(
type => submit,
escape => true
));
Helpers
601
You can specify not to display empty values by setting array(empty => false) in the attributes
parameter. It will also pre-select the fields with the current datetime.
FormHelper::year(string $fieldName, int $minYear, int $maxYear, array $attributes)
Creates a select element populated with the years from $minYear to $maxYear. HTML attributes
may be supplied in $attributes. If $attributes[empty] is false, the select will not include an
empty option:
echo $this->Form->year(purchased, 2000, date(Y));
Will output:
<select name="data[User][purchased][year]" id="UserPurchasedYear">
<option value=""></option>
<option value="2009">2009</option>
<option value="2008">2008</option>
<option value="2007">2007</option>
<option value="2006">2006</option>
<option value="2005">2005</option>
<option value="2004">2004</option>
<option value="2003">2003</option>
<option value="2002">2002</option>
<option value="2001">2001</option>
<option value="2000">2000</option>
</select>
Will output:
<select name="data[User][mob][month]" id="UserMobMonth">
<option value=""></option>
<option value="01">January</option>
<option value="02">February</option>
<option value="03">March</option>
<option value="04">April</option>
<option value="05">May</option>
<option value="06">June</option>
<option value="07">July</option>
<option value="08">August</option>
<option value="09">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
You can pass in your own array of months to be used by setting the monthNames attribute, or have
months displayed as numbers by passing false. (Note: the default months are internationalized and
can be translated using localization.):
602
Will output:
<select name="data[User][created][day]" id="UserCreatedDay">
<option value=""></option>
<option value="01">1</option>
<option value="02">2</option>
<option value="03">3</option>
...
<option value="31">31</option>
</select>
Helpers
603
FormHelper::tagIsInvalid()
Returns false if given form field described by the current entity has no errors. Otherwise it returns the
validation message.
Setting Defaults for all fields
All inputs created from that point forward will inherit the options declared in inputDefaults. You can override
the default options by declaring the option in the input() call:
echo $this->Form->input(password); // No div, no label with class fancy
// has a label element same defaults
echo $this->Form->input(
username,
array(label => Username)
);
SecurityComponent offers several features that make your forms safer and more secure. By simply
including the SecurityComponent in your controller, youll automatically benefit from CSRF and form
tampering features.
As mentioned previously when using SecurityComponent, you should always close your forms using
FormHelper::end(). This will ensure that the special _Token inputs are generated.
FormHelper::unlockField($name)
Unlocks a field making it exempt from the SecurityComponent field hashing. This also allows
the fields to be manipulated by JavaScript. The $name parameter should be the entity name for the
input:
$this->Form->unlockField(User.id);
The $selected parameter was removed from several methods in FormHelper. All methods now support
a $attributes[value] key now which should be used in place of $selected. This change
simplifies the FormHelper methods, reducing the number of arguments, and reduces the duplication that
$selected created. The effected methods are:
FormHelper::select()
FormHelper::dateTime()
FormHelper::year()
FormHelper::month()
FormHelper::day()
FormHelper::hour()
FormHelper::minute()
FormHelper::meridian()
Default URLs on forms is the current action
The default URL for all forms, is now the current URL including passed, named, and querystring parameters. You can override this default by supplying $options[url] in the second parameter of
$this->Form->create()
FormHelper::hidden()
Hidden fields no longer remove the class attribute. This means that if there are validation errors on hidden
fields, the error-field class name will be applied.
HtmlHelper
class HtmlHelper(View $view, array $settings = array())
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.
Many HtmlHelper methods 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:
Desired attributes: <tag class="someClass" />
Array parameter: array(class => someClass)
Desired attributes: <tag name="foo" value="bar" />
Array parameter: array(name => foo, value => bar)
Note: The HtmlHelper is available in all views by default. If youre getting an error informing you that
it isnt there, its usually due to its name being missing from a manually configured $helpers controller
variable.
Helpers
605
The most important task the HtmlHelper accomplishes is creating well formed markup. Dont be afraid to
use it often - you can cache views in CakePHP in order to save some CPU cycles when views are being
rendered and delivered. This section will cover some of the methods of the HtmlHelper and how to use
them.
HtmlHelper::charset($charset=null)
Parameters
$charset (string) Desired character set. If null, the value of App.encoding
will be used.
Used to create a meta tag specifying the documents character. Defaults to UTF-8
Example use:
echo $this->Html->charset();
Will output:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
Alternatively,
echo $this->Html->charset(ISO-8859-1);
Will output:
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
Will output:
606
Will output:
<link rel="stylesheet" type="text/css" href="/css/forms.css" />
<link rel="stylesheet" type="text/css" href="/css/tables.css" />
<link rel="stylesheet" type="text/css" href="/css/menu.css" />
You can include CSS files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/css/toolbar.css You could use the following:
echo $this->Html->css(DebugKit.toolbar.css);
If you want to include a CSS file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/css/Blog.common.css, you would:
.. versionchanged:: 2.4
translated value
text/html
application/rss+xml
application/atom+xml
image/x-icon
<?php
echo $this->Html->meta(
favicon.ico,
/favicon.ico,
array(type => icon)
);
Helpers
607
?>
// Output (line breaks added)
<link
href="http://example.com/favicon.ico"
title="favicon.ico" type="image/x-icon"
rel="alternate"
/>
<?php
echo $this->Html->meta(
Comments,
/comments/index.rss,
array(type => rss)
);
?>
// Output (line breaks added)
<link
href="http://example.com/comments/index.rss"
title="Comments"
type="application/rss+xml"
rel="alternate"
/>
This method can also be used to add the meta keywords and descriptions. Example:
<?php
echo $this->Html->meta(
keywords,
enter any meta keyword here
);
?>
// Output
<meta name="keywords" content="enter any meta keyword here" />
<?php
echo $this->Html->meta(
description,
enter any meta description here
);
?>
// Output
<meta name="description" content="enter any meta description here" />
If you want to add a custom meta tag then the first parameter should be set to an array. To output a
robots noindex tag use the following code:
echo $this->Html->meta(array(name => robots, content => noindex));
608
Returns a (X)HTML doctype tag. Supply the doctype according to the following table:
type
html4-strict
html4-trans
html4-frame
html5
xhtml-strict
xhtml-trans
xhtml-frame
xhtml11
translated value
HTML4 Strict
HTML4 Transitional
HTML4 Frameset
HTML5
XHTML1 Strict
XHTML1 Transitional
XHTML1 Frameset
XHTML1.1
echo $this->Html->docType();
// Outputs:
// <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
//
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
echo $this->Html->docType(html5);
// Outputs: <!DOCTYPE html>
echo $this->Html->docType(html4-trans);
// Outputs:
// <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
//
"http://www.w3.org/TR/html4/loose.dtd">
Will output:
background:#633; border-bottom:1px solid #000; padding:10px;
Helpers
609
Creates a formatted image tag. The path supplied should be relative to /app/webroot/img/.
echo $this->Html->image(cake_logo.png, array(alt => CakePHP));
Will output:
<img src="/img/cake_logo.png" alt="CakePHP" />
To create an image link specify the link destination using the url option in $htmlAttributes.
echo $this->Html->image("recipes/6.jpg", array(
"alt" => "Brownies",
url => array(controller => recipes, action => view, 6)
));
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
If you are creating images in emails, or want absolute paths to images you can use the fullBase
option:
echo $this->Html->image("logo.png", array(fullBase => true));
Will output:
<img src="http://example.com/img/logo.jpg" alt="" />
You can include image files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/img/icon.png You could use the following:
echo $this->Html->image(DebugKit.icon.png);
If you want to include a image file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/js/Blog.icon.png, you would:
echo $this->Html->image(Blog.icon.png, array(plugin => false));
Changed in version 2.1: The fullBase option was added. Support for plugin syntax was added.
HtmlHelper::link(string $title, mixed $url = null, array $options = array(), string $confirmMessage = false)
param string $title The text to display as the body of the link.
param mixed $url Either the string location, or a routing array.
param array $options An array of html attributes.
General purpose method for creating HTML links. Use $options to specify attributes
for the element and whether or not the $title should be escaped.
610
echo $this->Html->link(
Enter,
/pages/home,
array(class => button, target => _blank)
);
Will output:
<a href="/pages/home" class="button" target="_blank">Enter</a>
Will output:
<a href="http://www.yourdomain.com/dashboards/index">Dashboard</a>
Will output:
<a href="/recipes/delete/6"
onclick="return confirm(
Are you sure you wish to delete this recipe?
);">
Delete
</a>
Will output:
Helpers
611
When using named parameters, use the array syntax and include names for ALL parameters
in the URL. Using the string syntax for paramters (i.e. recipes/view/6/comments:false
will result in the colon characters being HTML escaped and the link will not work as
desired.
<?php
echo $this->Html->link(
$this->Html->image("recipes/6.jpg", array("alt" => "Brownies")),
array(
controller => recipes,
action => view,
id => 6,
comments => false
)
);
Will output:
<a href="/recipes/view/id:6/comments:false">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
HTML special characters in $title will be converted to HTML entities. To disable this
conversion, set the escape option to false in the $options array.
<?php
echo $this->Html->link(
$this->Html->image("recipes/6.jpg", array("alt" => "Brownies")),
"recipes/view/6",
array(escape => false)
);
Will output:
<a href="/recipes/view/6">
<img src="/img/recipes/6.jpg" alt="Brownies" />
</a>
Setting escape to false will also disable escaping of attributes of the link. As of 2.4 you
can use the option escapeTitle to disable just escaping of title and not the attributes.
<?php
echo $this->Html->link(
$this->Html->image(recipes/6.jpg, array(alt => Brownies)),
recipes/view/6,
array(escapeTitle => false, title => hi "howdy")
);
Will output:
<a href="/recipes/view/6" title="hi "howdy"">
<img src="/img/recipes/6.jpg" alt="Brownies" />
612
</a>
Helpers
613
); ?>
// Output
<video autoplay="autoplay">
<source src="/files/video.mp4" type="video/mp4"/>
<source src="/files/video.ogg" type="video/ogg;
codecs=theora, vorbis"/>
</video>
Note: Text is not escaped by default but you may use $htmlOptions[escape] = true
to escape your text. This replaces a fourth parameter boolean $escape = false that was
available in previous versions.
HtmlHelper::div(string $class, string $text, array $options)
Parameters
$class (string) The class name for the div.
$text (string) The content inside the div.
$options (array) An array of html attributes.
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.
If no text is specified, only an opening div tag is returned.:
614
<?php
echo $this->Html->div(error, Please enter your credit card number.);
?>
// Output
<div class="error">Please enter your credit card number.</div>
Will output:
Helpers
615
You can link to files with absolute paths as well to link files that are not in app/webroot/js:
echo $this->Html->script(/otherdir/script_file);
Will output:
<script type="text/javascript" href="http://code.jquery.com/jquery.min.js">
</script>
Will output:
<script type="text/javascript" href="/js/jquery.js"></script>
<script type="text/javascript" href="/js/wysiwyg.js"></script>
<script type="text/javascript" href="/js/scripts.js"></script>
You can append the script tag to a specific block using the block option:
echo $this->Html->script(wysiwyg, array(block => scriptBottom));
In your layout you can output all the script tags added to scriptBottom:
echo $this->fetch(scriptBottom);
You can include script files from any loaded plugin using plugin syntax.
To include
app/Plugin/DebugKit/webroot/js/toolbar.js You could use the following:
echo $this->Html->script(DebugKit.toolbar.js);
If you want to include a script file which shares a name with a loaded plugin you can
do the following. For example if you had a Blog plugin, and also wanted to include
app/webroot/js/Blog.plugins.js, you would:
echo $this->Html->script(Blog.plugins.js, array(plugin => false));
Changed in version 2.1: The block option was added. Support for plugin syntax was added.
HtmlHelper::scriptBlock($code, $options = array())
Parameters
$code (string) The code to go in the script tag.
$options (array) An array of html attributes.
Generate a code block containing $code set $options[inline] to false to have the script
block appear in the script view block. Other options defined will be added as attributes to
616
HtmlHelper::nestedList(array $list, array $options = array(), array $itemOptions = array(), string $tag = ul)
Parameters
$list (array) Set of elements to list.
$options (array) Additional HTML attributes of the list (ol/ul) tag or if ul/ol use
that as tag.
$itemOptions (array) Additional HTML attributes of the list item (LI) tag.
$tag (string) Type of list tag to use (ol/ul).
Build a nested list (UL/OL) out of an associative array:
$list = array(
Languages => array(
English => array(
American,
Canadian,
British,
),
Spanish,
German,
)
);
echo $this->Html->nestedList($list);
Output:
// Output (minus the whitespace)
<ul>
Helpers
617
<li>Languages
<ul>
<li>English
<ul>
<li>American</li>
<li>Canadian</li>
<li>British</li>
</ul>
</li>
<li>Spanish</li>
<li>German</li>
</ul>
</li>
</ul>
Output:
<tr>
<th>Date</th>
<th>Title</th>
<th>Active</th>
</tr>
echo $this->Html->tableHeaders(
array(Date,Title,Active),
array(class => status),
array(class => product_table)
);
Output:
<tr class="status">
<th class="product_table">Date</th>
<th class="product_table">Title</th>
<th class="product_table">Active</th>
</tr>
Changed in version 2.2: tableHeaders() now accepts attributes per cell, see below.
As of 2.2 you can set attributes per column, these are used instead of the defaults provided in the
$thOptions:
618
echo $this->Html->tableHeaders(array(
id,
array(Name => array(class => highlight)),
array(Date => array(class => sortable))
));
Output:
<tr>
<th>id</th>
<th class="highlight">Name</th>
<th class="sortable">Date</th>
</tr>
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>
echo $this->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,
Helpers
619
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>
echo $this->Html->tableCells(
array(
array(Red, Apple),
array(Orange, Orange),
array(Yellow, Banana),
),
array(class => darker)
);
Output:
620
<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>
Helpers
621
// Output
http://somedomain.com/posts
Output:
<form action="http://example.com" method="post" class="myform">
http://api.cakephp.org/2.4/class-Router.html#_url
622
The $startText option can also accept an array. This gives more control over the generated first
link:
echo $this->Html->getCrumbs( > , array(
text => $this->Html->image(home.png),
url => array(controller => pages, action => display, home),
escape => false
));
Any keys that are not text or url will be passed to link() as the $options parameter.
Changed in version 2.1: The $startText parameter now accepts an array.
HtmlHelper::addCrumb(string $name, string $link = null, mixed $options = null)
Now, in your view youll want to add the following to start the breadcrumb trails on each of the pages:
$this->Html->addCrumb(Users, /users);
$this->Html->addCrumb(Add User, array(controller => users, action => add));
This will add the output of Home > Users > Add User in your layout where getCrumbs was added.
HtmlHelper::getCrumbList(array $options = array(), mixed $startText)
Parameters
$options (array) An array of html attributes for the containing <ul> element.
Can also contain the separator, firstClass, lastClass and escape options.
$startText (string|array) The text or element that precedes the ul.
Returns breadcrumbs as a (x)html list.
This method uses HtmlHelper::tag() to generate list and its elements. Works similar
to getCrumbs(), so it uses options which every crumb was added with. You can use the
$startText parameter to provide the first breadcrumb link/text. This is useful when you always want to include a root link. This option works the same as the $startText option for
getCrumbs().
Changed in version 2.1: The $startText parameter was added.
Changed in version 2.3: The separator, firstClass and lastClass options were added.
Changed in version 2.5: The escape option was added.
JsHelper
class JsHelper(View $view, array $settings = array())
Helpers
623
Warning: The JsHelper is currently deprecated and completely removed in 3.x. We recommend using
regular JavaScript and directly interacting with JavaScript libraries where possible.
Since the beginning CakePHPs support for JavaScript has been with Prototype/Scriptaculous. While we
still think these are excellent JavaScript libraries, the community has been asking for support for other
libraries. Rather than drop Prototype in favour of another JavaScript library. We created an Adapter based
helper, and included 3 of the most requested libraries. Prototype/Scriptaculous, Mootools/Mootools-more,
and jQuery/jQuery UI. While the API is not as expansive as the previous AjaxHelper we feel that the adapter
based solution allows for a more extensible solution giving developers the power and flexibility they need
to address their specific application needs.
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.
Using a specific JavaScript engine
First of all download your preferred JavaScript library and place it in app/webroot/js
Then you must include the library in your page. To include it in all pages, add this line to the <head> section
of app/View/Layouts/default.ctp:
echo $this->Html->script(jquery); // Include jQuery library
Replace jquery with the name of your library file (.js will be added to the name).
By default scripts are cached, and you must explicitly print out the cache. To do this at the end of each page,
include this line just before the ending </body> tag:
echo $this->Js->writeBuffer(); // Write cached scripts
Warning: You must include the library in your page and print the cache for the helper to function.
JavaScript engine selection is declared when you include the helper in your controller:
public $helpers = array(Js => array(Jquery));
The above would use the Jquery Engine in the instances of JsHelper in your views. If you do not declare a
specific engine, the jQuery engine will be used as the default. As mentioned before, there are three engines
implemented in the core, but we encourage the community to expand the library compatibility.
Using jQuery with other libraries The jQuery library, and virtually all of its plugins are constrained
within the jQuery namespace. As a general rule, global objects are stored inside the jQuery namespace as
well, so you shouldnt get a clash between jQuery and any other library (like Prototype, MooTools, or YUI).
That said, there is one caveat: By default, jQuery uses $ as a shortcut for jQuery
To override the $ shortcut, use the jQueryObject variable:
624
$this->Js->JqueryEngine->jQueryObject = $j;
echo $this->Html->scriptBlock(
var $j = jQuery.noConflict();,
array(inline => false)
);
// Tell jQuery to go into noconflict mode
Using the JsHelper inside customHelpers Declare the JsHelper in the $helpers array in your customHelper:
public $helpers = array(Js);
Note: It is not possible to declare a JavaScript engine inside a custom helper. Doing that will have no effect.
If you are willing to use an other JavaScript engine than the default, do the helper setup in your controller as
follows:
public $helpers = array(
Js => array(Prototype),
CustomHelper
);
Warning:
controller.
Be sure to declare the JsHelper and its engine on top of the $helpers array in your
The selected JavaScript engine may disappear (replaced by the default) from the JsHelper object in your
helper, if you miss to do so and you will get code that does not fit your JavaScript library.
Creating a JavaScript Engine
JavaScript engine helpers follow normal helper conventions, with a few additional restrictions. They must
have the Engine suffix. DojoHelper is not good, DojoEngineHelper is correct. Furthermore, they
should extend JsBaseEngineHelper in order to leverage the most of the new API.
JavaScript engine usage
The JsHelper provides a few methods, and acts as a facade for the the Engine helper. You should not
directly access the Engine helper except in rare occasions. Using the facade features of the JsHelper
allows you to leverage the buffering and method chaining features built-in; (method chaining only works in
PHP5).
The JsHelper by default buffers almost all script code generated, allowing you to collect scripts throughout the view, elements and layout, and output it in one place. Outputting buffered scripts is done with
$this->Js->writeBuffer(); this will return the buffer contents in a script tag. You can disable
buffering wholesale with the $bufferScripts property or setting buffer => false in methods
taking $options.
Helpers
625
Since most methods in JavaScript begin with a selection of elements in the DOM, $this->Js->get()
returns a $this, allowing you to chain the methods using the selection. Method chaining allows you to write
shorter, more expressive code:
$this->Js->get(#foo)->event(click, $eventCode);
Is an example of method chaining. Method chaining is not possible in PHP4 and the above sample would
be written like:
$this->Js->get(#foo);
$this->Js->event(click, $eventCode);
Common options In attempts to simplify development where JavaScript 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 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.
Working with buffered scripts One drawback to previous implementation of Ajax type features was
the scattering of script tags throughout your document, and the inability to buffer scripts added by elements
in the layout. The new JsHelper if used correctly avoids both of those issues. It is recommended that you
place $this->Js->writeBuffer() at the bottom of your layout file above the </body> tag. This
will allow all scripts generated in layout elements to be output in one place. It should be noted that buffered
scripts are handled separately from included script files.
JsHelper::writeBuffer($options = array())
Writes all JavaScript generated so far to a code block or caches them to a file and returns a linked script.
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.
JsHelper::buffer($content)
Add $content to the internal script buffer.
JsHelper::getBuffer($clear = true)
626
Get the contents of the current buffer. Pass in false to not clear the buffer at the same time.
Buffering methods that are not normally buffered
Some methods in the helpers are buffered by default. The engines buffer the following methods by default:
event
sortable
drag
drop
slider
Additionally you can force any other method in JsHelper to use the buffering. By appending an boolean
to the end of the arguments you can force other methods to go into the buffer. For example the each()
method does not normally buffer:
$this->Js->each(alert("whoa!");, true);
The above would force the each() method to use the buffer. Conversely if you want a method that does
buffer to not buffer, you can pass a false in as the last argument:
$this->Js->event(click, alert("whoa!");, false);
This would force the event function which normally buffers to return its result.
Other Methods
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.
JsHelper::object($data, $options = array())
Serializes $data into JSON. This method is a proxy for json_encode() with a few extra features
added via the $options parameter.
Options:
prefix - String prepended to the returned data.
postfix - String appended to the returned data.
Example Use:
$json = $this->Js->object($data);
JsHelper::sortable($options = array())
Sortable generates a JavaScript snippet to make a set of elements (usually a list) drag and drop sortable.
The normalized options are:
Helpers
627
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:
$this->Js->get(#my-list);
$this->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
$("#myList").sortable({
containment:"parent",
distance:5,
sort:onSort,
start:onStart,
stop:onStop
});
628
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 response.
type - Data type for response. json and html are supported. Default is html for most
libraries.
evalScripts - Whether or not <script> tags should be evaled.
dataExpression - Should the data key be treated as a callback. Useful for supplying
$options[data] as another JavaScript expression.
Example use:
$this->Js->event(
click,
$this->Js->request(
array(action => foo, param1),
array(async => true, update => #element)
)
);
JsHelper::get($selector)
Set the internal selection to a CSS selector. The active selection is used in subsequent operations
until a new selection is made:
$this->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.
JsHelper::set(mixed $one, mixed $two = null)
Pass variables into JavaScript. Allows you to set variables that will be output when the buffer is
fetched with JsHelper::getBuffer() or JsHelper::writeBuffer(). The JavaScript
variable used to output set variables can be controlled with JsHelper::$setVariable.
JsHelper::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
Helpers
629
If you were using the jQuery engine the following code would be added to the buffer
$("#element").draggable({
containment:"#content",
drag:onDrag,
grid:[10,10],
start:onStart,
stop:onStop
});
JsHelper::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:
$this->Js->get(#element);
$this->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
$("#element").droppable({
accept:".items",
drop:onDrop,
630
out:onExit,
over:onHover
});
Note: Droppables in Mootools function differently from other libraries. Droppables are implemented
as an extension of Drag. So in addition 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.
JsHelper::slider($options = array())
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 sliders value is updated
complete - Fired when the user stops sliding the handle
Example use:
$this->Js->get(#element);
$this->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
$("#element").slider({
change:onChange,
max:10,
min:0,
orientation:"vertical",
stop:onComplete,
Helpers
631
value:2
});
If you were using the jQuery library you would get the following JavaScript code:
$(#some-link).bind(click, function (event) {
alert(hey you!);
632
return false;
});
You can remove the return false; by passing setting the stop option to false:
$this->Js->get(#some-link);
$this->Js->event(
click,
$this->Js->alert(hey you!),
array(stop => false)
);
If you were using the jQuery library you would the following JavaScript code would be added to the
buffer. Note that the default browser event is not cancelled:
$(#some-link).bind(click, function (event) {
alert(hey you!);
});
JsHelper::domReady($callback)
Creates the special DOM ready event. JsHelper::writeBuffer() automatically wraps the
buffered scripts in a domReady method.
JsHelper::each($callback)
Create a snippet that iterates over the currently selected elements, and inserts $callback.
Example:
$this->Js->get(div.message);
$this->Js->each($(this).css({color: "red"}););
JsHelper::alert($message)
Create a JavaScript snippet containing an alert() snippet. By default, alert does not buffer, and
returns the script snippet.
$alert = $this->Js->alert(Hey there);
JsHelper::confirm($message)
Create a JavaScript snippet containing a confirm() snippet. By default, confirm does not buffer,
and returns the script snippet.
$alert = $this->Js->confirm(Are you sure?);
JsHelper::prompt($message, $default)
Create a JavaScript snippet containing a prompt() snippet. By default, prompt does not buffer,
and returns the script snippet.
$prompt = $this->Js->prompt(What is your favorite color?, blue);
Helpers
633
Will create a submit button with an attached onclick event. The click event will be buffered by default.
echo $this->Js->submit(Save, array(
update => #content,
div => false,
type => json,
async => false
));
both
FormHelper::submit()
and
634
Will create a link pointing to /page:2 and updating #content with the response.
You can use the htmlAttributes option to add in additional custom attributes.
echo $this->Js->link(Page 2, array(page => 2), array(
update => #content,
htmlAttributes => array(other => value)
));
JsHelper::serializeForm($options = array())
Serialize the form attached to $selector. Pass true for $isForm if the current selection is a form
element. Converts the form or the form element attached to the current selection into a string/json
object (depending on the library implementation) for use with XHR operations.
Options
isForm - is the current selection a form, or an input? (defaults to false)
inline - is the rendered statement going to be used inside another JS statement? (defaults to
false)
Setting inline == false allows you to remove the trailing ;. This is useful when you need to serialize a
form element as part of another JavaScript operation, or use the serialize method in an Object literal.
JsHelper::redirect($url)
Redirect the page to $url using window.location.
JsHelper::value($value)
Converts a PHP-native variable of any type to a JSON-equivalent representation. Escapes any string
values into JSON compatible strings. UTF-8 characters will be escaped.
AJAX Pagination
Much like AJAX Pagination in 1.2, you can use the JsHelper to handle the creation of AJAX pagination
links instead of plain HTML links.
Making AJAX Links Before you can create AJAX links you must include the JavaScript library
that matches the adapter you are using with JsHelper. By default the JsHelper uses jQuery.
So in your layout include jQuery (or whichever library you are using). Also make sure to include
RequestHandlerComponent in your components. Add the following to your controller:
public $components = array(RequestHandler);
public $helpers = array(Js);
Next link in the JavaScript library you want to use. For this example well be using jQuery:
Helpers
635
echo $this->Html->script(jquery);
Similar to 1.2 you need to tell the PaginatorHelper that you want to make JavaScript enhanced links
instead of plain HTML ones. To do so, call the options() at the top of your view:
$this->Paginator->options(array(
update => #content,
evalScripts => true
));
The PaginatorHelper now knows to make JavaScript enhanced links, and that those links should
update the #content element. Of course this element must exist, and often times you want to wrap
$content_for_layout with a div matching the id used for the update option. You also should set
evalScripts to true if you are using the Mootools or Prototype adapters, without evalScripts these
libraries will not be able to chain requests together. The indicator option is not supported by JsHelper
and will be ignored.
You then create all the links as needed for your pagination features. Since the JsHelper automatically
buffers all generated script content to reduce the number of <script> tags in your source code you must
write the buffer out. At the bottom of your view file. Be sure to include:
echo $this->Js->writeBuffer();
If you omit this you will not be able to chain AJAX pagination links. When you write the buffer, it is also
cleared, so you dont have worry about the same JavaScript being output twice.
Adding effects and transitions Since indicator is no longer supported, you must add any indicator
effects yourself:
<!DOCTYPE html>
<html>
<head>
<?php echo $this->Html->script(jquery); ?>
//more stuff here.
</head>
<body>
<div id="content">
<?php echo $this->fetch(content); ?>
</div>
<?php
echo $this->Html->image(
indicator.gif,
array(id => busy-indicator)
);
?>
</body>
</html>
Remember to place the indicator.gif file inside app/webroot/img folder. You may see a situation where the
indicator.gif displays immediately upon the page load. You need to put in this CSS #busy-indicator
{ display:none; } in your main CSS file.
636
With the above layout, weve included an indicator image file, that will display a busy indicator animation
that we will show and hide with the JsHelper. To do that we need to update our options() function:
$this->Paginator->options(array(
update => #content,
evalScripts => true,
before => $this->Js->get(#busy-indicator)->effect(
fadeIn,
array(buffer => false)
),
complete => $this->Js->get(#busy-indicator)->effect(
fadeOut,
array(buffer => false)
),
));
This will show/hide the busy-indicator element before and after the #content div is updated. Although
indicator has been removed, the new features offered by JsHelper allow for more control and more
complex effects to be created.
NumberHelper
class NumberHelper(View $view, array $settings = array())
The NumberHelper contains convenient 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 formatting numbers.
Changed in version 2.1: NumberHelper have been refactored into CakeNumber class to allow easier
use outside of the View layer. Within a view, these methods are accessible via the NumberHelper class
and you can call it as you would call a normal helper method: $this->Number->method($args);.
All of these functions return the formatted number; They do not automatically echo the output into the view.
NumberHelper::currency(mixed $number, string $currency = USD, array $options = array())
Parameters
$number (float) The value to covert.
$currency (string) The known currency format to use.
$options (array) Options, see below.
This method is used to display a number in common currency formats (EUR,GBP,USD). Usage in a
view looks like:
// called as NumberHelper
echo $this->Number->currency($number, $currency);
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::currency($number, $currency);
Helpers
637
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:
$currency
EUR
GBP
USD
The third parameter is an array of options for further defining the output. The following options are
available:
Option
before
after
zero
places
thousands
decimals
negative
escape
wholeSymbol
wholePosition
fractionSymbol
fractionPosition
fractionExponent
Description
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 no decimal symbol. eg. 0.35 => $0.35.
The text to use for zero values, can be a string or a number. ie. 0, Free!
Number of decimal places to use. ie. 2
Thousands separator ie. ,
Decimal separator symbol ie. .
Symbol for negative numbers. If equal to (), the number will be wrapped with (
and )
Should the output be htmlentity escaped? Defaults to true
String to use for whole numbers ie. dollars
Either before or after to place the whole symbol
String to use for fraction numbers ie. cents
Either before or after to place the fraction symbol
Fraction exponent of this specific currency. Defaults to 2.
Add a currency format to the Number helper. Makes reusing currency formats easier:
// called as NumberHelper
$this->Number->addFormat(BRL, array(before => R$, thousands => ., decimals
// called as CakeNumber
App::uses(CakeNumber, Utility);
CakeNumber::addFormat(BRL, array(before => R$, thousands => ., decimals =>
You can now use BRL as a short form when formatting currency amounts:
// called as NumberHelper
echo $this->Number->currency($value, BRL);
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::currency($value, BRL);
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
,
before,
false,
after,
0,
2,
,,
.,
(),
true,
2
Helpers
639
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.
// called as NumberHelper
echo $this->Number->precision(456.91873645, 2);
// Outputs
456.92
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::precision(456.91873645, 2);
Description
Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal
percentages.
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.
// Called as NumberHelper. Output: 45.69%
echo $this->Number->toPercentage(45.691873645);
// Called as CakeNumber. Output: 45.69%
App::uses(CakeNumber, Utility);
echo CakeNumber::toPercentage(45.691873645);
// Called with multiply. Output: 45.69%
echo CakeNumber::toPercentage(0.45691, 2, array(
multiply => true
));
New in version 2.4: The $options argument with the multiply option was added.
NumberHelper::fromReadableSize(string $size, $default)
Parameters
$size (string) The formatted human readable value.
This method unformats a number from a human readable byte size to an integer number of bytes.
New in version 2.3: This method was added in 2.3
NumberHelper::toReadableSize(string $dataSize)
640
Parameters
$dataSize (string) The number of bytes to make readable.
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):
// called as NumberHelper
echo $this->Number->toReadableSize(0); // 0 Bytes
echo $this->Number->toReadableSize(1024); // 1 KB
echo $this->Number->toReadableSize(1321205.76); // 1.26 MB
echo $this->Number->toReadableSize(5368709120); // 5.00 GB
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::toReadableSize(0); // 0 Bytes
echo CakeNumber::toReadableSize(1024); // 1 KB
echo CakeNumber::toReadableSize(1321205.76); // 1.26 MB
echo CakeNumber::toReadableSize(5368709120); // 5.00 GB
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
Example:
// called as NumberHelper
echo $this->Number->format(123456.7890, array(
places => 2,
Helpers
641
before => ,
escape => false,
decimals => .,
thousands => ,
));
// output 123,456.79
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::format(123456.7890, array(
places => 2,
before => ,
escape => false,
decimals => .,
thousands => ,
));
// output 123,456.79
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 takes the same keys as CakeNumber::format() itself:
places (integer): the amount of desired precision
before (string): to be put before the outputted number
after (string): to be put after the outputted number
decimals (string): used to delimit the decimal places in a number
thousands (string): used to mark off thousand, millions, . . . places
Example:
// called as NumberHelper
echo $this->Number->formatDelta(123456.7890, array(
places => 2,
decimals => .,
thousands => ,
));
// output +123,456.79
// called as CakeNumber
App::uses(CakeNumber, Utility);
642
Paginator
class PaginatorHelper(View $view, array $settings = array())
The Pagination helper is used to output pagination controls such as page numbers and next/previous links.
It works in tandem with PaginatorComponent.
See also Pagination for information on how to create paginated datasets and do paginated queries.
Creating sort links
Helpers
643
echo $this->Paginator->sort(user_id);
Output:
<a href="/posts/index/page:1/sort:user_id/direction:asc/">User Id</a>
You can use the title parameter to create custom text for your link:
echo $this->Paginator->sort(user_id, User account);
Output:
<a href="/posts/index/page:1/sort:user_id/direction:asc/">User account</a>
If you are using HTML like images in your links remember to set escaping off:
echo $this->Paginator->sort(
user_id,
<em>User account</em>,
array(escape => false)
);
Output:
<a href="/posts/index/page:1/sort:user_id/direction:asc/">
<em>User account</em>
</a>
The direction option can be used to set the default direction for a link. Once a link is active, it will automatically switch directions like normal:
echo $this->Paginator->sort(user_id, null, array(direction => desc));
Output:
<a href="/posts/index/page:1/sort:user_id/direction:desc/">User Id</a>
The lock option can be used to lock sorting into the specified direction:
echo $this->Paginator->sort(user_id, null, array(direction => asc, lock => true));
PaginatorHelper::numbers($options = array())
Returns a set of numbers for the paged result set. Uses a modulus to decide how many numbers to show on
each side of the current page By default 8 links on either side of the current page will be created if those
pages exist. Links will not be generated for pages that do not exist. The current page is also not a link.
644
last Whether you want last links generated, set to an integer to define the number of last links
to generate. Defaults to false. Follows the same logic as the first option. There is a last()
method to be used separately as well if you wish.
ellipsis Ellipsis content, defaults to ...
class The class name used on the wrapping tag.
currentClass The class name to use on the current/active link. Defaults to current.
currentTag Tag to use for current page number, defaults to null. This allows you to generate for
example Twitter Bootstrap like links with the current page number wrapped in extra a or span tag.
While this method allows a lot of customization for its output. It is also ok to just call the method without
any params.
echo $this->Paginator->numbers();
Using the first and last options you can create links to the beginning and end of the page set. The following
would create a set of page links that include links to the first 2 and last 2 pages in the paged results:
echo $this->Paginator->numbers(array(first => 2, last => 2));
In addition to generating links that go directly to specific page numbers, youll often want links that go to
the previous and next links, first and last pages in the paged data set.
PaginatorHelper::prev($title = << Previous, $options = array(), $disabledTitle = null,
$disabledOptions = array())
Parameters
Helpers
645
If you were currently on the second page of posts, you would get the following:
<span class="prev">
<a rel="prev" href="/posts/index/page:1/sort:title/order:desc">
<< previous
</a>
</span>
You can change the wrapping tag using the tag option:
echo $this->Paginator->prev(__(previous), array(tag => li));
Output:
<li class="prev">
<a rel="prev" href="/posts/index/page:1/sort:title/order:desc">
previous
</a>
</li>
646
Output:
<a class="prev" rel="prev"
href="/posts/index/page:1/sort:title/order:desc">
previous
</a>
Changed
in
version
2.3:
For
methods:
PaginatorHelper::prev()
and
PaginatorHelper::next() it is now possible to set the tag option to false to disable the
wrapper. New options disabledTag has been added.
If you leave the $disabledOptions empty the $options parameter will be used. This can save some
additional typing if both sets of options are the same.
PaginatorHelper::next($title = Next >>, $options = array(), $disabledTitle = null, $disabledOptions = array())
This method is identical to prev() with a few exceptions. It creates links pointing to the next page
instead of the previous one. It also uses next as the rel attribute value instead of prev
PaginatorHelper::first($first = << first, $options = array())
Returns a first or set of numbers for the first pages. If a string is given, then only a link to the first
page with the provided text will be created:
echo $this->Paginator->first(< first);
The above creates a single link for the first page. Will output nothing if you are on the first page. You
can also use an integer to indicate how many first paging links you want generated:
echo $this->Paginator->first(3);
The above will create links for the first 3 pages, once you get to the third or greater page. Prior to that
nothing will be output.
The options parameter accepts the following:
tag The tag wrapping tag you want to use, defaults to span
after Content to insert after the link/tag
model The model to use defaults to PaginatorHelper::defaultModel()
separator Content between the generated links, defaults to |
ellipsis Content for ellipsis, defaults to ...
PaginatorHelper::last($last = last >>, $options = array())
This method works very much like the first() method. It has a few differences though. It will not
generate any links if you are on the last page for a string values of $last. For an integer value of
$last no links will be generated once the user is inside the range of last pages.
PaginatorHelper::current(string $model = null)
Gets the current page of the recordset for the given model:
Helpers
647
PaginatorHelper::counter($options = array())
Returns a counter string for the paged result set. Using a provided format string and a number of options
you can create localized and application specific indicators of where a user is in the paged data set.
There are a number of options for counter(). The supported ones are:
format Format of the counter. Supported formats are range, pages and custom. Defaults to pages
which would output like 1 of 10. In the custom mode the supplied string is parsed and tokens are
replaced with actual values. The available tokens are:
{:page} - the current page displayed.
{:pages} - total number of pages.
{:current} - current number of records being shown.
{:count} - the total number of records in the result set.
{:start} - number of the first record being displayed.
{:end} - number of the last record being displayed.
{:model} - The pluralized human form of the model name. If your model was RecipePage,
{:model} would be recipe pages. This option was added in 2.0.
You could also supply only a string to the counter method using the tokens available. For example:
echo $this->Paginator->counter(
Page {:page} of {:pages}, showing {:current} records out of
{:count} total, starting on record {:start}, ending on {:end}
);
648
separator The separator between the actual page and the number of pages. Defaults to of . This
is used in conjunction with format = pages which is format default value:
echo $this->Paginator->counter(array(
separator => of a total of
));
model
The
name
of
the
model
being
paginated,
defaults
to
PaginatorHelper::defaultModel(). This is used in conjunction with the custom
string on format option.
Modifying the options PaginatorHelper uses
PaginatorHelper::options($options = array())
Parameters
$options (mixed) Default options for pagination links. If a string is supplied - it
is used as the DOM id element to update.
Sets all the options for the Paginator Helper. Supported options are:
url The URL of the paginating action. url has a few sub options as well:
sort The key that the records are sorted by.
direction The direction of the sorting. Defaults to ASC.
page The page number to display.
The above mentioned options can be used to force particular pages/directions. You can also append
additional URL content into all URLs generated in the helper:
$this->Paginator->options(array(
url => array(
sort => email, direction => desc, page => 6,
lang => en
)
));
The above adds the en route parameter to all links the helper will generate. It will also create links
with specific sort, direction and page values. By default PaginatorHelper will merge in all of the
current pass and named parameters. So you dont have to do that in each view file.
escape Defines if the title field for links should be HTML escaped. Defaults to true.
update The CSS selector of the element to update with the results of AJAX pagination calls. If not
specified, regular links will be created:
$this->Paginator->options(array(update => #content));
This is useful when doing AJAX Pagination. Keep in mind that the value of update can be any valid
CSS selector, but most often is simpler to use an id selector.
Helpers
649
model
The
name
of
the
model
PaginatorHelper::defaultModel().
being
paginated,
defaults
to
Using GET parameters for pagination Normally Pagination in CakePHP uses Named Parameters. There
are times you want to use GET parameters instead. While the main configuration option for this feature is
in PaginatorComponent, you have some additional control in the view. You can use options() to
indicate that you want other named parameters to be converted:
$this->Paginator->options(array(
convertKeys => array(your, keys, here)
));
Configuring the PaginatorHelper to use a JavaScript helper By default the PaginatorHelper uses
JsHelper to do AJAX features. However, if you dont want that and want to use a custom helper for AJAX
links, you can do so by changing the $helpers array in your controller. After running paginate() do
the following:
// In your controller action.
$this->set(posts, $this->paginate());
$this->helpers[Paginator] = array(ajax => CustomJs);
Will change the PaginatorHelper to use the CustomJs for AJAX operations. You could also set
the ajax key to be any helper, as long as that class implements a link() method that behaves like
HtmlHelper::link()
Pagination in Views
Its up to you to decide how to show records to the user, but most often this will be done inside HTML
tables. The examples below assume a tabular layout, but the PaginatorHelper available in views doesnt
always need to be restricted as such.
See the details on PaginatorHelper17 in the API. As mentioned, the PaginatorHelper also offers sorting
features which can be easily integrated into your table column headers:
// app/View/Posts/index.ctp
<table>
<tr>
<th><?php echo $this->Paginator->sort(id, ID); ?></th>
<th><?php echo $this->Paginator->sort(title, Title); ?></th>
</tr>
<?php foreach ($data as $recipe): ?>
<tr>
<td><?php echo $recipe[Recipe][id]; ?> </td>
<td><?php echo h($recipe[Recipe][title]); ?> </td>
</tr>
<?php endforeach; ?>
</table>
17
http://api.cakephp.org/2.4/class-PaginatorHelper.html
650
The links output from the sort() method of the PaginatorHelper allow users to click on table headers
to toggle the sorting of the data by a given field.
It is also possible to sort a column based on associations:
<table>
<tr>
<th><?php echo $this->Paginator->sort(title, Title); ?></th>
<th><?php echo $this->Paginator->sort(Author.name, Author); ?></th>
</tr>
<?php foreach ($data as $recipe): ?>
<tr>
<td><?php echo h($recipe[Recipe][title]); ?> </td>
<td><?php echo h($recipe[Author][name]); ?> </td>
</tr>
<?php endforeach; ?>
</table>
The final ingredient to pagination display in views is the addition of page navigation, also supplied by the
PaginationHelper:
// Shows the page numbers
echo $this->Paginator->numbers();
// Shows the next and previous links
echo $this->Paginator->prev(
Previous,
null,
null,
array(class => disabled)
);
echo $this->Paginator->next(
Next ,
null,
null,
array(class => disabled)
);
// prints X of Y, where X is current page and Y is number of pages
echo $this->Paginator->counter();
The wording output by the counter() method can also be customized using special markers:
echo $this->Paginator->counter(array(
format => Page {:page} of {:pages}, showing {:current} records out of
{:count} total, starting on record {:start}, ending on {:end}
));
Other Methods
651
Would
create
link
pointing
at
PaginatorHelper::defaultModel()
Gets the default model of the paged sets or null if pagination is not initialized.
PaginatorHelper::params(string $model = null)
Gets the current paging parameters from the resultset for the given model:
debug($this->Paginator->params());
/*
Array
(
[page] => 2
[current] => 2
[count] => 43
[prevPage] => 1
[nextPage] => 3
[pageCount] => 3
[order] =>
[limit] => 20
[options] => Array
(
652
[page] => 2
[conditions] => Array
(
)
)
[paramType] => named
)
*/
You can also append the output of the meta function to the named block:
$this->Paginator->meta(array(block => true));
This example assumes you have a Posts Controller and Post Model already created and want to make an
alternative view for RSS.
Creating an xml/rss version of posts/index is a snap with CakePHP. After a few simple steps you can simply
append the desired extension .rss to posts/index making your URL posts/index.rss. Before
we jump too far ahead trying to get our webservice up and running we need to do a few things. First
parseExtensions needs to be activated, this is done in app/Config/routes.php:
Helpers
653
Router::parseExtensions(rss);
In the call above weve activated the .rss extension. When using Router::parseExtensions() you
can pass as many arguments or extensions as you want. This will activate each extension/content-type for
use in your application. Now when the address posts/index.rss is requested you will get an xml
version of your posts/index. However, first we need to edit the controller to add in the rss-specific
code.
Controller Code It is a good idea to add RequestHandler to your PostsControllers $components array.
This will allow a lot of automagic to occur:
public $components = array(RequestHandler);
Our view will also use the TextHelper for formatting, so that should be added to the controller as well:
public $helpers = array(Text);
Before we can make an RSS version of our posts/index we need to get a few things in order. It may
be tempting to put the channel metadata in the controller action and pass it to your view using the
Controller::set() method but this is inappropriate. That information can also go in the view. That
will come later though, for now if you have a different set of logic for the data used to make the RSS feed
and the data for the HTML view you can use the RequestHandler::isRss() method, otherwise your
controller can stay the same:
// Modify the Posts Controller action that corresponds to
// the action which deliver the rss feed, which is the
// index action in our example
public function index() {
if ($this->RequestHandler->isRss() ) {
$posts = $this->Post->find(
all,
array(limit => 20, order => Post.created DESC)
);
return $this->set(compact(posts));
}
// this is not an Rss request, so deliver
// data used by websites interface
$this->paginate[Post] = array(
order => Post.created DESC,
limit => 10
);
$posts = $this->paginate();
$this->set(compact(posts));
}
With all the View variables set we need to create an rss layout.
654
Layout An
Rss
layout
is
very
simple,
app/View/Layouts/rss/default.ctp:
put
the
following
contents
in
if (!isset($documentData)) {
$documentData = array();
}
if (!isset($channelData)) {
$channelData = array();
}
if (!isset($channelData[title])) {
$channelData[title] = $this->fetch(title);
}
$channel = $this->Rss->channel(array(), $channelData, $this->fetch(content));
echo $this->Rss->document($documentData, $channel);
It doesnt look like much but thanks to the power in the RssHelper its doing a lot of lifting for us. We
havent set $documentData or $channelData in the controller, however in CakePHP 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
View/Posts/rss/ directory and create a new index.ctp inside that folder. The contents of the file
are below.
View Our view, located at app/View/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 channels metadata back to the layout:
$this->set(channelData, array(
title => __("Most Recent Posts"),
link => $this->Html->url(/, true),
description => __("Most recent posts."),
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.
Note: You will need to modify the $postLink variable as appropriate to your application.
foreach ($posts as $post) {
$postTime = strtotime($post[Post][created]);
Helpers
655
$postLink = array(
controller => posts,
action => view,
year => date(Y, $postTime),
month => date(m, $postTime),
day => date(d, $postTime),
$post[Post][slug]
);
// Remove & escape any HTML to make sure the feed content will validate.
$bodyText = h(strip_tags($post[Post][body]));
$bodyText = $this->Text->truncate($bodyText, 400, array(
ending => ...,
exact => true,
html
=> true,
));
echo
$this->Rss->item(array(), array(
title => $post[Post][title],
link => $postLink,
guid => array(url => $postLink, isPermaLink => true),
description => $bodyText,
pubDate => $post[Post][created]
));
}
You can see above that we can use the loop to prepare the data to be transformed into XML elements. It
is important to filter out any non-plain text characters out of the description, especially if you are using
a rich text editor for the body of your blog. In the code above we used strip_tags() and h() to
remove/escape any XML special characaters from the content, as they could cause validation errors. Once
we have set up the data for the feed, we can then use the RssHelper::item() method to create the
XML in RSS format. Once you have all this setup, you can test your RSS feed by going to your site
/posts/index.rss and you will see your new feed. It is always important that you validate your RSS
feed before making it live. This can be done by visiting sites that validate the XML such as Feed Validator
or the w3c site at http://validator.w3.org/feed/.
Note: 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.
property RssHelper::$action
Current action
property RssHelper::$base
Base URL
property RssHelper::$data
656
Helpers
657
SessionHelper
class SessionHelper(View $view, array $settings = array())
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 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 read by using dot notation array structures:
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.
SessionHelper::read(string $key)
Return type mixed
Read from the Session. Returns a string or array depending on the contents of the session.
SessionHelper::check(string $key)
Return type boolean
Check to see whether a key is in the Session. Returns a boolean representing the keys existence.
SessionHelper::error()
Return type string
Returns last error encountered in a session.
SessionHelper::valid()
Return type boolean
Used to check whether a session is valid in a view.
Displaying notifications or flash messages
658
The above will output a simple message with the following HTML:
<div id="flashMessage" class="message">
Your stuff has been saved.
</div>
As with the component method, you can set additional properties and customize which element is
used. In the controller, you might have code like:
// in a controller
$this->Session->setFlash(The user could not be deleted.);
When outputting this message, you can choose the element used to display the message:
// in a layout.
echo $this->Session->flash(flash, array(element => failure));
This would use View/Elements/failure.ctp to render the message. The message text would
be available as $message in the element.
The failure element would contain something like this:
<div class="flash flash-failure">
<?php echo h($message); ?>
</div>
You can also pass additional parameters into the flash() method, which allows you to generate
customized messages:
// In the controller
$this->Session->setFlash(Thanks for your payment.);
// In the layout.
echo $this->Session->flash(flash, array(
params => array(name => $user[User][name])
element => payment
));
// View/Elements/payment.ctp
<div class="flash payment">
<?php printf($message, h($name)); ?>
</div>
Note: By default, CakePHP does not escape the HTML in flash messages. If you are using any
request or user data in your flash messages, you should escape it with h when formatting your messages.
TextHelper
class TextHelper(View $view, array $settings = array())
Helpers
659
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 gracefully truncating long stretches of text.
Changed in version 2.1: Several TextHelper methods have been moved into the String class to allow
easier use outside of the View layer. Within a view, these methods are accessible via the TextHelper class.
You can call one as you would call a normal helper method: $this->Text->method($args);.
TextHelper::autoLinkEmails(string $text, array $options=array())
Parameters
$text (string) The text to convert.
$options (array) An array of html attributes for the generated links.
Adds links to the well-formed email addresses in $text, according to any options defined in
$htmlOptions (see HtmlHelper::link()).
$myText = For more information regarding our world-famous .
pastries and desserts, contact [email protected];
$linkedText = $this->Text->autoLinkEmails($myText);
Output:
For more information regarding our world-famous pastries and desserts,
contact <a href="mailto:[email protected]">[email protected]</a>
Changed in version 2.1: In 2.1 this method automatically escapes its input. Use the escape option
to disable this if necessary.
TextHelper::autoLinkUrls(string $text, array $htmlOptions=array())
Parameters
$text (string) The text to convert.
$htmlOptions (array) An array html attributes for the generated links
Same as autoLinkEmails(), only this method searches for strings that start with https, http, ftp,
or nntp and links them appropriately.
Changed in version 2.1: In 2.1 this method automatically escapes its input. Use the escape option
to disable this if necessary.
TextHelper::autoLink(string $text, array $htmlOptions=array())
Parameters
$text (string) The text to autolink.
$htmlOptions (array) An array html attributes for the generated links
Performs the functionality in both autoLinkUrls() and autoLinkEmails() on the supplied
$text. All URLs and emails are linked appropriately given the supplied $htmlOptions.
Changed in version 2.1: As of 2.1, this method automatically escapes its input. Use the escape
option to disable this if necessary.
660
TextHelper::autoParagraph(string $text)
Parameters
$text (string) The text to convert.
Adds proper <p> around text where double-line returns are found, and <br> where single-line returns
are found.
$myText = For more information
regarding our world-famous pastries and desserts.
contact [email protected];
$formattedText = $this->Text->autoParagraph($myText);
Output:
<p>For more information<br />
regarding our world-famous pastries and desserts.<p>
<p>contact [email protected]</p>
Helpers
661
Output:
Highlights $needle in $haystack <span class="highlight">using</span>
the $options[format] string specified or a default string.
TextHelper::stripLinks($text)
Strips the supplied $text of any HTML links.
TextHelper::truncate(string $text, int $length=100, array $options)
Parameters
$text (string) The text to truncate.
$length (int) The length, in characters, beyond which the text should be truncated.
$options (array) An array of options to use.
If $text is longer than $length characters, this method truncates it at $length and adds a prefix
consisting of ellipsis, if defined. If exact is passed as false, the truncation will occur
at the first whitespace after the point at which $length is exceeded. If html is passed as true,
HTML tags will be respected and will not be cut off.
$options is used to pass all extra parameters, and has the following possible keys by default, all of
which are optional:
array(
ellipsis => ...,
exact => true,
html => false
)
Example:
// called as TextHelper
echo $this->Text->truncate(
The killer crept forward and tripped on the rug.,
22,
array(
ellipsis => ...,
exact => false
)
);
// called as String
App::uses(String, Utility);
echo String::truncate(
The killer crept forward and tripped on the rug.,
22,
array(
ellipsis => ...,
exact => false
)
);
662
Output:
The killer crept...
Changed in version 2.3: ending has been replaced by ellipsis. ending is still used in 2.2.1
TextHelper::tail(string $text, int $length=100, array $options)
Parameters
$text (string) The text to truncate.
$length (int) The length, in characters, beyond which the text should be truncated.
$options (array) An array of options to use.
If $text is longer than $length characters, this method removes an initial substring with length
consisting of the difference and prepends a suffix consisting of ellipsis, if defined. If
exact is passed as false, the truncation will occur at the first whitespace prior to the point
at which truncation would otherwise take place.
$options is used to pass all extra parameters, and has the following possible keys by default, all of
which are optional:
array(
ellipsis => ...,
exact => true
)
Helpers
663
)
);
Output:
...a TV, a C# program that can divide by zero, death metal t-shirts
Output:
... by $radius, and prefix/suffix with $ellipsis. This method is
especially handy for search results. The query...
Output:
664
TimeHelper
class TimeHelper(View $view, array $settings = array())
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:
1. It can format time strings.
2. It can test time (but cannot bend time, sorry).
Changed in version 2.1: TimeHelper has been refactored into the CakeTime class to allow easier use
outside of the View layer. Within a view, these methods are accessible via the TimeHelper class and you
can call it as you would call a normal helper method: $this->Time->method($args);.
Using the Helper
A common use of the Time Helper is to offset the date and time to match a users time zone. Lets use a
forum as an example. Your forum has many users who may post messages at any time from any part of the
world. An easy way to manage the time is to save all dates and times as GMT+0 or UTC. Uncomment the
line date_default_timezone_set(UTC); in app/Config/core.php to ensure your applications time zone is set to GMT+0.
Next add a time zone field to your users table and make the necessary modifications to allow your users to
set their time zone. Now that we know the time zone of the logged in user we can correct the date and time
on our posts using the Time Helper:
echo $this->Time->format(
F jS, Y h:i A,
$post[Post][created],
null,
$user[User][time_zone]
);
// Will display August 22nd, 2011 11:53 PM for a user in GMT+0
// August 22nd, 2011 03:53 PM for a user in GMT-8
// and August 23rd, 2011 09:53 AM GMT+10
Most of the Time Helper methods have a $timezone parameter. The $timezone parameter accepts a valid
timezone identifier string or an instance of DateTimeZone class.
Formatting
Helpers
665
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
TimeHelper::convertSpecifiers($format, $time = NULL)
Return type string
Converts a string representing the format for the function strftime and returns a windows safe and
i18n aware format.
TimeHelper::dayAsSql($dateString, $field_name, $timezone = NULL)
Return type string
Creates a string in the same format as daysAsSql but only needs a single date object:
// called via TimeHelper
echo $this->Time->dayAsSql(Aug 22, 2011, modified);
// (modified >= 2011-08-22 00:00:00) AND
// (modified <= 2011-08-22 23:59:59)
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::dayAsSql(Aug 22, 2011, modified);
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::daysAsSql($begin, $end, $fieldName, $timezone = NULL)
Return type string
Returns a string in the format ($field_name >= 2008-01-21 00:00:00) AND ($field_name <= 200801-25 23:59:59). This is handy if you need to search for records between two dates inclusively:
// called via TimeHelper
echo $this->Time->daysAsSql(Aug 22, 2011, Aug 25, 2011, created);
// (created >= 2011-08-22 00:00:00) AND
// (created <= 2011-08-25 23:59:59)
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::daysAsSql(Aug 22, 2011, Aug 25, 2011, created);
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
666
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::format($date, $format = NULL, $default = false, $timezone = NULL)
Return type string
Will return a string formatted to the given format using the PHP strftime() formatting options18 :
// called via TimeHelper
echo $this->Time->format(2011-08-22 11:53:00, %B %e, %Y %H:%M %p);
// August 22, 2011 11:53 AM
echo $this->Time->format(+2 days, %c);
// 2 days from now formatted as Sun, 13 Nov 2011 03:36:10 AM EET
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::format(2011-08-22 11:53:00, %B %e, %Y %H:%M %p);
echo CakeTime::format(+2 days, %c);
You can also provide the date/time as the first argument. When doing this you should use strftime
compatible formatting. This call signature allows you to leverage locale aware date formatting which
is not possible using date() compatible formatting:
// called via TimeHelper
echo $this->Time->format(2012-01-13, %d-%m-%Y, invalid);
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::format(2011-08-22, %d-%m-%Y);
Changed in version 2.2: $format and $date parameters are in opposite order as used in 2.1 and below. $timezone parameter replaces $userOffset parameter used in 2.1 and below. $default
parameter replaces $invalid parameter used in 2.1 and below.
New in version 2.2: $date parameter now also accepts a DateTime object.
TimeHelper::fromString($dateString, $timezone = NULL)
Return type string
Takes a string and uses strtotime19 to convert it into a date integer:
// called via TimeHelper
echo $this->Time->fromString(Aug 22, 2011);
// 1313971200
echo $this->Time->fromString(+1 days);
// 1321074066 (+1 day from current date)
// called as CakeTime
App::uses(CakeTime, Utility);
18
19
http://www.php.net/manual/en/function.strftime.php
http://us.php.net/manual/en/function.date.php
Helpers
667
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::gmt($dateString = NULL)
Return type integer
Will return the date as an integer set to Greenwich Mean Time (GMT).
// called via TimeHelper
echo $this->Time->gmt(Aug 22, 2011);
// 1313971200
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::gmt(Aug 22, 2011);
668
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::serverOffset()
Return type integer
Returns servers offset from GMT in seconds.
TimeHelper::timeAgoInWords($dateString, $options = array())
Return type string
Will take a datetime string (anything that is parsable by PHPs strtotime() function or MySQLs datetime format) and convert it into a friendly word format like, 3 weeks, 3 days ago:
// called via TimeHelper
echo $this->Time->timeAgoInWords(Aug 22, 2011);
// on 22/8/11
// on August 22nd, 2011
echo $this->Time->timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y)
);
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::timeAgoInWords(Aug 22, 2011);
echo CakeTime::timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y)
);
Use the end option to determine the cutoff point to no longer will use words; default +1 month:
// called via TimeHelper
echo $this->Time->timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y, end => +1 year)
);
// On Nov 10th, 2011 it would display: 2 months, 2 weeks, 6 days ago
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::timeAgoInWords(
Helpers
669
Use the accuracy option to determine how precise the output should be. You can use this to limit the
output:
// If $timestamp is 1 month, 1 week, 5 days and 6 hours ago
echo CakeTime::timeAgoInWords($timestamp, array(
accuracy => array(month => month),
end => 1 year
));
// Outputs 1 month ago
New in version 2.2: $dateString parameter now also accepts a DateTime object.
670
New in version 2.4: The new option parameters relativeString (defaults to %s ago) and
absoluteString (defaults to on %s) to allow customization of the resulting output string are
now available.
TimeHelper::toRSS($dateString, $timezone = NULL)
Return type string
Will return a date string in the RSS format Sat, 12 Jan 2008 00:00:00 -0500
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::toUnix($dateString, $timezone = NULL)
Return type integer
A wrapper for fromString.
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
TimeHelper::toServer($dateString, $timezone = NULL, $format = Y-m-d H:i:s)
Return type mixed
New in version 2.2: Returns a formatted date in servers timezone.
TimeHelper::timezone($timezone = NULL)
Return type DateTimeZone
New in version 2.2: Returns a timezone object from a string or the users timezone object. If the
function is called without a parameter it tries to get timezone from Config.timezone configuration
variable.
TimeHelper::listTimezones($filter = null, $country = null, $group = true)
Return type array
New in version 2.2: Returns a list of timezone identifiers.
Testing Time
671
wasWithinLast takes a time interval which is a string in the format 3 months and accepts a time
interval of seconds, minutes, hours, days, weeks, months and years (plural and not). If a time interval
is not recognized (for example, if it is mistyped) then it will default to days.
Utilities
Beyond the core MVC components, CakePHP includes a great selection of utility classes that help you do
everything from webservice requests, to caching, to logging, internationalization and more.
Utilities
Caching
Caching is frequently used to reduce the time it takes to create or read from other resources. Caching is
often used to make reading from expensive resources less expensive. You can easily store the results of
expensive queries, or remote webservice access that doesnt frequently change in a cache. Once in the
cache, re-reading the stored resource from the cache is much cheaper than accessing the remote resource.
Caching in CakePHP is primarily facilitated by the Cache class. This class provides a set of static methods
that provide a uniform API to dealing with all different types of Caching implementations. CakePHP comes
with several cache engines built-in, and provides an easy system to implement your own caching systems.
The built-in caching engines are:
FileCache File cache is a simple cache that uses local files. It is the slowest cache engine, and
doesnt provide as many features for atomic operations. However, since disk storage is often quite
cheap, storing large objects, or elements that are infrequently written work well in files. This is the
default Cache engine for 2.3+
672
ApcCache APC cache uses the PHP APC20 extension. This extension uses shared memory on the
webserver to store objects. This makes it very fast, and able to provide atomic read/write features. By
default CakePHP in 2.0-2.2 will use this cache engine if its available.
Wincache Wincache uses the Wincache21 extension. Wincache is similar to APC in features and
performance, but optimized for Windows and IIS.
XcacheEngine Xcache22 is a PHP extension that provides similar features to APC.
MemcacheEngine Uses the Memcache23 extension. Memcache provides a very fast cache system
that can be distributed across many servers, and provides atomic operations.
MemcachedEngine Uses the Memcached24 extension. It also interfaces with memcache but provides better performance.
RedisEngine Uses the phpredis25 extension. Redis provides a fast and persistent cache system
similar to memcached, also provides atomic operations.
Changed in version 2.3: FileEngine is always the default cache engine. In the past a number of people had
difficulty setting up and deploying APC correctly both in cli + web. Using files should make setting up
CakePHP simpler for new developers.
Changed in version 2.5: The Memcached engine was added. And the Memcache engine was deprecated.
Regardless of the CacheEngine you choose to use, your application interacts with Cache in a consistent
manner. This means you can easily swap cache engines as your application grows. In addition to the Cache
class, the CacheHelper allows for full page caching, which can greatly improve performance as well.
Configuring Cache class
Configuring the Cache class can be done anywhere, but generally you will want to configure Cache in
app/Config/bootstrap.php. You can configure as many cache configurations as you need, and use
any mixture of cache engines. CakePHP uses two cache configurations internally, which are configured in
app/Config/core.php. If you are using APC or Memcache you should make sure to set unique keys
for the core caches. This will prevent multiple applications from overwriting each others cached data.
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. Using multiple configurations also lets you incrementally
change the storage as needed.
Note: You must specify which engine to use. It does not default to File.
Example:
20
http://php.net/apc
http://php.net/wincache
22
http://xcache.lighttpd.net/
23
http://php.net/memcache
24
http://php.net/memcached
25
https://github.com/nicolasff/phpredis
21
Utilities
673
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/bootstrap.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().
Note: When using the FileEngine you might need to use the mask option to ensure cache files are made
with the correct permissions.
New in version 2.4: In debug mode missing directories will now be automatically created to avoid unnecessary errors thrown when using the FileEngine.
Creating a storage engine for Cache
You can provide custom Cache adapters in app/Lib as well as in plugins using $plugin/Lib.
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/Lib/Cache/Engine/MyCustomCacheEngine.php as an app/libs or in
$plugin/Lib/Cache/Engine/MyCustomCacheEngine.php as part of a plugin. Cache configs
from plugins need to use the plugin dot syntax.
Cache::config(custom, array(
engine => CachePack.MyCustomCache,
// ...
));
Note: App and Plugin cache engines should be configured in app/Config/bootstrap.php. If you
try to configure them in core.php they will not work correctly.
Custom Cache engines must extend CacheEngine which defines a number of abstract methods as well as
provides a few initialization methods.
The required API for a CacheEngine is
class CacheEngine
The base class for all cache engines used with Cache.
674
You can greatly improve the performance of your application by putting results that infrequently change,
or that are subject to heavy reads into the cache. A perfect example of this are the results from
Model::find(). A method that uses Cache to store results could look like:
Utilities
675
You could improve the above code by moving the cache reading logic into a behavior, that read from the
cache, or ran the associated model method. That is an exercise you can do though.
As of 2.5 you can accomplish the above much more simply using Cache::remember(). Assuming you
are using PHP 5.3 or newer, using the remember() method would look like:
class Post extends AppModel {
public function newest() {
$model = $this;
return Cache::remember(newest_posts, function() use ($model){
return $model->find(all, array(
order => Post.updated DESC,
limit => 10
));
}, long);
}
}
Counters for various things are easily stored in a cache. For example, a simple countdown for remaining
slots in a contest could be stored in Cache. The Cache class exposes atomic ways to increment/decrement
counter values in an easy way. Atomic operations are important for these values as it reduces the risk of
contention, and ability for two users to simultaneously lower the value by one, resulting in an incorrect
value.
After setting an integer value,
Cache::decrement():
Cache::write(initial_count, 10);
// Later on
Cache::decrement(initial_count);
// or
Cache::increment(initial_count);
Note:
676
Incrementing and decrementing do not work with FileEngine. You should use APC, Redis or
Memcached instead.
Using groups
Lets say you want to store the HTML generated for your homepage in cache, but would also want to
automatically invalidate this cache every time a comment or post is added to your database. By adding the
groups comment and post, we have effectively tagged any key stored into this cache configuration with
both group names.
For instance, whenever a new post is added, we could tell the Cache engine to remove all entries associated
to the post group:
// Model/Post.php
public function afterSave($created, $options = array()) {
if ($created) {
Cache::clearGroup(post, site_home);
}
}
Utilities
677
Groups are shared across all cache configs using the same engine and same prefix. If you are using groups
and want to take advantage of group deletion, choose a common prefix for all your configs.
Cache API
class Cache
The Cache class in CakePHP provides a generic frontend for several backend caching systems. Different Cache configurations and engines can be set up in your app/Config/core.php
static Cache::config($name = null, $settings = array())
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.
static Cache::read($key, $config = default)
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 doesnt exist. The contents of the cache might evaluate
false, so make sure you use the strict comparison operators: === or !==.
For example:
$cloud = Cache::read(cloud);
if ($cloud !== false) {
return $cloud;
}
// generate cloud data
// ...
// store data in cache
Cache::write(cloud, $cloud);
return $cloud;
Using Cache::write() and Cache::read() to easily reduce the number of trips made to the
database to fetch posts.
static Cache::delete($key, $config = default)
Cache::delete() will allow you to completely remove a cached object from the Cache store.
678
If you find yourself repeatedly calling Cache::set() then perhaps you should create a new
Cache::config(). This will remove the need to call Cache::set().
static Cache::increment($key, $offset = 1, $config = default)
Atomically increment a value stored in the cache engine. Ideal for modifying counters or semaphore
type values.
static Cache::decrement($key, $offset = 1, $config = default)
Atomically decrement a value stored in the cache engine. Ideal for modifying counters or semaphore
type values.
static Cache::clear($check, $config = default)
Destroy all cached values for a cache configuration. In engines like Apc, Memcache and Wincache,
the cache configurations prefix is used to remove cache entries. Make sure that different cache configurations have different prefixes.
Cache::clearGroup($group, $config = default)
Returns Boolean true on success.
Delete all keys from the cache belonging to the same group.
static Cache::gc($config)
Garbage collects entries in the cache configuration. This is primarily used by FileEngine. It should
be implemented by any Cache engine that requires manual eviction of cached data.
static Cache::groupConfigs($group = null)
Returns Array of groups and its related configuration names.
Retrieve group names to config mapping.
static Cache::remember($key, $callable, $config = default)
Provides an easy way to do read-through caching. If the cache key exists it will be returned. If the
key does not exist, the callable will be invoked and the results stored in the cache at the provided key.
For example, you often want to cache query results. You could use remember() to make this simple.
Assuming you are using PHP 5.3 or newer:
class Articles extends AppModel {
function all() {
Utilities
679
$model = $this;
return Cache::remember(all_articles, function() use ($model){
return $model->find(all);
});
}
}
First of all, you should ensure the class is loaded using App::uses():
App::uses(CakeEmail, Network/Email);
Using CakeEmail is similar to using EmailComponent. But instead of using attributes you use methods.
Example:
$Email = new CakeEmail();
$Email->from(array([email protected] => My Site));
$Email->to([email protected]);
$Email->subject(About);
$Email->send(My message);
To simplify things, all of the setter methods return the instance of class. You can re-write the above code as:
$Email = new CakeEmail();
$Email->from(array([email protected] => My Site))
->to([email protected])
->subject(About)
->send(My message);
Choosing the sender When sending email on behalf of other people its often a good idea to define the
original sender using the Sender header. You can do so using sender():
$Email = new CakeEmail();
$Email->sender([email protected], MyApp emailer);
680
Note: Its also a good idea to set the envelope sender when sending mail on another persons behalf. This
prevents them from getting any messages about deliverability.
Configuration
EmailConfig.
The
CakeEmail will create an instance of the EmailConfig class to access the config. If you have dynamic
data to put in the configs, you can use the constructor to do that:
class EmailConfig {
public function __construct() {
// Do conditional assignments here.
}
}
It is not required to create app/Config/email.php, CakeEmail can be used without it and use
respective methods to set all configurations separately or load an array of configs.
To load a config from EmailConfig you can use the config() method or pass it to the constructor of
CakeEmail:
$Email = new CakeEmail();
$Email->config(default);
//or in constructor::
$Email = new CakeEmail(default);
Instead of passing a string which matches the configuration name in EmailConfig you can also just load
an array of configs:
$Email = new CakeEmail();
$Email->config(array(from => [email protected], transport => MyCustom));
//or in constructor::
$Email = new CakeEmail(array(from => [email protected], transport => MyCustom));
You can configure SSL SMTP servers such as Gmail. To do so, prefix the host with ssl:// and
configure the port value accordingly. Example:
class EmailConfig {
public $gmail = array(
host => ssl://smtp.gmail.com,
port => 465,
username => [email protected],
password => secret,
transport => Smtp
Utilities
681
);
}
Note: To use this feature, you will need to have the SSL configured in your PHP install.
As of 2.3.0 you can also enable TLS SMTP using the tls option:
class EmailConfig {
public $gmail = array(
host => smtp.gmail.com,
port => 465,
username => [email protected],
password => secret,
transport => Smtp,
tls => true
);
}
The above configuration would enable TLS communication for email messages.
Configurations The following configuration keys are used:
from: Email or array of sender. See CakeEmail::from().
sender: Email or array of real sender. See CakeEmail::sender().
to: Email or array of destination. See CakeEmail::to().
cc: Email or array of carbon copy. See CakeEmail::cc().
bcc: Email or array of blind carbon copy. See CakeEmail::bcc().
replyTo: Email or array to reply the e-mail. See CakeEmail::replyTo().
readReceipt: Email address or an array of addresses to receive the receipt of read. See
CakeEmail::readReceipt().
returnPath: Email address or and array of addresses to return if have some error. See
CakeEmail::returnPath().
messageId: Message ID of e-mail. See CakeEmail::messageId().
subject: Subject of the message. See CakeEmail::subject().
message: Content of message. Do not set this field if you are using rendered content.
headers: Headers to be included. See CakeEmail::setHeaders().
viewRender: If you are using rendered content, set the view class name.
CakeEmail::viewRender().
See
template:
If you are using rendered content,
CakeEmail::template().
See
682
layout: If you are using rendered content, set the layout to render. If you want to render a
template without layout, set this field to null. See CakeEmail::template().
viewVars: If you are using rendered content, set the array with variables to be used in the view.
See CakeEmail::viewVars().
attachments: List of files to attach. See CakeEmail::attachments().
emailFormat: Format of email (html, text or both). See CakeEmail::emailFormat().
transport: Transport name. See CakeEmail::transport().
log: Log level to log the email headers and message. true will use LOG_DEBUG. See also
CakeLog::write()
helpers: Array of helpers used in the email template.
All of these configurations are optional, except from. If you put more configurations in this array, the
configurations will be used in the CakeEmail::config() method and passed to the transport class
config(). For example, if you are using the SMTP transport, you should pass the host, port and other
configurations.
Note: The values of above keys using Email or array, like from, to, cc, etc will be passed as first parameter of corresponding methods. The equivalent for: CakeEmail::from([email protected], My
Site) would be defined as from => array([email protected] => My Site) in your
config.
Setting headers In CakeEmail you are free to set whatever headers you want. When migrating to use
CakeEmail, do not forget to put the X- prefix in your headers.
See CakeEmail::setHeaders() and CakeEmail::addHeaders()
Sending templated emails Emails are often much more than just a simple text message. In order to
facilitate that, CakePHP provides a way to send emails using CakePHPs view layer.
The templates for emails reside in a special folder in your applications View directory called Emails.
Email views can also use layouts, and elements just like normal views:
$Email = new CakeEmail();
$Email->template(welcome, fancy)
->emailFormat(html)
->to([email protected])
->from([email protected])
->send();
Utilities
683
->to([email protected])
->from([email protected])
->send();
You can use helpers in emails as well, much like you can in normal view files. By default only the
HtmlHelper is loaded. You can load additional helpers using the helpers() method:
$Email->helpers(array(Html, Custom, Text));
When setting helpers be sure to include Html or it will be removed from the helpers loaded in your email
template.
If you want to send email using templates in a plugin you can use the familiar plugin syntax to do so:
$Email = new CakeEmail();
$Email->template(Blog.new_comment, Blog.auto_message);
The above would use templates from the Blog plugin as an example.
In some cases, you might need to override the default template provided by plugins. You can do this using
themes by telling CakeEmail to use appropriate theme using CakeEmail::theme() method:
$Email = new CakeEmail();
$Email->template(Blog.new_comment, Blog.auto_message);
$Email->theme(TestTheme);
This allows you to override the new_comment template in your theme without modifying the Blog plugin.
The template file needs to be created in the following path:
APP/View/Themed/TestTheme/Blog/Emails/text/new_comment.ctp.
Sending attachments
CakeEmail::attachments($attachments = null)
You can attach files to email messages as well. There are a few different formats depending on what kind of
files you have, and how you want the filenames to appear in the recipients mail client:
684
The above will attach the file with different mimetype and with custom Content ID (when set the
content ID the attachment is transformed to inline). The mimetype and contentId are optional in this
form.
4.1. When you are using the contentId, you can use the file in the HTML body like <img
src="cid:my-content-id">.
4.2. You can use the contentDisposition option to disable the Content-Disposition
header for an attachment. This is useful when sending ical invites to clients using outlook.
4.3 Instead of the file option you can provide the file contents as a string using the data option.
This allows you to attach files without needing file paths to them.
Changed in version 2.3: The contentDisposition option was added.
Changed in version 2.4: The data option was added.
Using transports Transports are classes designed to send the e-mail over some protocol or method.
CakePHP supports the Mail (default), Debug and SMTP transports.
To configure your method, you must use the CakeEmail::transport() method or have the transport
in your configuration.
Creating custom Transports You are able to create your custom transports to integrate
with others email systems (like SwiftMailer).
To create your transport, first create the file
app/Lib/Network/Email/ExampleTransport.php (where Example is the name of your transport). To start off your file should look like:
App::uses(AbstractTransport, Network/Email);
class ExampleTransport extends AbstractTransport {
public function send(CakeEmail $Email) {
Utilities
685
// magic inside!
}
}
You must implement the method send(CakeEmail $Email) with your custom logic. Optionally, you
can implement the config($config) method. config() is called before send() and allows you to
accept user configurations. By default, this method puts the configuration in protected attribute $_config.
If you need to call additional methods on the transport before send,
CakeEmail::transportClass() to get an instance of the transport. Example:
you
can
use
$yourInstance = $Email->transport(your)->transportClass();
$yourInstance->myCustomMethod();
$Email->send();
Sometimes you need a quick way to fire off an email, and you dont necessarily want do setup a bunch of
configuration ahead of time. CakeEmail::deliver() is intended for that purpose.
You can create your configuration in EmailConfig, or use an array with all options that you need and use
the static method CakeEmail::deliver(). Example:
This method will send an email to [email protected] , from [email protected] with subject Subject
and content Message.
The return of deliver() is a CakeEmail instance with all configurations set. If you do not want to send
the email right away, and wish to configure a few things before sending, you can pass the 5th parameter as
false.
The 3rd parameter is the content of message or an array with variables (when using rendered content).
26
27
[email protected]
[email protected]
686
The 4th parameter can be an array with the configurations or a string with the name of configuration in
EmailConfig.
If you want, you can pass the to, subject and message as null and do all configurations in the 4th parameter
(as array or using EmailConfig). Check the list of configurations to see all accepted configs.
Sending emails from CLI
A valid message id can help to prevent emails ending up in spam folders. If you are generating links in your
email bodies you will also need to set the App.fullBaseUrl configure value.
Folder & File
The Folder and File utilities are convenience classes to help you read from and write/append to files; list
files within a folder and other common directory related tasks.
Basic usage
and search for all .ctp files within that folder using regex:
<?php
$files = $dir->find(.*\.ctp);
Now we can loop through the files and read from or write/append to the contents or simply delete the file:
<?php
foreach ($files as $file) {
$file = new File($dir->pwd() . DS . $file);
$contents = $file->read();
Utilities
687
Folder API
class Folder(string $path = false, boolean $create = false, string|boolean $mode = false)
<?php
// Create a new folder with 0755 permissions
$dir = new Folder(/path/to/folder, true, 0755);
property Folder::$path
Path of the current folder. Folder::pwd() will return the same information.
property Folder::$sort
Whether or not the list results should be sorted by name.
property Folder::$mode
Mode to be used when creating folders. Defaults to 0755. Does nothing on windows machines.
static Folder::addPathElement(string $path, string $element)
Return type string
Returns $path with $element added, with correct slash in-between:
$path = Folder::addPathElement(/a/path/for, testing);
// $path equals /a/path/for/testing
Folder::chmod(string $path, integer $mode = false, boolean $recursive = true, array $exceptions = array())
688
Utilities
689
<?php
$folder = new Folder();
if ($folder->create(foo . DS . bar . DS . baz . DS . shoe . DS . horn)) {
// Successfully created the nested folders
}
Folder::dirsize()
Return type integer
Returns the size in bytes of this Folder and its contents.
Folder::errors()
Return type array
Get the error from latest method.
Folder::find(string $regexpPattern = .*, boolean $sort = false)
Return type array
Returns an array of all matching files in the current directory:
<?php
// Find all .png in your app/webroot/img/ folder and sort the results
$dir = new Folder(WWW_ROOT . img);
$files = $dir->find(.*\.png, true);
/*
Array
(
[0] => cake.icon.png
[1] => test-error-icon.png
[2] => test-fail-icon.png
[3] => test-pass-icon.png
[4] => test-skip-icon.png
)
*/
Note: The folder find and findRecursive methods will only find files. If you would like to get folders and
files see Folder::read() or Folder::tree()
Folder::findRecursive(string $pattern = .*, boolean $sort = false)
690
Folder::inCakePath(string $path = )
Return type boolean
Returns true if the file is in a given CakePath.
Folder::inPath(string $path = , boolean $reverse = false)
Return type boolean
Returns true if the file is in the given path:
<?php
$Folder = new Folder(WWW_ROOT);
$result = $Folder->inPath(APP);
// $result = true, /var/www/example/app/ is in /var/www/example/app/webroot/
Utilities
691
692
(
[0] => .htaccess
[1] => favicon.ico
[2] => test.php
)
)
*/
Folder::realpath(string $path)
Return type string
Get the real path (taking .. and such into account).
static Folder::slashTerm(string $path)
Return type string
Returns $path with added terminating slash (corrected for Windows or other OS).
Folder::tree(null|string $path = null, array|boolean $exceptions = true, null|string $type =
null)
Return type mixed
Returns an array of nested directories and files in each directory.
File API
property File::$Folder
The Folder object of the file.
property File::$name
The name of the file with the extension. Differs from File::name() which returns the name
without the extension.
property File::$info
An array of file info. Use File::info() instead.
property File::$handle
Holds the file handler resource if the file is opened.
property File::$lock
Enable locking for file reading and writing.
property File::$path
The current files absolute path.
File::append(string $data, boolean $force = false)
Utilities
693
694
Utilities
695
696
The path syntax described below is used by all the methods in Hash. Not all parts of the path syntax are
available in all methods. A path expression is made of any number of tokens. Tokens are composed of two
groups. Expressions, are used to traverse the array data, while matchers are used to qualify elements. You
apply matchers to expression elements.
Expression Types
Expression
{n}
{s}
Foo
Definition
Represents a numeric key. Will match any string or numeric key.
Represents a string. Will match any string value including numeric string values.
Matches keys with the exact same value.
All expression elements are supported by all methods. In addition to expression elements, you can use
attribute matching with certain methods. They are extract(), combine(), format(), check(),
map(), reduce(), apply(), sort(), insert(), remove() and nest().
Matcher
[id]
[id=2]
[id!=2]
[id>2]
[id>=2]
[id<2]
[id<=2]
[text=/.../]
Definition
Match elements with a given array key.
Match elements with id equal to 2.
Match elements with id not equal to 2.
Match elements with id greater than 2.
Match elements with id greater than or equal to 2.
Match elements with id less than 2
Match elements with id less than or equal to 2.
Match elements that have values matching the regular expression inside
Changed in version 2.5: Matcher support was added to insert() and remove().
static Hash::get(array $data, $path, $default = null)
Return type mixed
get() is a simplified version of extract(), it only supports direct path expressions. Paths with
{n}, {s} or matchers are not supported. Use get() when you want exactly one value out of an
array. The optional third argument will be returned if the requested path is not found in the array.
Changed in version 2.5: The optional third argument $default = null was added.
static Hash::extract(array $data, $path)
Return type array
Utilities
697
Hash::extract() supports all expression, and matcher components of Hash path syntax. You can
use extract to retrieve data from arrays, along arbitrary paths quickly without having to loop through
the data structures. Instead you use path expressions to qualify which elements you want returned
// Common Usage:
$users = $this->User->find("all");
$results = Hash::extract($users, {n}.User.id);
// $results equals:
// array(1,2,3,4,5,...);
You can use paths using {n} and {s} to insert data into multiple points:
$users = $this->User->find(all);
$users = Hash::insert($users, {n}.User.new, value);
Changed in version 2.5: As of 2.5.0 attribute matching expressions work with insert().
static Hash::remove(array $data, $path = null)
Return type array
Removes all elements from an array that match $path.
$a = array(
pages => array(name => page),
files => array(name => files)
);
$result = Hash::remove($a, files);
/* $result now looks like:
Array
(
[pages] => Array
(
698
Using {n} and {s} will allow you to remove multiple values at once.
Changed in version 2.5: As of 2.5.0 attribute matching expressions work with remove()
static Hash::combine(array $data, $keyPath = null, $valuePath = null, $groupPath = null)
Return type array
Creates an associative array using a $keyPath as the path to build its keys, and optionally $valuePath
as path to get the values. If $valuePath is not specified, or doesnt match anything, values will be
initialized to null. You can optionally group the values by what is obtained when following the path
specified in $groupPath.
$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
)
)
),
);
$result = Hash::combine($a, {n}.User.id);
/* $result now looks like:
Array
(
[2] =>
[14] =>
)
*/
$result = Hash::combine($a, {n}.User.id, {n}.User.Data);
/* $result now looks like:
Array
Utilities
699
(
[2] => Array
(
[user]
[name]
)
[14] => Array
(
[user]
[name]
)
=> mariano.iglesias
=> Mariano Iglesias
=> phpnut
=> Larry E. Masters
)
*/
$result = Hash::combine($a, {n}.User.id, {n}.User.Data.name);
/* $result now looks like:
Array
(
[2] => Mariano Iglesias
[14] => Larry E. Masters
)
*/
$result = Hash::combine($a, {n}.User.id, {n}.User.Data, {n}.User.group_id);
/* $result now looks like:
Array
(
[1] => Array
(
[2] => Array
(
[user] => mariano.iglesias
[name] => Mariano Iglesias
)
)
[2] => Array
(
[14] => Array
(
[user] => phpnut
[name] => Larry E. Masters
)
)
)
*/
700
)
[2] => Array
(
[14] => Larry E. Masters
)
)
*/
You can provide arrays for both $keyPath and $valuePath. If you do this, the first value will be used
as a format string, for values extracted by the other paths:
$result = Hash::combine(
$a,
{n}.User.id,
array(%s: %s, {n}.User.Data.user, {n}.User.Data.name),
{n}.User.group_id
);
/* $result now looks like:
Array
(
[1] => Array
(
[2] => mariano.iglesias: Mariano Iglesias
)
[2] => Array
(
[14] => phpnut: Larry E. Masters
)
)
*/
$result = Hash::combine(
$a,
array(%s: %s, {n}.User.Data.user, {n}.User.Data.name),
{n}.User.id
);
/* $result now looks like:
Array
(
[mariano.iglesias: Mariano Iglesias] => 2
[phpnut: Larry E. Masters] => 14
)
*/
Utilities
701
702
1 => array(name
);
$b = array(
0 => array(name
1 => array(name
2 => array(name
a => b
);
=> about)
=> main),
=> about),
=> contact),
703
$data = array(
0,
false,
true,
0,
array(one thing, I can tell you, is you got to be, false)
);
$res = Hash::filter($data);
/* $data now looks like:
Array (
[0] => 0
[2] => true
[3] => 0
[4] => Array
(
[0] => one thing
[1] => I can tell you
[2] => is you got to be
)
)
/
*
704
Utilities
705
706
$data = array(1 => array(1.1 => 1.1.1), 2, 3 => array(3.1 => 3.1.1));
$result = Hash::dimensions($data);
// $result == 2
$data = array(1 => 1.1, 2, 3 => array(3.1 => 3.1.1));
$result = Hash::dimensions($data);
// $result == 1
$data = array(1 => array(1.1 => 1.1.1), 2, 3 => array(3.1 => array(3.1.1
$result = Hash::dimensions($data);
// $result == 2
$data = array(1 => array(1.1 => 1.1.1), 2, 3 => array(3.1 => array(3.1.1
$result = Hash::maxDimensions($data, true);
// $result == 3
Utilities
707
);
$result = Hash::sort($a, {n}.Person.name, asc);
/* $result now looks like:
Array
(
[0] => Array
(
[Shirt] => Array
(
[color] => black
)
)
[1] => Array
(
[Person] => Array
(
[name] => Jeff
)
)
)
*/
$dir can be either asc or desc. $type can be one of the following values:
regular for regular sorting.
numeric for sorting values as their numeric equivalents.
string for sorting values as their string value.
natural for sorting values in a human friendly way. Will sort foo10 below foo2 as an
example. Natural sorting requires PHP 5.4 or greater.
static Hash::diff(array $data, array $compare)
Return type array
Computes the difference between two arrays:
$a = array(
0 => array(name
1 => array(name
);
$b = array(
0 => array(name
1 => array(name
2 => array(name
);
=> main),
=> about)
=> main),
=> about),
=> contact)
708
$array1 = array(ModelOne => array(id => 1001, field_one => a1.m1.f1, field_tw
$array2 = array(ModelOne => array(id => 1003, field_one => a3.m1.f1, field_tw
$res = Hash::mergeDiff($array1, $array2);
/* $res now looks like:
Array
(
[ModelOne] => Array
(
[id] => 1001
[field_one] => a1.m1.f1
[field_two] => a1.m1.f2
[field_three] => a3.m1.f3
)
)
*/
Example 2
$array1 = array("a" => "b", 1 => 20938, "c" => "string");
$array2 = array("b" => "b", 3 => 238, "c" => "string", array("extra_field"));
$res = Hash::mergeDiff($array1, $array2);
/* $res now looks like:
Array
(
[a] => b
[1] => 20938
[c] => string
[b] => b
[3] => 238
[4] => Array
(
[0] => extra_field
)
)
*/
Utilities
709
array. Numeric keys with values, will be converted to string keys with null values. Normalizing an
array, makes using the results with Hash::merge() easier:
$a = array(Tree, CounterCache,
Upload => array(
folder => products,
fields => array(image_1_id, image_2_id)
)
);
$result = Hash::normalize($a);
/* $result now looks like:
Array
(
[Tree] => null
[CounterCache] => null
[Upload] => Array
(
[folder] => products
[fields] => Array
(
[0] => image_1_id
[1] => image_2_id
)
)
)
*/
$b = array(
Cacheable => array(enabled => false),
Limit,
Bindable,
Validator,
Transactional
);
$result = Hash::normalize($b);
/* $result now looks like:
Array
(
[Cacheable] => Array
(
[enabled] => false
)
[Limit] => null
[Bindable] => null
[Validator] => null
[Transactional] => null
)
*/
710
Options:
children The key name to use in the result set for children. Defaults to children.
idPath The path to a key that identifies each entry.
Hash::extract(). Defaults to {n}.$alias.id
parentPath The path to a key that identifies the parent of each entry. Should be compatible
with Hash::extract(). Defaults to {n}.$alias.parent_id
root The id of the desired top-most result.
Example:
$data = array(
array(ModelName
array(ModelName
array(ModelName
array(ModelName
array(ModelName
array(ModelName
array(ModelName
array(ModelName
array(ModelName
array(ModelName
);
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
array(id
array(id
array(id
array(id
array(id
array(id
array(id
array(id
array(id
array(id
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
Utilities
711
HttpSocket
class HttpSocket(mixed $config = array())
CakePHP includes an HttpSocket class which can be used easily for making requests. It is a great way to
communicate with external webservices, or remote apis.
Making a request
You can use HttpSocket to create most kinds of HTTP requests with the different HTTP methods.
HttpSocket::get($uri, $query, $request)
The $query parameter, can either be a query string, or an array of keys and values. The get method
makes a simple HTTP GET request returning the results:
App::uses(HttpSocket, Network/Http);
$HttpSocket = new HttpSocket();
// string query
$results = $HttpSocket->get(http://www.google.com/search, q=cakephp);
// array query
$results = $HttpSocket->get(http://www.google.com/search, array(q => cakephp));
712
$results = $HttpSocket->post(
http://example.com/add,
name=test&type=user
);
// array data
$data = array(name => test, type => user);
$results = $HttpSocket->post(http://example.com/add, $data);
Utilities
713
Responses from requests made with HttpSocket are instances of HttpResponse. This object gives
you a few accessor methods to access the contents of an HTTP response. This class implements the ArrayAccess28 and __toString()29 , so you can continue using the $http->response as array and the return of
request methods as string:
App::uses(HttpSocket, Network/Http);
$http = new HttpSocket();
$response = $http->get(http://www.cakephp.org);
// Check the body for the presence of a title tag.
$titlePos = strpos($response->body, <title>);
// Get the status code for the response.
$code = $response->code;
http://php.net/manual/en/class.arrayaccess.php
http://www.php.net/manual/en/language.oop5.magic.php#language.oop5.magic.tostring
714
Getting headers from a response Following others places in core, the HttpSocket does not change the
casing of headers. RFC 261630 states that headers are case insensitive, and HttpSocket preserves the values
the remote host sends:
HTTP/1.1 200 OK
Date: Mon, 16 Apr 2007 04:14:16 GMT
server: CakeHttp Server
content-tyPe: text/html
Your $response->headers (or $response[header]) will contain the exact keys sent. In order
to safely access the header fields, its best to use getHeader(). If your headers looks like:
Date: Mon, 16 Apr 2007 04:14:16 GMT
server: CakeHttp Server
content-tyPe: text/html
http://tools.ietf.org/html/rfc2616.html
Utilities
715
mozilla certificate authority file to verify SSL certificates. You can use the following options to configure
how SSL certificates are handled:
ssl_verify_peer Set to false to disable SSL verification. This is not recommended.
ssl_verify_host Set to false if you wish to ignore hostname match errors when validating certificates.
ssl_allow_self_signed Set to true to enable self-signed certificates to be accepted. This requires ssl_verify_peer to be enabled.
ssl_cafile Set to the absolute path of the Certificate Authority file that you wish to use for verifying SSL certificates.
These options are provided as constructor arguments:
$socket = new HttpSocket(array(
ssl_allow_self_signed => true
));
Would allow self-signed certificates for all requests made with the created socket.
New in version 2.3: SSL certificate validation was added in 2.3.
Creating a custom response class You can create your own response class to use with HttpSocket. You
could create the file app/Lib/Network/Http/YourResponse.php with the content:
App::uses(HttpResponse, Network/Http);
class YourResponse extends HttpResponse {
public function parseResponse($message) {
parent::parseResponse($message);
// Make what you want
}
}
Changed in version 2.3: As of 2.3.0 you should extend HttpSocketResponse instead. This avoids a
common issue with the HTTP PECL extension.
Downloading the results HttpSocket has a new method called setContentResource(). By setting a resource with this method, the content will be written to this resource, using fwrite(). To you download a file,
you can do:
App::uses(HttpSocket, Network/Http);
716
Note: The headers are not included in file, you will only get the body content written to your resource. To
disable saving into the resource, use $http->setContentResource(false).
Using authentication
HttpSocket supports a HTTP Basic and Digest authentication methods out of the box. You can also create
custom authentication objects to support protocols like OAuth. To use any authentication system you need
to configure the HttpSocket instance:
App::uses(HttpSocket, Network/Http);
$http = new HttpSocket();
$http->configAuth(Basic, user, password);
The above would configure the HttpSocket instance to use Basic authentication using user and
password as the credentials.
Creating a custom authentication object You can now create your
thentication
method
to
use
with
HttpSocket.
You
could
create
app/Lib/Network/Http/YourMethodAuthentication.php with the content:
own
the
aufile
class YourMethodAuthentication {
/**
* Authentication
*
* @param HttpSocket $http
* @param array $authInfo
* @return void
*/
public static function authentication(HttpSocket $http, &$authInfo) {
// Do something, for example set $http->request[header][Authentication] value
}
}
To configure HttpSocket to use your auth configuration, you can use the new method configAuth():
$http->configAuth(YourMethod, array(config1 => value1, config2 => value2));
$http->get(http://secure.your-site.com);
Utilities
717
Using a HttpSocket with a proxy As part of auth configuration, you can configure proxy authentication.
You can create your customized method to proxy authentication in the same class of authentication. For
example:
class YourMethodAuthentication {
/**
* Authentication
*
* @param HttpSocket $http
* @param array $authInfo
* @return void
*/
public static function authentication(HttpSocket $http, &$authInfo) {
// Do something, for example set $http->request[header][Authentication] value
}
/**
* Proxy Authentication
*
* @param HttpSocket $http
* @param array $proxyInfo
* @return void
*/
public static function proxyAuthentication(HttpSocket $http, &$proxyInfo) {
// Do something, for example set $http->request[header][Proxy-Authentication] v
}
}
Note:
To use a proxy, you must call the HttpSocket::configProxy() similar to
HttpSocket::configAuth().
Inflector
class Inflector
The Inflector class takes a string and can manipulate it to handle word variations such as pluralizations
or camelizing and is normally accessed statically. Example: Inflector::pluralize(example)
returns examples.
You can try out the inflections online at inflector.cakephp.org31 .
static Inflector::pluralize($singular)
Input: Apple, Orange, Person, Man
Output: Apples, Oranges, People, Men
static Inflector::singularize($plural)
31
http://inflector.cakephp.org/
718
Utilities
719
There are only a few steps to go from a single-language application to a multi-lingual application, the first
of which is to make use of the __() function in your code. Below is an example of some code for a
single-language application:
<h2>Posts</h2>
To internationalize your code, all you need to do is to wrap strings in __() like so:
<h2><?php echo __(Posts); ?></h2>
If you do nothing further, these two code examples are functionally identical - they will both send the same
content to the browser. The __() function will translate the passed string if a translation is available, or
return it unmodified. It works similar to other Gettext32 implementations (as do the other translate functions,
such as __d() , __n() etc)
With your code ready to be multilingual, the next step is to create your pot file33 , which is the template for
all translatable strings in your application. To generate your pot file(s), all you need to do is run the i18n
console task, which will look for where youve used a translate function in your code and generate your pot
file(s) for you. You can and should re-run this console task any time you change the translations in your
code.
The pot file(s) themselves are not used by CakePHP, they are the templates used to create or update your po
files34 , which contain the translations. CakePHP will look for your po files in the following location:
/app/Locale/<locale>/LC_MESSAGES/<domain>.po
The default domain is default, therefore your locale folder would look something like this:
/app/Locale/eng/LC_MESSAGES/default.po (English)
/app/Locale/fra/LC_MESSAGES/default.po (French)
/app/Locale/por/LC_MESSAGES/default.po (Portuguese)
To create or edit your po files its recommended that you do not use your favorite editor. To create a po file
for the first time it is possible to copy the pot file to the correct location and change the extension however
unless youre familiar with their format, its quite easy to create an invalid po file or to save it as the wrong
32
http://en.wikipedia.org/wiki/Gettext
http://en.wikipedia.org/wiki/Gettext
34
http://en.wikipedia.org/wiki/Gettext
33
720
charset (if youre editing manually, use UTF-8 to avoid problems). There are free tools such as PoEdit35
which make editing and updating your po files an easy task; especially for updating an existing po file with
a newly updated pot file.
The three-character locale codes conform to the ISO 639-236 standard, although if you create regional locales
(en_US, en_GB, etc.) cake will use them if appropriate.
Warning: In 2.3 and 2.4 some language codes have been corrected to meet the ISO standard. Please
see the corresponding migration guides for details.
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.
// App Controller Code.
public function beforeFilter() {
$locale = Configure::read(Config.language);
if ($locale && file_exists(APP . View . DS . $locale . DS . $this->viewPath)) {
// e.g. use /app/View/fra/Pages/tos.ctp instead of /app/View/Pages/tos.ctp
$this->viewPath = $locale . DS . $this->viewPath;
}
}
or:
// View code
echo $this->element(Configure::read(Config.language) . /tos);
For translation of strings of LC_TIME category CakePHP uses POSIX compliant LC_TIME files. The i18n
functions of CakeTime utility class and helper TimeHelper use these LC_TIME files.
Just place LC_TIME file in its respective locale directory:
/app/Locale/fra/LC_TIME (French)
/app/Locale/por/LC_TIME (Portuguese)
You can find these files for few popular languages from the official Localized37 repo.
Internationalizing CakePHP Plugins
If you want to include translation files within your application youll need to follow a few conventions.
Instead of __() and __n() you will have to use __d() and __dn(). The D means domain. So if you have a
plugin called DebugKit you would have to do this:
__d(debug_kit, My example text);
Using the underscored syntax is important, if you dont use it CakePHP wont find your translation file.
Your translation file for this example should go into:
35
http://www.poedit.net
http://www.loc.gov/standards/iso639-2/php/code_list.php
37
https://github.com/cakephp/localized
36
Utilities
721
/app/Plugin/DebugKit/Locale/<locale>/LC_MESSAGES/<domain>.po
The reason for that is that CakePHP will use the lower cased and underscored plugin name to compare it to
the translation domain and is going to look into the plugin if there is a match for the given translation file.
Controlling the Translation Order
The Configure value I18n.preferApp can be used to control the order of translations. If set to true in
bootstrap it will prefer the app translations over any plugins ones:
Configure::write(I18n.preferApp, true);
It defaults to false.
New in version 2.6.
Localization in CakePHP
To change or set the language for your application, all you need to do is the following:
Configure::write(Config.language, fra);
This tells CakePHP which locale to use (if you use a regional locale, such as fr_FR, it will use the ISO 639238 locale as a fallback if it doesnt exist), you can change the language at any time during a request. e.g. in
your bootstrap if youre setting the application default language, in your (app) controller beforeFilter if its
specific to the request or user, or in fact anytime at all before you want a message in a different language.
To set the language for the current user, you can store the setting in the Session object, like this:
$this->Session->write(Config.language, fra);
At the beginning of each request in your controllers beforeFilter you should configure Configure
as well:
class AppController extends Controller {
public function beforeFilter() {
if ($this->Session->check(Config.language)) {
Configure::write(Config.language, $this->Session->read(Config.language));
}
}
}
Doing this will ensure that both I18n and TranslateBehavior access the same language value.
38
http://www.loc.gov/standards/iso639-2/php/code_list.php
722
Its 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 theyre 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 browsers 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.
CakePHP will automatically assume that all model validation error messages in your $validate array are
intended to be localized. When running the i18n shell these strings will also be extracted.
Theres one other aspect of localizing your application which is not covered by the use of the translate
functions, and that is date/money formats. Dont forget that CakePHP is PHP :), therefore to set the formats
for these things you need to use setlocale39 .
If you pass a locale that doesnt exist on your computer to setlocale40 it will have no effect. You can find the
list of available locales by running the command locale -a in a terminal.
Translating model validation errors
CakePHP will automatically extract the validation error when you are using the i18n console task. By
default, the default domain is used. This can be overwritten by setting the $validationDomain property
in your model:
class User extends AppModel {
public $validationDomain = validation_errors;
}
Additional parameters defined in the validation rule are passed to the translation function. This allows you
to create dynamic validation messages:
class User extends AppModel {
public $validationDomain = validation;
public $validate = array(
username => array(
length => array(
rule => array(between, 2, 10),
message => Username should be between %d and %d characters
)
)
)
}
http://www.php.net/setlocale
http://www.php.net/setlocale
Utilities
723
Logging
While CakePHP core Configure Class settings can really help you see whats happening under the hood,
there are certain times that youll need to log data to the disk in order to find out whats 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 whats 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. You can also use CakeLog::write() directly. See Writing to
logs
Creating and configuring log streams
When configuring a log stream the engine parameter is used to locate and load the log handler. All of the
other configuration properties are passed to the log streams constructor as an array.
App::uses(BaseLog, Log/Engine);
class DatabaseLog extends BaseLog {
public function __construct($options = array()) {
parent::__construct($options);
// ...
}
724
While CakePHP has no requirements for Log streams other than that they must implement a write method,
extending the BaseLog class has a few benefits:
It automatically handles the scope and type argument casting.
It implements the config() method which is required to make scoped logging work.
Each loggers write method must take two parameters $type, $message in that order. $type is the
string type of the logged message, core values are error, warning, info and debug. In addition you
can define your own types by using them when you call CakeLog::write. New in version 2.4.
As of 2.4 FileLog engine takes a few new options:
size Used to implement basic log file rotation. If log file size reaches specified size the existing
file is renamed by appending timestamp to filename and new log file is created. Can be integer bytes
value or human reabable string values like 10MB, 100KB etc. Defaults to 10MB.
rotate Log files are rotated specified times before being removed. If value is 0, old versions are
removed rather then rotated. Defaults to 10.
mask Set the file permissions for created files. If left empty the default permissions are used.
Warning:
Prior to 2.4 you had to include the suffix Log in your configuration
(LoggingPack.DatabaseLog). This is now not necessary anymore. If you have been using a
Log engine like DatabaseLogger that does not follow the convention to use a suffix Log for your
class name you have to adjust your class name to DatabaseLog. You should also avoid class names
like SomeLogLog which include the suffix twice at the end.
Note: Always configure loggers in app/Config/bootstrap.php Trying to use Application or plugin
loggers in core.php will cause issues, as application paths are not yet configured.
Also new in 2.4: In debug mode missing directories will now be automatically created to avoid unnecessary
errors thrown when using the FileEngine.
Errors and Exceptions can also be logged. By configuring the co-responding values in your core.php file.
Errors will be displayed when debug > 0 and logged when debug == 0. Set Exception.log to true to
log uncaught exceptions. See Configuration for more information.
Interacting with log streams
725
CakeLog::drop(). Once a log stream has been dropped, it will no longer receive messages.
Using the default FileLog class
While CakeLog can be configured to write to a number of user configured logging adapters, it also comes
with a default logging configuration. The default logging configuration will be used any time there are
no other logging adapters configured. Once a logging adapter has been configured you will need to also
configure FileLog if you want file logging to continue.
As its name implies FileLog writes log messages to files. The type of log message being written determines
the name of the file the message is stored in. If a type is not supplied, LOG_ERROR is used which writes
to the error log. The default log location is app/tmp/logs/$type.log:
// Executing this inside a CakePHP class
$this->log("Something didnt work!");
// Results in this being appended to app/tmp/logs/error.log
// 2007-11-02 10:22:02 Error: Something didnt work!
You can specify a custom log name using the first parameter. The default built-in FileLog class will treat
this log name as the file you wish to write logs to:
// called statically
CakeLog::write(activity, A special message for activity logging);
// Results in this being appended to app/tmp/logs/activity.log (rather than error.log)
// 2007-11-02 10:22:02 Activity: A special message for activity logging
The configured directory must be writable by the web server user in order for logging to work correctly.
You can configure additional/alternate FileLog locations using CakeLog::config(). FileLog accepts a
path which allows for custom paths to be used:
CakeLog::config(custom_path, array(
engine => File,
path => /path/to/custom/place/
));
Logging to Syslog
726
CakeLog::config(default, array(
engine => Syslog
));
The configuration array accepted for the Syslog logging engine understands the following keys:
format: An sprintf template strings with two placeholders, the first one for the error type, and the second for the message itself. This key is useful to add additional information about the server or process
in the logged message. For example: %s - Web Server 1 - %s will look like error - Web
Server 1 - An error occurred in this request after replacing the placeholders.
prefix: An string that will be prefixed to every logged message.
flag: An integer flag to be used for opening the connection to the logger, by default LOG_ODELAY
will be used. See openlog documentation for more options
facility: The logging slot to use in syslog. By default LOG_USER is used. See syslog documentation
for more options
Writing to logs
Writing to the log files can be done in 2 different ways. The first is to use the static CakeLog::write()
method:
CakeLog::write(debug, Something did not work);
The second is to use the log() shortcut function available on any class that extends Object. Calling log()
will internally call CakeLog::write():
// Executing this inside a CakePHP class:
$this->log("Something did not work!", debug);
All configured log streams are written to sequentially each time CakeLog::write() is called.
Changed in version 2.5.
CakeLog does not auto-configure itself anymore. As a result log files will not be auto-created anymore if no
stream is listening. Make sure you got at least one default stream set up, if you want to listen to all types
and levels. Usually, you can just set the core FileLog class to output into app/tmp/logs/:
CakeLog::config(default, array(
engine => File
));
Logging Scopes
Utilities
727
CakePHP exposes this concept as logging scopes. When log messages are written you can include a scope
name. If there is a configured logger for that scope, the log messages will be directed to those loggers. If a
log message is written to an unknown scope, loggers that handle that level of message will log the message.
For example:
// Configure tmp/logs/shop.log to receive the two configured types (log levels), but only
// those with orders and payments as scope
CakeLog::config(shop, array(
engine => FileLog,
types => array(warning, error),
scopes => array(orders, payments),
file => shop.log,
));
// Configure tmp/logs/payments.log to receive the two configured types, but only
// those with payments as scope
CakeLog::config(payments, array(
engine => SyslogLog,
types => array(info, error, warning),
scopes => array(payments)
));
CakeLog::warning(This gets written only to shops stream, orders);
CakeLog::warning(This gets written to both shops and payments streams, payments);
CakeLog::warning(This gets written to both shops and payments streams, unknown);
class CakeLog
A simple class for writing to logs.
static CakeLog::config($name, $config)
Parameters
$name (string) Name for the logger being connected, used to drop a logger later
on.
$config (array) Array of configuration information and constructor arguments
for the logger.
Connect a new logger to CakeLog. Each connected logger receives all log messages each time a log
message is written.
static CakeLog::configured
Returns An array of configured loggers.
728
To set/replace an existing configuration, pass an array with the second argument set to false:
CakeLog::levels(array(user0, user1), false);
static CakeLog::defaultLevels
Returns An array of the default log levels values.
Resets log levels to their original values:
array(
emergency => LOG_EMERG,
alert
=> LOG_ALERT,
critical => LOG_CRIT,
Utilities
729
error
warning
notice
info
debug
=>
=>
=>
=>
=>
LOG_ERR,
LOG_WARNING,
LOG_NOTICE,
LOG_INFO,
LOG_DEBUG,
);
static CakeLog::enabled($streamName)
Returns boolean
Checks whether $streamName has been enabled.
static CakeLog::enable($streamName)
Returns void
Enable the stream $streamName.
static CakeLog::disable($streamName)
Returns void
Disable the stream $streamName.
static CakeLog::stream($streamName)
Returns Instance of BaseLog or false if not found.
Gets $streamName from the active streams.
Convenience methods New in version 2.2.
The following convenience methods were added to log $message with the appropriate log level.
static CakeLog::emergency($message, $scope = array())
static CakeLog::alert($message, $scope = array())
static CakeLog::critical($message, $scope = array())
static CakeLog::notice($message, $scope = array())
static CakeLog::debug($message, $scope = array())
static CakeLog::info($message, $scope = array())
CakeNumber
class CakeNumber
If you need NumberHelper functionalities outside of a View, use the CakeNumber class:
class UsersController extends AppController {
public $components = array(Auth);
730
New in version 2.1: CakeNumber has been factored out from NumberHelper.
All of these functions return the formatted number; They do not automatically echo the output into the view.
CakeNumber::currency(mixed $number, string $currency = USD, array $options = array())
Parameters
$number (float) The value to covert.
$currency (string) The known currency format to use.
$options (array) Options, see below.
This method is used to display a number in common currency formats (EUR,GBP,USD). Usage in a
view looks like:
// called as NumberHelper
echo $this->Number->currency($number, $currency);
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::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:
$currency
EUR
GBP
USD
The third parameter is an array of options for further defining the output. The following options are
available:
Utilities
731
Option
before
after
zero
places
thousands
decimals
negative
escape
wholeSymbol
wholePosition
fractionSymbol
fractionPosition
fractionExponent
Description
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 no decimal symbol. eg. 0.35 => $0.35.
The text to use for zero values, can be a string or a number. ie. 0, Free!
Number of decimal places to use. ie. 2
Thousands separator ie. ,
Decimal separator symbol ie. .
Symbol for negative numbers. If equal to (), the number will be wrapped with (
and )
Should the output be htmlentity escaped? Defaults to true
String to use for whole numbers ie. dollars
Either before or after to place the whole symbol
String to use for fraction numbers ie. cents
Either before or after to place the fraction symbol
Fraction exponent of this specific currency. Defaults to 2.
732
Add a currency format to the Number helper. Makes reusing currency formats easier:
// called as NumberHelper
$this->Number->addFormat(BRL, array(before => R$, thousands => ., decimals
// called as CakeNumber
App::uses(CakeNumber, Utility);
CakeNumber::addFormat(BRL, array(before => R$, thousands => ., decimals =>
You can now use BRL as a short form when formatting currency amounts:
// called as NumberHelper
echo $this->Number->currency($value, BRL);
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::currency($value, BRL);
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
,
before,
false,
after,
0,
2,
,,
.,
(),
true,
2
Utilities
733
App::uses(CakeNumber, Utility);
echo CakeNumber::precision(456.91873645, 2);
Description
Boolean to indicate whether the value has to be multiplied by 100. Useful for decimal
percentages.
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.
// Called as NumberHelper. Output: 45.69%
echo $this->Number->toPercentage(45.691873645);
// Called as CakeNumber. Output: 45.69%
App::uses(CakeNumber, Utility);
echo CakeNumber::toPercentage(45.691873645);
// Called with multiply. Output: 45.69%
echo CakeNumber::toPercentage(0.45691, 2, array(
multiply => true
));
New in version 2.4: The $options argument with the multiply option was added.
CakeNumber::fromReadableSize(string $size, $default)
Parameters
$size (string) The formatted human readable value.
This method unformats a number from a human readable byte size to an integer number of bytes.
New in version 2.3: This method was added in 2.3
CakeNumber::toReadableSize(string $dataSize)
Parameters
$dataSize (string) The number of bytes to make readable.
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):
// called as NumberHelper
echo $this->Number->toReadableSize(0); // 0 Bytes
734
echo $this->Number->toReadableSize(1024); // 1 KB
echo $this->Number->toReadableSize(1321205.76); // 1.26 MB
echo $this->Number->toReadableSize(5368709120); // 5.00 GB
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::toReadableSize(0); // 0 Bytes
echo CakeNumber::toReadableSize(1024); // 1 KB
echo CakeNumber::toReadableSize(1321205.76); // 1.26 MB
echo CakeNumber::toReadableSize(5368709120); // 5.00 GB
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
Example:
// called as NumberHelper
echo $this->Number->format(123456.7890, array(
places => 2,
before => ,
escape => false,
decimals => .,
thousands => ,
));
// output 123,456.79
// called as CakeNumber
App::uses(CakeNumber, Utility);
Utilities
735
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 takes the same keys as CakeNumber::format() itself:
places (integer): the amount of desired precision
before (string): to be put before the outputted number
after (string): to be put after the outputted number
decimals (string): used to delimit the decimal places in a number
thousands (string): used to mark off thousand, millions, . . . places
Example:
// called as NumberHelper
echo $this->Number->formatDelta(123456.7890, array(
places => 2,
decimals => .,
thousands => ,
));
// output +123,456.79
// called as CakeNumber
App::uses(CakeNumber, Utility);
echo CakeNumber::formatDelta(123456.7890, array(
places => 2,
decimals => .,
thousands => ,
));
// output +123,456.79
736
Router
Router can be used to parse URLs into arrays containing indexes for the controller, action, and any parameters, and the opposite: to convert URL arrays (eg. array(controller => posts, action => index)) to
string URLs.
Read more about ways to configure the Router and the Router class.
Data Sanitization
The Sanitize class is deprecated as of 2.4, and will be removed in CakePHP 3.0. Instead of using the
Sanitize class you can accomplish the same tasks using other parts of CakePHP, native PHP functions, or
other libraries.
Input filtering
Instead of using the destructive input filtering features of Sanitize class you should instead apply more
thorough Data Validation to the user data your application accepts. By rejecting invalid input you can often
remove the need to destructively modify user data. You might also want to look at PHPs filter extension41
in situations you need to modify user input.
Accepting user submitted HTML
Often input filtering is used when accepting user-submitted HTML. In these situations it is best to use a
dedicated library like HTML Purifier42 .
SQL Escaping
CakePHP handles SQL escaping on all parameters to Model::find() and Model::save(). In the
rare case you need to construct SQL by hand using user input you should using Prepared Statements.
Security
class Security
The security library43 handles basic security measures such as providing methods for hashing and encrypting
data.
41
http://php.net/filter
http://htmlpurifier.org/
43
http://api.cakephp.org/2.4/class-Security.html
42
Utilities
737
Security API
Warning: cipher() uses a weak XOR cipher and should not be used. It is only included for
backwards compatibility.
static Security::rijndael($text, $key, $mode)
Parameters
$text (string) The text to encrypt
$key (string) The key to use for encryption. This must be longer than 32 bytes.
$mode (string) The mode to use, either encrypt or decrypt
Encrypts/Decrypts text using the rijndael-256 cipher. This requires the mcrypt extension44 to be
installed:
rijndael() can be used to store data you need to decrypt later, like the contents of cookies. It
should never be used to store passwords. Instead you should use the one way hashing methods
provided by hash()
New in version 2.2: Security::rijndael() was added in 2.2.
static Security::encrypt($text, $key, $hmacSalt = null)
Parameters
$plain (string) The value to encrypt.
$key (string) The 256 bit/32 byte key to use as a cipher key.
$hmacSalt (string) The salt to use for the HMAC process. Leave null to use
Security.salt.
44
http://php.net/mcrypt
738
Encrypt $text using AES-256. The $key should be a value with a lots of variance in the data, much
like a good password. The returned result will be the encrypted value with an HMAC checksum.
This method should never be used to store passwords. Instead you should use the one way hashing
methods provided by hash(). An example use would be:
// Assuming key is stored somewhere it can be re-used for
// decryption later.
$key = wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA;
$result = Security::encrypt($value, $key);
If the value cannot be decrypted due to changes in the key or HMAC salt false will be returned.
New in version 2.5.
static Security::generateAuthKey()
Return type string
Generate authorization hash.
static Security::getInstance()
Return type object
Singleton implementation to get object instance.
static Security::hash($string, $type = NULL, $salt = false)
Return type string
Create a hash from a string using given method or fallback to next available method. If $salt is set
to true, the applications salt value will be used:
Utilities
739
hash() also supports other secure hashing algorithms like bcrypt. When using bcrypt, you should
be mindful of the slightly different usage. Creating an initial hash works the same as other algorithms:
// Create a hash using bcrypt
Security::setHash(blowfish);
$hash = Security::hash(CakePHP Framework);
Unlike other hash types comparing plain text values to hashed values should be done as follows:
// $storedPassword, is a previously generated bcrypt hash.
$newHash = Security::hash($newPassword, blowfish, $storedPassword);
When comparing values hashed with bcrypt, the original hash should be provided as the $salt
parameter. This allows bcrypt to reuse the same cost and salt values, allowing the generated hash to
return the same resulting hash, given the same input value.
Changed in version 2.3: Support for bcrypt was added in 2.3
static Security::setHash($hash)
Return type void
Sets the default hash method for the Security object. This affects all objects using Security::hash().
static Security::validateAuthKey($authKey)
Return type boolean
Validate authorization hash.
Set
class Set
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.
CakePHPs Set class can be called from any model or controller in the same way Inflector is called. Example:
Set::combine().
Deprecated since version 2.2: The Set class has been deprecated in 2.2 in favour of the Hash class. It offers
a more consistent interface and API.
740
The Path syntax is used by (for example) sort, and is used to define a path.
Usage example (using Set::sort()):
$a = array(
0 => array(Person => array(name =>
1 => array(Person => array(name =>
2 => array(Person => array(name =>
);
$result = Set::sort($a, {n}.Person.name,
/* result now looks like
array(
0 => array(Person => array(name =>
1 => array(Person => array(name =>
2 => array(Person => array(name =>
);
*/
As you can see in the example above, some things are wrapped in {}s, others not. In the table below, you
can see which options are available.
Expression
{n}
{s}
Foo
{[a-z]+}
Definition
Represents a numeric key
Represents a string
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 expression.
Utilities
741
$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
742
{n}.Article.title);
(
[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 2
$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))
);
$result = Set::classicExtract($a, {n}.{s}.name);
/* $result now looks like:
Array
(
[0] => Array
(
[0] => page
)
[1] => Array
(
[0] => fruit
)
)
*/
$result = Set::classicExtract($a, {s}.{n}.name);
/* $result now looks like:
Array
(
[0] => Array
(
[0] => jippi
)
[1] => Array
(
[0] => jippi
)
)
*/
$result = Set::classicExtract($a,{\w+}.{\w+}.name);
/* $result now looks like:
Array
(
Utilities
743
744
)
[1] => Array
(
[0] => jippi
)
)
*/
$result = Set::classicExtract($a,{s});
/* $result now looks like:
Array
(
[0] => Array
(
[0] => Array
(
[name] => jippi
)
)
[1] => Array
(
[0] => Array
(
[name] => jippi
)
)
)
/
*
$result = Set::classicExtract($a,{[a-z]});
/* $result now looks like:
Array
(
[test] => Array
(
[0] => Array
(
[name] => jippi
)
)
[dot.test] => Array
(
[0] => Array
(
[name] => jippi
)
)
)
*/
$result = Set::classicExtract($a, {dot\.test}.{n});
/* $result now looks like:
Array
(
Utilities
745
746
)
)
);
$result = Set::combine($a, {n}.User.id);
/* $result now looks like:
Array
(
[2] =>
[14] =>
[25] =>
)
*/
$result = Set::combine($a, {n}.User.id, {n}.User.non-existent);
/* $result now looks like:
Array
(
[2] =>
[14] =>
[25] =>
)
*/
$result = Set::combine($a, {n}.User.id, {n}.User.Data);
/* $result now looks like:
Array
(
[2] => Array
(
[user] => mariano.iglesias
[name] => Mariano Iglesias
)
[14] => Array
(
[user] => phpnut
[name] => Larry E. Masters
)
[25] => Array
(
[user] => gwoo
[name] => The Gwoo
)
)
/
*
$result = Set::combine($a, {n}.User.id, {n}.User.Data.name);
/* $result now looks like:
Array
(
[2] => Mariano Iglesias
[14] => Larry E. Masters
[25] => The Gwoo
)
Utilities
747
*/
$result = Set::combine($a, {n}.User.id, {n}.User.Data, {n}.User.group_id);
/* $result now looks like:
Array
(
[1] => Array
(
[2] => Array
(
[user] => mariano.iglesias
[name] => Mariano Iglesias
)
[25] => Array
(
[user] => gwoo
[name] => The Gwoo
)
)
[2] => Array
(
[14] => Array
(
[user] => phpnut
[name] => Larry E. Masters
)
)
)
*/
$result = Set::combine($a, {n}.User.id, {n}.User.Data.name, {n}.User.group_id);
/* $result now looks like:
Array
(
[1] => Array
(
[2] => Mariano Iglesias
[25] => The Gwoo
)
[2] => Array
(
[14] => Larry E. Masters
)
)
/
*
748
Utilities
749
Determines if one Set or array contains the exact keys and values of another:
$a = array(
0 => array(name
1 => array(name
);
$b = array(
0 => array(name
1 => array(name
2 => array(name
a => b
);
=> main),
=> about)
=> main),
=> about),
=> contact),
$data = array(1 => array(1.1 => 1.1.1), 2, 3 => array(3.1 => array(3.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
$result = Set::countDim($data, true);
// $result == 3
750
$data = array(1 => array(1.1 => 1.1.1), array(2 => array(2.1 => array(2.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
$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
$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
$result = Set::countDim($set, false, 0);
// $result == 2
$result = Set::countDim($set, true);
// $result == 5
=> main),
=> about)
=> main),
=> about),
=> contact)
Utilities
751
(
[name] => about
)
)
*/
$result = Set::diff(array(), $b);
/* $result now looks like:
Array
(
[0] => Array
(
[name] => main
)
[1] => Array
(
[name] => about
)
[2] => Array
(
[name] => contact
)
)
/
*
$b = array(
0 => array(name => me),
1 => array(name => about)
);
$result = Set::diff($a, $b);
/* $result now looks like:
Array
(
[0] => Array
(
[name] => main
)
)
/
*
752
the
function
testExtract()
in
static Set::filter($var)
Return type array
Filters empty elements out of a route array, excluding 0:
Utilities
753
looks like:
=>
=>
=>
=>
(
0
1
0
Array
[0] => one thing
[1] => I can tell you
[2] => is you got to be
)
)
*/
754
Utilities
755
756
Array
(
[pages] => Array
(
[0] => Array
(
[name] => main
)
[1] => Array
(
[name] => about
[vars] => Array
(
[title] => page title
)
)
)
)
*/
Utilities
757
);
$mapped = Set::map($data);
/* $mapped now looks like:
Array
(
[0] => stdClass Object
(
[_name_] => IndexedPage
[id] => 1
[url] => http://blah.com/
[hash] => 68a9f053b19526d08e36c6a9ad150737933816a5
[get_vars] =>
[redirect] =>
[created] => 1195055503
[updated] => 1195055503
)
[1] => stdClass Object
(
[_name_] => IndexedPage
[id] => 2
[url] => http://blah.com/
[hash] => 68a9f053b19526d08e36c6a9ad150737933816a5
[get_vars] =>
[redirect] =>
[created] => 1195055503
[updated] => 1195055503
)
)
*/
758
$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-existent), $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
Utilities
759
),
array(
id => 48c257a8-cf7c-4af2-ac2f-114ecbdd56cb,
name => pbpaste | grep -i Unpaid | pbcopy,
description => Remove all lines that say "Unpaid".,
)
);
$arry2
$arry3
$arry4
$res =
= 4;
= array(0 => test array, cats => dogs, people => 1267);
= array(cats => felines, dog => angry);
Set::merge($arry1, $arry2, $arry3, $arry4);
760
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
array(id
array(id
array(id
array(id
array(id
array(id
array(id
array(id
array(id
array(id
=>
=>
=>
=>
=>
=>
=>
=>
=>
=>
);
$result = Set::nest($data, array(root => 6));
/* $result now looks like:
array(
(int) 0 => array(
ModelName => array(
id => (int) 6,
parent_id => null
),
children => array(
(int) 0 => array(
ModelName => array(
id => (int) 7,
parent_id => (int)
),
children => array()
),
(int) 1 => array(
ModelName => array(
id => (int) 8,
parent_id => (int)
),
children => array()
),
(int) 2 => array(
ModelName => array(
id => (int) 9,
parent_id => (int)
),
children => array()
),
(int) 3 => array(
ModelName => array(
id => (int) 10,
parent_id => (int)
),
children => array()
)
)
)
) */
Utilities
761
762
(
[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)); // Now merge the two and normalize
/* $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] =>
)
*/
static Set::numeric($array=null)
Utilities
763
764
$array1 = array(ModelOne => array(id => 1001, field_one => a1.m1.f1, field_tw
$array2 = array(ModelOne => array(id => 1003, field_one => a3.m1.f1, field_tw
$res = Set::pushDiff($array1, $array2);
/* $res now looks like:
Array
(
[ModelOne] => Array
(
[id] => 1001
[field_one] => a1.m1.f1
[field_two] => a1.m1.f2
[field_three] => a3.m1.f3
)
)
/
*
Example 2
$array1 = array("a" => "b", 1 => 20938, "c" => "string");
$array2 = array("b" => "b", 3 => 238, "c" => "string", array("extra_field"));
$res = Set::pushDiff($array1, $array2);
/* $res now looks like:
Array
(
[a] => b
[1] => 20938
[c] => string
[b] => b
[3] => 238
[4] => Array
(
[0] => extra_field
)
)
/
*
Utilities
765
static Set::reverse($object)
Return type array
Set::reverse is basically the opposite of Set::map. It converts an object into an array. If $object is
not an object, reverse will simply return $object.
$result = Set::reverse(null);
// Null
$result = Set::reverse(false);
// false
$a = array(
Post => array(id => 1, title => First Post),
Comment => array(
array(id => 1, title => First Comment),
array(id => 2, title => Second Comment)
),
Tag => array(
array(id => 1, title => First Tag),
array(id => 2, title => Second Tag)
),
);
$map = Set::map($a); // Turn $a into a class object
/* $map now looks like:
stdClass Object
(
[_name_] => Post
[id] => 1
[title] => First Post
[Comment] => Array
(
[0] => stdClass Object
(
[id] => 1
[title] => First Comment
)
[1] => stdClass Object
(
[id] => 2
[title] => Second Comment
)
766
)
[Tag] => Array
(
[0] => stdClass
(
[id] =>
[title]
)
[1] => stdClass
(
[id] =>
[title]
)
)
Object
1
=> First Tag
Object
2
=> Second Tag
)
*/
$result = Set::reverse($map);
/* $result now looks like:
Array
(
[Post] => Array
(
[id] => 1
[title] => First Post
[Comment] => Array
(
[0] => Array
(
[id] =>
[title]
)
[1] => Array
(
[id] =>
[title]
)
)
[Tag] => Array
(
[0] => Array
(
[id] =>
[title]
)
[1] => Array
(
[id] =>
[title]
)
)
)
)
Utilities
1
=> First Comment
2
=> Second Comment
1
=> First Tag
2
=> Second Tag
767
*/
$result = Set::reverse($a[Post]); // Just return the array
/* $result now looks like:
Array
(
[id] => 1
[title] => First Post
)
*/
desc);
String
class String
The String class includes convenience methods for creating and manipulating strings and is normally accessed statically. Example: String::uuid().
If you need TextHelper functionalities outside of a View, use the String class:
class UsersController extends AppController {
public $components = array(Auth);
768
Changed in version 2.1: Several methods from TextHelper have been moved to String class.
static String::uuid
The UUID method is used to generate unique identifiers as per RFC 412245 . The UUID is a 128bit
string in the format of 485fc381-e790-47a3-9794-1337c0a8fe68.
String::uuid(); // 485fc381-e790-47a3-9794-1337c0a8fe68
String::insert(My name is :name and I am :age years old., array(name => Bob, ag
// generates: "My name is Bob and I am 65 years old."
http://tools.ietf.org/html/rfc4122.html
Utilities
769
You can provide an array of options that control how wrapping is done. The supported options are:
width The width to wrap to. Defaults to 72.
wordWrap Whether or not to wrap whole words. Defaults to true.
indent The character to indent lines with. Defaults to .
indentAt The line number to start indenting text. Defaults to 0.
String::highlight(string $haystack, string $needle, array $options = array())
Parameters
$haystack (string) The string to search.
$needle (string) The string to find.
$options (array) An array of options, see below.
Highlights $needle in $haystack using the $options[format] string specified or a default string.
Options:
format - string The piece of HTML with that the phrase will be highlighted
html - bool If true, will ignore any HTML tags, ensuring that only the correct text is highlighted
Example:
// called as TextHelper
echo $this->Text->highlight(
$lastSentence,
using,
array(format => <span class="highlight">\1</span>)
);
// called as String
App::uses(String, Utility);
echo String::highlight(
$lastSentence,
using,
array(format => <span class="highlight">\1</span>)
);
Output:
770
String::stripLinks($text)
Strips the supplied $text of any HTML links.
String::truncate(string $text, int $length=100, array $options)
Parameters
$text (string) The text to truncate.
$length (int) The length, in characters, beyond which the text should be truncated.
$options (array) An array of options to use.
If $text is longer than $length characters, this method truncates it at $length and adds a prefix
consisting of ellipsis, if defined. If exact is passed as false, the truncation will occur
at the first whitespace after the point at which $length is exceeded. If html is passed as true,
HTML tags will be respected and will not be cut off.
$options is used to pass all extra parameters, and has the following possible keys by default, all of
which are optional:
array(
ellipsis => ...,
exact => true,
html => false
)
Example:
// called as TextHelper
echo $this->Text->truncate(
The killer crept forward and tripped on the rug.,
22,
array(
ellipsis => ...,
exact => false
)
);
// called as String
App::uses(String, Utility);
echo String::truncate(
The killer crept forward and tripped on the rug.,
22,
array(
ellipsis => ...,
exact => false
)
);
Output:
Utilities
771
Changed in version 2.3: ending has been replaced by ellipsis. ending is still used in 2.2.1
String::tail(string $text, int $length=100, array $options)
Parameters
$text (string) The text to truncate.
$length (int) The length, in characters, beyond which the text should be truncated.
$options (array) An array of options to use.
If $text is longer than $length characters, this method removes an initial substring with length
consisting of the difference and prepends a suffix consisting of ellipsis, if defined. If
exact is passed as false, the truncation will occur at the first whitespace prior to the point
at which truncation would otherwise take place.
$options is used to pass all extra parameters, and has the following possible keys by default, all of
which are optional:
array(
ellipsis => ...,
exact => true
)
772
Output:
...a TV, a C# program that can divide by zero, death metal t-shirts
Output:
... by $radius, and prefix/suffix with $ellipsis. This method is
especially handy for search results. The query...
Output:
red, orange, yellow, green, blue, indigo and violet
Utilities
773
CakeTime
class CakeTime
If you need TimeHelper functionalities outside of a View, use the CakeTime class:
class UsersController extends AppController {
public $components = array(Auth);
public function afterLogin() {
App::uses(CakeTime, Utility);
if (CakeTime::isToday($this->Auth->user(date_of_birth]))) {
// greet user with a happy birthday message
$this->Session->setFlash(__(Happy birthday you...));
}
}
}
New in version 2.1: CakeTime has been factored out from TimeHelper.
Formatting
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
CakeTime::convertSpecifiers($format, $time = NULL)
Return type string
Converts a string representing the format for the function strftime and returns a windows safe and
i18n aware format.
CakeTime::dayAsSql($dateString, $field_name, $timezone = NULL)
Return type string
Creates a string in the same format as daysAsSql but only needs a single date object:
774
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
CakeTime::daysAsSql($begin, $end, $fieldName, $timezone = NULL)
Return type string
Returns a string in the format ($field_name >= 2008-01-21 00:00:00) AND ($field_name <= 200801-25 23:59:59). This is handy if you need to search for records between two dates inclusively:
// called via TimeHelper
echo $this->Time->daysAsSql(Aug 22, 2011, Aug 25, 2011, created);
// (created >= 2011-08-22 00:00:00) AND
// (created <= 2011-08-25 23:59:59)
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::daysAsSql(Aug 22, 2011, Aug 25, 2011, created);
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
CakeTime::format($date, $format = NULL, $default = false, $timezone = NULL)
Return type string
Will return a string formatted to the given format using the PHP strftime() formatting options46 :
// called via TimeHelper
echo $this->Time->format(2011-08-22 11:53:00, %B %e, %Y %H:%M %p);
// August 22, 2011 11:53 AM
echo $this->Time->format(+2 days, %c);
// 2 days from now formatted as Sun, 13 Nov 2011 03:36:10 AM EET
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::format(2011-08-22 11:53:00, %B %e, %Y %H:%M %p);
echo CakeTime::format(+2 days, %c);
46
http://www.php.net/manual/en/function.strftime.php
Utilities
775
You can also provide the date/time as the first argument. When doing this you should use strftime
compatible formatting. This call signature allows you to leverage locale aware date formatting which
is not possible using date() compatible formatting:
// called via TimeHelper
echo $this->Time->format(2012-01-13, %d-%m-%Y, invalid);
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::format(2011-08-22, %d-%m-%Y);
Changed in version 2.2: $format and $date parameters are in opposite order as used in 2.1 and below. $timezone parameter replaces $userOffset parameter used in 2.1 and below. $default
parameter replaces $invalid parameter used in 2.1 and below.
New in version 2.2: $date parameter now also accepts a DateTime object.
CakeTime::fromString($dateString, $timezone = NULL)
Return type string
Takes a string and uses strtotime47 to convert it into a date integer:
// called via TimeHelper
echo $this->Time->fromString(Aug 22, 2011);
// 1313971200
echo $this->Time->fromString(+1 days);
// 1321074066 (+1 day from current date)
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::fromString(Aug 22, 2011);
echo CakeTime::fromString(+1 days);
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
CakeTime::gmt($dateString = NULL)
Return type integer
Will return the date as an integer set to Greenwich Mean Time (GMT).
// called via TimeHelper
echo $this->Time->gmt(Aug 22, 2011);
// 1313971200
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::gmt(Aug 22, 2011);
http://us.php.net/manual/en/function.date.php
776
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
CakeTime::serverOffset()
Return type integer
Returns servers offset from GMT in seconds.
CakeTime::timeAgoInWords($dateString, $options = array())
Return type string
Will take a datetime string (anything that is parsable by PHPs strtotime() function or MySQLs datetime format) and convert it into a friendly word format like, 3 weeks, 3 days ago:
Utilities
777
Use the end option to determine the cutoff point to no longer will use words; default +1 month:
// called via TimeHelper
echo $this->Time->timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y, end => +1 year)
);
// On Nov 10th, 2011 it would display: 2 months, 2 weeks, 6 days ago
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::timeAgoInWords(
Aug 22, 2011,
array(format => F jS, Y, end => +1 year)
);
Use the accuracy option to determine how precise the output should be. You can use this to limit the
output:
// If $timestamp is 1 month, 1 week, 5 days and 6 hours ago
echo CakeTime::timeAgoInWords($timestamp, array(
accuracy => array(month => month),
end => 1 year
));
// Outputs 1 month ago
778
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
CakeTime::toQuarter($dateString, $range = false)
Return type mixed
Will return 1, 2, 3 or 4 depending on what quarter of the year the date falls in. If range is set to true, a
two element array will be returned with start and end dates in the format 2008-03-31:
// called via TimeHelper
echo $this->Time->toQuarter(Aug 22, 2011);
// Would print 3
$arr = $this->Time->toQuarter(Aug 22, 2011, true);
/*
Array
(
[0] => 2011-07-01
[1] => 2011-09-30
)
*/
// called as CakeTime
App::uses(CakeTime, Utility);
echo CakeTime::toQuarter(Aug 22, 2011);
$arr = CakeTime::toQuarter(Aug 22, 2011, true);
New in version 2.2: $dateString parameter now also accepts a DateTime object.
New in version 2.4: The new option parameters relativeString (defaults to %s ago) and
absoluteString (defaults to on %s) to allow customization of the resulting output string are
now available.
CakeTime::toRSS($dateString, $timezone = NULL)
Return type string
Will return a date string in the RSS format Sat, 12 Jan 2008 00:00:00 -0500
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
CakeTime::toUnix($dateString, $timezone = NULL)
Return type integer
A wrapper for fromString.
Changed in version 2.2: $timezone parameter replaces $userOffset parameter used in 2.1 and
below.
New in version 2.2: $dateString parameter now also accepts a DateTime object.
CakeTime::toServer($dateString, $timezone = NULL, $format = Y-m-d H:i:s)
Utilities
779
wasWithinLast takes a time interval which is a string in the format 3 months and accepts a time
interval of seconds, minutes, hours, days, weeks, months and years (plural and not). If a time interval
is not recognized (for example, if it is mistyped) then it will default to days.
780
Xml
class Xml
The Xml class was all refactored. As PHP 5 has SimpleXML48 and DOMDocument49 , CakePHP doesnt
need to re-implement an XML parser. The new XML class will transform an array into SimpleXMLElement
or DOMDocument objects, and vice versa.
Importing data to Xml class
In CakePHP 1.3 you can pass array, XML as string, URL or file path to the constructor of the Xml class to
import data. In CakePHP 2.0 you can do it using Xml::build(). Unless the return is an Xml object, it
will return a SimpleXMLElement or DOMDocument object (depending of your options parameter - default
is SimpleXMLElement). Below the samples how to import data from URL:
//First Load the Utility Class
App::uses(Xml, Utility);
// Old method:
$xml = new Xml(http://bakery.cakephp.org/articles.rss);
// New method using SimpleXML
$xml = Xml::build(http://bakery.cakephp.org/articles.rss);
// $xml now is a instance of SimpleXMLElement
//or
$xml = Xml::build(http://bakery.cakephp.org/articles.rss, array(return => simplexml))
// $xml now is a instance of SimpleXMLElement
You can use Xml::build() to build XML objects from a variety of sources. You can use XML to build
objects from string data:
$text = <?xml version="1.0" encoding="utf-8"?>
<post>
<id>1</id>
<title>Best post</title>
<body> ... </body>
</post>;
$xml = Xml::build($text);
You can also build Xml objects from either local files, or remote files. Remote files will be fetched with
HttpSocket:
// local file
$xml = Xml::build(/home/awesome/unicorns.xml);
48
49
http://php.net/simplexml
http://php.net/domdocument
Utilities
781
// remote file
$xml = Xml::build(http://bakery.cakephp.org/articles.rss);
Note: DOMDocument50 and SimpleXML51 implement different APIs. Be sure to use the correct methods
on the object you request from Xml.
Converting XML strings into arrays is simple with the Xml class as well. By default youll get a SimpleXml
object back:
//Old method:
$xmlString = <?xml version="1.0"?><root><child>value</child></root>;
$xmlObject = new Xml($xmlString);
$xmlArray = $xmlObject->toArray();
// New method:
$xmlString = <?xml version="1.0"?><root><child>value</child></root>;
$xmlArray = Xml::toArray(Xml::build($xmlString));
http://php.net/domdocument
http://php.net/simplexml
782
// New method:
$xmlArray = array(root => array(child => value));
$xmlObject = Xml::fromArray($xmlArray, array(format => tags)); // You can use Xml::buil
$xmlString = $xmlObject->asXML();
Your array must have only one element in the top level and it can not be numeric. If the array is not in this
format, Xml will throw a Exception. Examples of invalid arrays:
// Top level with numeric key
array(
array(key => value)
);
// Multiple keys in top level
array(
key1 => first value,
key2 => other value
);
Warning: The default format option was changed from attributes to tags. This was done to make the
Xml that the Xml class generates more compatible with XML in the wild. Be careful if you depend of
this. In the new version you can create a mixed array with tags, attributes and value, just use format
as tags (or do not say anything, because it is the default value) and prefix keys that are supposed to be
attributes with @. For value text, use @ as the key.
$xmlArray = array(
project => array(
@id => 1,
name => Name of project, as tag,
@ => Value of project
)
);
$xmlObject = Xml::fromArray($xmlArray);
$xmlString = $xmlObject->asXML();
Note: The structure of array was changed. Now the child must have in a sub-tree and not in the same tree.
Moreover, the strings not will be changed by Inflector. See the sample below:
$oldArray = array(
Projects => array(
array(
Project => array(id => 1, title => Project 1),
Industry => array(id => 1, name => Industry 1)
Utilities
783
),
array(
Project => array(id => 2, title => Project 2),
Industry => array(id => 2, name => Industry 2)
)
)
);
$newArray = array(
projects => array(
project => array(
array(
id => 1, title => Project
industry => array(id => 1,
),
array(
id => 2, title => Project
industry => array(id => 2,
)
)
)
);
1,
name => Industry 1)
2,
name => Industry 2)
Using Namespaces To use XML Namespaces, in your array you must create a key with name xmlns:
to generic namespace or input the prefix xmlns: in a custom namespace. See the samples:
$xmlArray = array(
root => array(
xmlns: => http://cakephp.org,
child => value
784
)
);
$xml1 = Xml::fromArray($xmlArray);
$xmlArray(
root => array(
tag => array(
xmlns:pref => http://cakephp.org,
pref:item => array(
item 1,
item 2
)
)
)
);
$xml2 = Xml::fromArray($xmlArray);
<?xml version="1.0"?>
<root><tag xmlns:pref="http://cakephp.org"><pref:item>item 1</pref:item><pref:item>item 2</
Creating a child The Xml class of CakePHP 2.0 doesnt provide the manipulation of content, this must be
made using SimpleXMLElement or DOMDocument. But, how CakePHP is so sweet, below has the steps
to do for create a child node:
// CakePHP 1.3
$myXmlOriginal = <?xml version="1.0"?><root><child>value</child></root>;
$xml = new Xml($myXmlOriginal, array(format => tags));
$xml->children[0]->createNode(young, new value);
// CakePHP 2.0 - Using SimpleXML
$myXmlOriginal = <?xml version="1.0"?><root><child>value</child></root>;
$xml = Xml::build($myXmlOriginal);
$xml->root->addChild(young, new value);
// CakePHP 2.0 - Using DOMDocument
$myXmlOriginal = <?xml version="1.0"?><root><child>value</child></root>;
$xml = Xml::build($myXmlOriginal, array(return => domdocument));
$child = $xml->createElement(young, new value);
$xml->firstChild->appendChild($child);
Tip:
After manipulate your XML using SimpleXMLElement or DomDocument you can use
Xml::toArray() without problem.
Utilities
785
Xml API
A factory and conversion class for creating SimpleXml or DOMDocument objects from a number of sources
including strings, arrays and remote URLs.
static Xml::build($input, $options = array())
Initialize SimpleXMLElement or DOMDocument from a given XML string, file path, URL or array
Building XML from a string:
$xml = Xml::build(<example>text</example>);
When building XML from an array ensure that there is only one top level element.
static Xml::toArray($obj)
Convert either a SimpleXml or DOMDocument object into an array.
786
CHAPTER 8
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 applications 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.
Manually
To install a plugin manually, you just have to drop the plugin folder into your app/Plugin/ folder. If youre
installing a plugin named ContactManager then you should have a folder in app/Plugin/ named ContactManager under which are the plugins View, Model, Controller, webroot and any other directories.
787
Composer
If you arent familiar with the dependency management tool named Composer, take the time to read the
Composer documentation1 .
To install the imaginary plugin ContactManager through Composer, add it as dependency to your projects
composer.json:
{
"require": {
"cakephp/contact-manager": "1.2.*"
}
}
If a CakePHP plugin is of the type cakephp-plugin, as it should, Composer will install it inside your
/Plugin directory, rather than in the usual vendors folder.
Note: Consider using require-dev if you only want to include the plugin for your development environment.
Alternatively, you can use the Composer CLI tool to require2 (and install) plugins:
php composer.phar require cakephp/contact-manager:1.2.*
Git Clone
If the plugin you want to install is hosted as a Git repo, you can also clone it. Lets assume the imaginary
plugin ContactManager is hosted on GitHub. You could clone it by executing the following command in
your app/Plugin/ folder:
git clone git://github.com/cakephp/contact-manager.git ContactManager
Git Submodule
If the plugin you want to install is hosted as a Git repo, but you prefer not to clone it, you can also integrate
it as a Git submodule. Execute the following commands in your app folder:
git submodule add git://github.com/cakephp/contact-manager.git Plugin/ContactManager
git submodule init
git submodule update
https://getcomposer.org/doc/00-intro.md
https://getcomposer.org/doc/03-cli.md#require
788
Chapter 8. Plugins
loadAll() loads all plugins available, while allowing you to set certain settings for specific plugins.
load() works similarly, but only loads the plugins you explicitly specify.
Plugin Configuration
There is a lot you can do with the load and loadAll methods to help with plugin configuration and routing.
Perhaps you want to load all plugins automatically, while specifying custom routes and bootstrap files for
certain plugins.
No problem:
CakePlugin::loadAll(array(
Blog => array(routes => true),
ContactManager => array(bootstrap => true),
WebmasterTools => array(bootstrap => true, routes => true),
));
With this style of configuration, you no longer need to manually include() or require() a plugins configuration or routes fileIt happens automatically at the right time and place. The exact same parameters could
have also been supplied to the load() method, which would have loaded only those three plugins, and not
the rest.
Finally, you can also specify a set of defaults for loadAll which will apply to every plugin that doesnt have
a more specific configuration.
Load the bootstrap file from all plugins, and additionally the routes from the Blog plugin:
CakePlugin::loadAll(array(
array(bootstrap => true),
Blog => array(routes => true)
));
Note that all files specified should actually exist in the configured plugin(s) or PHP will give warnings for
each file it cannot load. This is especially important to remember when specifying defaults for all plugins.
CakePHP 2.3.0 added an ignoreMissing option, that allows you to ignore any missing routes and
bootstrap files when loading plugins. You can shorten the code needed to load all plugins using this:
// Loads all plugins including any possible routes and bootstrap files
CakePlugin::loadAll(array(
array(routes => true, bootstrap => true, ignoreMissing => true)
));
789
Some plugins additionally need to create one or more tables in your database. In those cases, they will often
include a schema file which you can call from the cake shell like this:
user@host $ cake schema create --plugin ContactManager
Most plugins will indicate the proper procedure for configuring them and setting up the database in their
documentation. Some plugins will require more setup than others.
Advanced Bootstrapping
If you like to load more than one bootstrap file for a plugin. You can specify an array of files for the bootstrap
configuration key:
CakePlugin::loadAll(array(
Blog => array(
bootstrap => array(
config1,
config2
)
)
));
You can also specify a callable function that needs to be called when the plugin has been loaded:
function aCallableFunction($pluginName, $config) {
}
CakePlugin::loadAll(array(
Blog => array(
bootstrap => aCallableFunction
)
));
Using a Plugin
You can reference a plugins controllers, models, components, behaviors, and helpers by prefixing the name
of the plugin before the class name.
For example, say you wanted to use the ContactManager plugins ContactInfoHelper to output some pretty
contact information in one of your views. In your controller, your $helpers array could look like this:
public $helpers = array(ContactManager.ContactInfo);
You would then be able to access the ContactInfoHelper just like any other helper in your view, such as:
echo $this->ContactInfo->address($contact);
790
Chapter 8. Plugins
Note the name of the plugin folder, ContactManager. It is important that this folder has the same name
as the plugin.
Inside the plugin folder, youll notice it looks a lot like a CakePHP application, and thats basically what it
is. You dont actually have to include any of those folders if you do not use them. Some plugins might only
define a Component and a Behavior, and in that case they can completely omit the View directory.
A plugin can also have basically any of the other directories that your application can, such as Config,
Console, Lib, webroot, etc.
Note: If you want to be able to access your plugin with a URL, defining an AppController and AppModel
for the plugin is required. These two special classes are named after the plugin, and extend the parent
applications AppController and AppModel. Heres what they should look like for our ContactManager
example:
// /app/Plugin/ContactManager/Controller/ContactManagerAppController.php:
class ContactManagerAppController extends AppController {
}
// /app/Plugin/ContactManager/Model/ContactManagerAppModel.php:
class ContactManagerAppModel extends AppModel {
}
If you forgot to define these special classes, CakePHP will hand you Missing Controller errors until youve
done so.
Please note that the process of creating plugins can be greatly simplified by using the Cake shell.
In order to bake a plugin please use the following command:
user@host $ cake bake plugin ContactManager
Now you can bake using the same conventions which apply to the rest of your app. For example - baking
controllers:
791
Please refer to the chapter Code Generation with Bake if you have any problems with using the command
line.
Warning: Plugins do not work as namespacing to separate code. Due to PHP lacking namespaces
in older versions you cannot have the same class, or same filename, in your plugins. Even if it is two
different plugins. So use unique classes and filenames, possible prefixing the class and filename with the
plugin name.
Plugin Controllers
Controllers for our ContactManager plugin will be stored in /app/Plugin/ContactManager/Controller/. Since
the main thing well be doing is managing contacts, well need a ContactsController for this plugin.
So, we place our new ContactsController in /app/Plugin/ContactManager/Controller and it looks like so:
// app/Plugin/ContactManager/Controller/ContactsController.php
class ContactsController extends ContactManagerAppController {
public $uses = array(ContactManager.Contact);
public function index() {
//...
}
}
Note: This controller extends the plugins AppController (called ContactManagerAppController) rather
than the parent applications AppController.
Also note how the name of the model is prefixed with the name of the plugin. This is required to differentiate
between models in the plugin and models in the main application.
In this case, the $uses array would not be required as ContactManager.Contact would be the default model
for this controller, however it is included to demonstrate how to properly prepend the plugin name.
If you want to access what weve got going thus far, visit /contact_manager/contacts. You should get a
Missing Model error because we dont have a Contact model defined yet.
Plugin Models
Models for the plugin are stored in /app/Plugin/ContactManager/Model. Weve already defined a ContactsController for this plugin, so lets create the model for that controller, called Contact:
// /app/Plugin/ContactManager/Model/Contact.php:
class Contact extends ContactManagerAppModel {
}
Visiting /contact_manager/contacts now (given youve got a table in your database called contacts) should
give us a Missing View error. Lets create that next.
792
Chapter 8. Plugins
Note: 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:
// /app/Plugin/ContactManager/Model/Contact.php:
class Contact extends ContactManagerAppModel {
public $hasMany = array(ContactManager.AltName);
}
If you would prefer that the array keys for the association not have the plugin prefix on them, use the
alternative syntax:
// /app/Plugin/ContactManager/Model/Contact.php:
class Contact extends ContactManagerAppModel {
public $hasMany = array(
AltName => array(
className => ContactManager.AltName
)
);
}
Plugin Views
Views behave exactly as they do in normal applications. Just place them in the right folder inside of the
/app/Plugin/[PluginName]/View/ folder. For our ContactManager plugin, well need a view for our ContactsController::index() action, so lets include that as well:
// /app/Plugin/ContactManager/View/Contacts/index.ctp:
<h1>Contacts</h1>
<p>Following is a sortable list of your contacts</p>
<!-- A sortable list of contacts would go here....-->
Note: For information on how to use elements from a plugin, look up Elements
793
Plugin Assets
A plugins web assets (but not PHP files) can be served through the plugins webroot directory, just like
the main applications assets:
app/Plugin/ContactManager/webroot/
css/
js/
img/
flash/
pdf/
You may put any type of file in any directory, just like a regular webroot.
But keep in mind that handling static assets, such as images, Javascript and CSS files of plugins, through
the Dispatcher is incredibly inefficient. It is strongly recommended to symlink them for production. For
example like this:
ln -s app/Plugin/YourPlugin/webroot/css/yourplugin.css app/webroot/css/yourplugin.css
would
serve
the
asset
Note: It is important to note the /your_plugin/ prefix before the asset path. That makes the magic happen!
Changed in version 2.1: Use plugin syntax to request assets. For example in your View::
<?php echo $this->Html->css(ContactManager.style); ?>
794
Chapter 8. Plugins
Plugin Tips
Once a plugin has been installed in /app/Plugin/, you can access it at the URL /plugin_name/controller_name/action. In our ContactManager plugin example, wed access our ContactsController at /contact_manager/contacts.
Some final tips on working with plugins in your CakePHP applications:
When you dont have a [Plugin]AppController and [Plugin]AppModel, youll get missing Controller
errors when trying to access a plugin controller.
You can define your own layouts for plugins, inside app/Plugin/[Plugin]/View/Layouts. Otherwise,
plugins will use the layouts from the /app/View/Layouts folder by default.
http://plugins.cakephp.org
795
Also, you might want to create a composer.json file and publish your plugin at packagist.org4 . This way it
can easily be used through composer.
Choose a semantically meaningful name for the package name. This should ideally be prefixed with the
dependency, in this case cakephp as the framework. The vendor name will usually be your GitHub username. Do not use the CakePHP namespace (cakephp) as this is reserved to CakePHP owned plugins. The
convention is to use lowercase letters and dashes as separator.
So if you created a plugin Logging with your GitHub account FooBar, a good name would be foobar/cakephp-logging. And the CakePHP owned Localized plugin can be found under cakephp/localized
respectively.
https://packagist.org/
796
Chapter 8. Plugins
CHAPTER 9
CakePHP features not only a web framework but also a console framework for creating console applications. Console applications are ideal for handling a variety of background tasks such as maintenance, and
completing work outside of the request-response cycle. CakePHP console applications allow you to reuse
your application classes from the command line.
CakePHP comes with a number of console applications out of the box. Some of these applications are used
in concert with other CakePHP features (like ACL or i18n), and others are for general use in getting you
working faster.
797
Its often wise to add the core cake executable to your system path so you can use the cake command
anywhere. This comes in handy when you are creating new projects. See Adding cake to your system path
for how to make cake available systemwide.
Running the Console with no arguments produces this help message:
Welcome to CakePHP v2.0.0 Console
--------------------------------------------------------------App : app
Path: /path/to/cakephp/app/
--------------------------------------------------------------Current Paths:
-app: app
-working: /path/to/cakephp/app
-root: /path/to/cakephp/
-core: /path/to/cakephp/core
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/cakephp/app or -app /absolute/path/to/cakephp/app
Available Shells:
acl [CORE]
api [CORE]
bake [CORE]
command_list [CORE]
console [CORE]
i18n [CORE]
import [app]
schema [CORE]
testsuite [CORE]
upgrade [CORE]
The first information printed relates to paths. This is especially helpful if youre running the console from
different parts of the filesystem.
Since many users add the CakePHP console to their systems path so it can be accessed easily. Printing
out the working, root, app, and core paths allows you to see where the console will be making changes.
To change the app folder you wish to work with, you can supply its path as the first argument to the cake
command. This next example shows how to specify an app folder, assuming youve already added the
console folder to your PATH:
$ cake -app /path/to/cakephp/app
The path supplied can be relative to the current working directory or supplied as an absolute path.
are.
For
example
2. Edit your .bashrc or .bash_profile file in your home directory, and add the following:
export PATH="$PATH:/Users/mark/cakephp/lib/Cake/Console"
3. Reload the bash configuration or open a new terminal, and cake should work anywhere.
If you are on Windows Vista or 7, you should follow the steps below.
1. Locate where your CakePHP install and cake executable
C:\xampp\htdocs\cakephp\lib\Cake\Console
are.
For
example
2. Open System Properties window from My Computer. You want to try the shortcut Windows Key +
Pause or Windows Key + Break. Or, from the Desktop, right-click My Computer, click Properties
then click Advanced System Settings link in the left column
3. Go under Advanced tab and click on Environment Variables button
4. In the System Variables portion, reach Path variable and double-click on it to Edit
5. Add the cake install path string followed by a semi colon. Result example:
%SystemRoot%\system32;%SystemRoot%;C:\xampp\htdocs\cakephp\lib\Cake\Console;
Creating a shell
Lets create a shell for use in the Console. For this example, well create a simple Hello world shell. In your
applications Console/Command directory create HelloShell.php. Put the following code inside it:
class HelloShell extends AppShell {
public function main() {
$this->out(Hello world.);
}
}
The conventions for shell classes are that the class name should match the file name, with the suffix of
Shell. In our shell we created a main() method. This method is called when a shell is called with no
additional commands. Well add some more commands in a bit, but for now lets just run our shell. From
your application directory, run:
Console/cake hello
Creating a shell
799
As mentioned before, the main() method in shells is a special method called whenever there
are no other commands or arguments given to a shell. You may have also noticed that HelloShell is extending AppShell.
Much like The App Controller, AppShell gives you a base
class to contain all your common functions or logic. You can define an AppShell, by creating
app/Console/Command/AppShell.php. If you dont have one, CakePHP will use the built-in one.
Since our main method wasnt very interesting lets add another command that does something:
class HelloShell extends AppShell {
public function main() {
$this->out(Hello world.);
}
public function hey_there() {
$this->out(Hey there . $this->args[0]);
}
}
After saving this file you should be able to run Console/cake hello hey_there your-name
and see your name printed out. Any public method not prefixed by an _ is allowed to be called from the
command line. In our hey_there method we also used $this->args, this property contains an array
of all the positional arguments provided to a command. You can also use switches or options on shell
applications, these are available at $this->params, but well cover that in a bit.
When using a main() method you wont be able to use the positional arguments or parameters. This is
because the first positional argument or option is interpreted as the command name. If you want to use
arguments and options, you should use method names other than main.
The above shell, will fetch a user by username and display the information stored in the database.
Shell tasks
There will be times when building more advanced console applications, youll want to compose functionality
into re-usable classes that can be shared across many shells. Tasks allow you to extract commands into
800
classes. For example the bake is made almost entirely of tasks. You define a shells tasks by using the
$tasks property:
class UserShell extends AppShell {
public $tasks = array(Template);
}
You can use tasks from plugins using the standard plugin syntax.
Tasks are stored in
Console/Command/Task/ in files named after their classes. So if we were to create a new FileGenerator task, you would create Console/Command/Task/FileGeneratorTask.php.
Each task must at least implement an execute() method. The ShellDispatcher, will call this method
when the task is invoked. A task class looks like:
class FileGeneratorTask extends Shell {
public $uses = array(User);
public function execute() {
}
}
A shell can also access its tasks as properties, which makes tasks great for making re-usable chunks of
functionality similar to Components:
// found in Console/Command/SeaShell.php
class SeaShell extends AppShell {
public $tasks = array(Sound); // found in Console/Command/Task/SoundTask.php
public function main() {
$this->Sound->execute();
}
}
You can also access tasks directly from the command line:
$ cake sea sound
Note: In order to access tasks directly from the command line, the task must be included in the shell class
$tasks property. Therefore, be warned that a method called sound in the SeaShell class would override the
ability to access the functionality in the Sound task specified in the $tasks array.
Would load and return a ProjectTask instance. You can load tasks from plugins using:
$ProgressBar = $this->Tasks->load(ProgressBar.ProgressBar);
Shell tasks
801
The above shows how you can call the schema shell to create the schema for a plugin from inside your
plugins shell.
You can control the output level of shells, by using the --quiet and --verbose options. These options
are added by default, and allow you to consistently control output levels inside your CakePHP shells.
Styling output
Styling output is done by including tags - just like HTML - in your output. ConsoleOutput will replace these
tags with the correct ansi code sequence, or remove the tags if you are on a console that doesnt support ansi
802
codes. There are several built-in styles, and you can create more. The built-in ones are
error Error messages. Red underlined text.
warning Warning messages. Yellow text.
info Informational messages. Cyan text.
comment Additional text. Blue text.
question Text that is a question, added automatically by shell.
You can create additional styles using $this->stdout->styles(). To declare a new output style you could do:
$this->stdout->styles(flashy, array(text => magenta, blink => true));
This would then allow you to use a <flashy> tag in your shell output, and if ansi colours are enabled, the
following would be rendered as blinking magenta text $this->out(<flashy>Whoooa</flashy>
Something went wrong);. When defining styles you can use the following colours for the text and
background attributes:
black
red
green
yellow
blue
magenta
cyan
white
You can also use the following options as boolean switches, setting them to a truthy value enables them.
bold
underline
blink
reverse
Adding a style makes it available on all instances of ConsoleOutput as well, so you dont have to redeclare
styles for both stdout and stderr objects.
The above will put the output object into raw output mode. In raw output mode, no styling is done at all.
There are three modes you can use.
Styling output
803
ConsoleOutput::RAW - Raw output, no styling or formatting will be done. This is a good mode
to use if you are outputting XML or, want to debug why your styling isnt working.
ConsoleOutput::PLAIN - Plain text output, known style tags will be stripped from the output.
ConsoleOutput::COLOR - Output with color escape codes in place.
By default on *nix systems ConsoleOutput objects default to colour output. On windows systems, plain
output is the default unless the ANSICON environment variable is present.
804
ConsoleOptionParser::epilog($text = null)
Gets or sets the epilog for the option parser. The epilog is displayed after the argument and option information. By passing in either an array or a string, you can set the value of the epilog. Calling with no arguments
will return the current value:
// Set multiple lines at once
$parser->epilog(array(line one, line two));
// read the current value
$parser->epilog();
Adding arguments
ConsoleOptionParser::addArgument($name, $params = array())
Positional arguments are frequently used in command line tools, and ConsoleOptionParser allows
you to define positional arguments as well as make them required. You can add arguments one at a time
with $parser->addArgument(); or multiple at once with $parser->addArguments();:
$parser->addArgument(model, array(help => The model to bake));
805
As with all the builder methods on ConsoleOptionParser, addArguments can be used as part of a fluent
method chain.
Validating arguments
When creating positional arguments, you can use the required flag, to indicate that an argument must be
present when a shell is called. Additionally you can use choices to force an argument to be from a list of
valid choices:
$parser->addArgument(type, array(
help => The type of node to interact with.,
required => true,
choices => array(aro, aco)
));
The above will create an argument that is required and has validation on the input. If the argument is either
missing, or has an incorrect value an exception will be raised and the shell will be stopped.
Adding Options
ConsoleOptionParser::addOption($name, $options = array())
Options or flags are also frequently used in command line tools. ConsoleOptionParser supports
creating options with both verbose and short aliases, supplying defaults and creating boolean switches.
Options are created with either $parser->addOption() or $parser->addOptions().
$parser->addOption(connection, array(
short => c,
help => connection,
806
The above would allow you to use either cake myshell --connection=other, cake myshell
--connection other, or cake myshell -c other when invoking the shell. You can also create
boolean switches, these switches do not consume values, and their presence just enables them in the parsed
parameters.
$parser->addOption(no-commit, array(boolean => true));
With this option, when calling a shell like cake myshell --no-commit something the no-commit
param would have a value of true, and something would be a treated as a positional argument. The built-in
--help, --verbose, and --quiet options use this feature.
When creating options you can use the following options to define the behavior of the option:
short - The single letter variant for this option, leave undefined for none.
help - Help text for this option. Used when generating help for the option.
default - The default value for this option. If not defined the default will be true.
boolean - The option uses no value, its just a boolean switch. Defaults to false.
choices An array of valid choices for this option. If left empty all values are valid. An exception
will be raised when parse() encounters an invalid value.
ConsoleOptionParser::addOptions(array $options)
If you have an array with multiple options you can use $parser->addOptions() to add multiple
options at once.
$parser->addOptions(array(
node => array(short => n, help => The node to create),
parent => array(short => p, help => The parent node)
));
As with all the builder methods on ConsoleOptionParser, addOptions is can be used as part of a fluent
method chain.
Validating options
Options can be provided with a set of choices much like positional arguments can be. When an option has defined choices, those are the only valid choices for an option. All other values will raise an
InvalidArgumentException:
$parser->addOption(accept, array(
help => What version to accept.,
choices => array(working, theirs, mine)
));
807
The following option would result in $this->params[verbose] always being available. This lets
you omit empty() or isset() checks for boolean flags:
if ($this->params[verbose]) {
// do something
}
Since the boolean options are always defined as true or false you can omit additional check methods.
Adding subcommands
ConsoleOptionParser::addSubcommand($name, $options = array())
Console applications are often made of subcommands, and these subcommands may require special option
parsing and have their own help. A perfect example of this is bake. Bake is made of many separate tasks
that all have their own help and options. ConsoleOptionParser allows you to define subcommands
and provide command specific option parsers so the shell knows how to parse commands for its tasks:
$parser->addSubcommand(model, array(
help => Bake a model,
parser => $this->Model->getOptionParser()
));
The above is an example of how you could provide help and a specialized option parser for a shells task.
By calling the Tasks getOptionParser() we dont have to duplicate the option parser generation, or
mix concerns in our shell. Adding subcommands in this way has two advantages. First it lets your shell
easily document its subcommands in the generated help, and it also allows easy access to the subcommand
help. With the above subcommand created you could call cake myshell --help and see the list of
subcommands, and also run cake myshell model --help to view the help for just the model task.
When defining a subcommand you can use the following options:
help - Help text for the subcommand.
parser - A ConsoleOptionParser for the subcommand. This allows you to create method
specific option parsers. When help is generated for a subcommand, if a parser is present
it will be used.
You can also supply the parser as an array that is compatible with
ConsoleOptionParser::buildFromArray()
Adding subcommands can be done as part of a fluent method chain.
808
Inside the parser spec, you can define keys for arguments, options, description and
epilog. You cannot define subcommands inside an array style builder. The values for arguments, and options, should follow the format that ConsoleOptionParser::addArguments() and
ConsoleOptionParser::addOptions() use. You can also use buildFromArray on its own, to build
an option parser:
public function getOptionParser() {
return ConsoleOptionParser::buildFromArray(array(
description => array(
__("Use this command to grant ACL permissions. Once executed, the "),
__("ARO specified (and its children, if any) will have ALLOW access "),
__("to the specified ACO action (and the ACOs children, if any).")
),
arguments => array(
aro => array(help => __(ARO to check.), required => true),
aco => array(help => __(ACO to check.), required => true),
action => array(help => __(Action to check))
)
));
}
809
Would both generate the help for bake. If the shell supports subcommands you can get help for those in a
similar fashion:
cake bake model --help
cake bake model -h
This would get you the help specific to bakes model task.
The above would return an XML document with the generated help, options, arguments and subcommands
for the selected shell. A sample XML document would look like:
<?xml version="1.0"?>
<shell>
<command>bake fixture</command>
<description>Generate fixtures for use with the test suite. You can use
bake fixture all to bake all fixtures.</description>
<epilog>
Omitting all arguments and options will enter into an interactive
mode.
</epilog>
<subcommands/>
<options>
<option name="--help" short="-h" boolean="1">
<default/>
<choices/>
</option>
<option name="--verbose" short="-v" boolean="1">
<default/>
<choices/>
</option>
<option name="--quiet" short="-q" boolean="1">
<default/>
<choices/>
</option>
<option name="--count" short="-n" boolean="">
<default>10</default>
<choices/>
</option>
<option name="--connection" short="-c" boolean="">
<default>default</default>
<choices/>
</option>
<option name="--plugin" short="-p" boolean="">
<default/>
810
<choices/>
</option>
<option name="--records" short="-r" boolean="1">
<default/>
<choices/>
</option>
</options>
<arguments>
<argument name="name" help="Name of the fixture to bake.
Can use Plugin.name to bake plugin fixtures." required="">
<choices/>
</argument>
</arguments>
</shell>
Shell API
class AppShell
AppShell can be used as a base class for all your shells. It should extend Shell, and be located in
Console/Command/AppShell.php
class Shell($stdout = null, $stderr = null, $stdin = null)
Shell is the base class for all shells, and provides a number of functions for interacting with user input,
outputting text a generating errors.
property Shell::$tasks
An array of tasks you want loaded for this shell/task.
property Shell::$uses
An array of models that should be loaded for this shell/task.
Shell::clear()
Clears the current output being displayed.
811
Shell::createFile($path, $contents)
Parameters
$path (string) Absolute path to the file you want to create.
$contents (string) Contents to put in the file.
Creates a file at a given path. If the Shell is interactive, a warning will be generated, and the user
asked if they want to overwrite the file if it already exists. If the shells interactive property is false,
no question will be asked and the file will simply be overwritten.
Shell::dispatchShell()
Dispatch a command to another Shell. Similar to Controller::requestAction() but intended for running shells from other shells.
See Invoking other shells from your shell.
Shell::err($message = null, $newlines = 1)
Parameters
$method (string) The message to print.
$newlines (integer) The number of newlines to follow the message.
Outputs a method to stderr, works similar to Shell::out()
Shell::error($title, $message = null)
Parameters
$title (string) Title of the error
$message (string) An optional error message
Displays a formatted error message and exits the application with status code 1
Shell::getOptionParser()
Should return a ConsoleOptionParser object, with any sub-parsers for the shell.
Shell::hasMethod($name)
Check to see if this shell has a callable method by the given name.
Shell::hasTask($task)
Check to see if this shell has a task with the provided name.
Shell::hr($newlines = 0, $width = 63)
Parameters
$newlines (int) The number of newlines to precede and follow the line.
$width (int) The width of the line to draw.
Create a horizontal line preceded and followed by a number of newlines.
Shell::in($prompt, $options = null, $default = null)
Parameters
812
Shell API
813
By default on *nix systems ConsoleOutput objects default to colour output. On windows systems,
plain output is the default unless the ANSICON environment variable is present.
Shell::overwrite($message = null, $newlines = 1, $size = null)
Parameters
$method (string) The message to print.
$newlines (integer) The number of newlines to follow the message.
$size (integer) The number of bytes to overwrite
A useful method to generate progress bars or to avoid outputting too many lines.
Warning: You cannot overwrite text that contains newlines.
New in version 2.6.
Shell::runCommand($command, $argv)
Runs the Shell with the provided argv.
Delegates calls to Tasks and resolves methods inside the class. Commands are looked up with the
following order:
Method on the shell.
Matching task name.
main() method.
If a shell implements a main() method, all missing method calls will be sent to main() with the original
method name in the argv.
Shell::shortPath($file)
Makes absolute file path easier to read.
Shell::startup()
Starts up the Shell and displays the welcome message. Allows for checking and configuring prior to
command or main execution.
Override this method if you want to remove the welcome information, or otherwise modify the precommand flow.
Shell::wrapText($text, $options = array())
Wrap a block of text. Allows you to set the width, and indenting on a block of text.
Parameters
$text (string) The text to format
$options (array)
width The width to wrap to. Defaults to 72
wordWrap Only wrap on words breaks (spaces) Defaults to true.
indent Indent the text with the string provided. Defaults to null.
814
More topics
Running Shells as cronjobs
A common thing to do with a shell is making it run as a cronjob to clean up the database once in a while or
send newsletters. This is trivial to setup, for example:
#
#
#
#
#
#
#
#
*/5 *
*
*
* cd /full/path/to/app && Console/cake myshell myparam
*
*
*
*
* command to execute
|
|
|
|
|
|
|
|
|
|
|
|
|
|
\----- day of week (0 - 6) (0 to 6 are Sunday to Saturday,
|
|
|
|
or use names)
|
|
|
\---------- month (1 - 12)
|
|
\--------------- day of month (1 - 31)
|
\-------------------- hour (0 - 23)
\------------------------- min (0 - 59)
Completion Shell
New in version 2.5.
Working with the console gives the developer a lot of possibilities but having to completely know and write
those commands can be tedious. Especially when developing new shells where the commands differ per
minute iteration. The Completion Shells aids in this matter by providing an API to write completion scripts
for shells like bash, zsh, fish etc.
Sub Commands
The Completion Shell consists of a number of sub commands to assist the developer creating its completion
script. Each for a different step in the autocompletion process.
commands
For the first step commands outputs the available Shell Commands, including plugin name when applicable.
(All returned possibilities, for this and the other sub commands, are separated by a space.) For example:
./Console/cake Completion commands
Returns:
acl api bake command_list completion console i18n schema server test testsuite upgrade
Your completion script can select the relevant commands from that list to continue with. (For this and the
following sub commands.)
More topics
815
subCommands
Once the preferred command has been chosen subCommands comes in as the second step and outputs the
possible sub command for the given shell command. For example:
./Console/cake Completion subcommands bake
Returns:
controller db_config fixture model plugin project test view
options
As the third and final options outputs options for the given (sub) command as set in getOptionParser. (Including the default options inherited from Shell.) For example:
./Console/cake Completion options bake
Returns:
--help -h --verbose -v --quiet -q --connection -c --theme -t
Bash Example
The following bash example comes from the original author:
# bash completion for CakePHP console
_cake()
{
local cur prev opts cake
COMPREPLY=()
cake="${COMP_WORDS[0]}"
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [[ "$cur" == -* ]] ; then
if [[ $ {COMP_CWORD} = 1 ]] ; then
opts= $ ( $ {cake} Completion options)
elif [[ $ {COMP_CWORD} = 2 ]] ; then
opts= $ ( $ {cake} Completion options "${COMP_WORDS[1]}")
else
opts= $ ( $ {cake} Completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}")
fi
COMPREPLY=( $ (compgen -W "${opts}" -- $ {cur}) )
return 0
fi
if [[ $ {COMP_CWORD} = 1 ]] ; then
816
More topics
817
Alternatively, you can run any of these commands directly from the command line:
$ cake bake db_config
$ cake bake model
$ cake bake view
$ cake bake controller
$ cake bake project
$ cake bake fixture
$ cake bake test
$ cake bake plugin plugin_name
$ cake bake all
Changed in version 2.5: Test files produced by bake test include calls to PHPunits markTestIncomplete()1 to draw attention to empty test methods. Before 2.5, empty tests pass silently.
Modify default HTML produced by baked templates
If you wish to modify the default HTML output produced by the bake command, follow these simple
steps:
For baking custom views
1. Go into: lib/Cake/Console/Templates/default/views
2. Notice the 4 files there
3. Copy them to your: app/Console/Templates/[themename]/views
4. Make changes to the HTML output to control the way bake builds your views
The [themename] path segment should be the name of the bake theme that you are creating. Bake theme
names need to be unique, so dont use default.
1
http://phpunit.de/manual/3.7/en/incomplete-and-skipped-tests.html
818
1. Go into: lib/Cake/Console/Templates/skel
2. Notice the base application files there
3. Copy them to your: app/Console/Templates/skel
4. Make changes to the HTML output to control the way bake builds your views
5. Pass the skeleton path parameter to the project task
cake bake project --skel Console/Templates/skel
Note:
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 CakePHP starts supporting
overriding the skel folder like it does for views)
This will drop and create the tables based on the contents of the schema.php.
Schema files can also be used to generate sql dump files. To generate a sql file containing the CREATE
TABLE statements, run:
More topics
819
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.
CakeSchema callbacks
After generating a schema you might want to insert data on some tables to get your app started. This can
be achieved through CakeSchema callbacks. Every schema file is generated with a before($event =
array()) and a after($event = array()) method.
The $event param holds an array with two keys. One to tell if a table is being dropped or created and
another for errors. Examples:
array(drop => posts, errors => null)
array(create => posts, errors => null)
The before() and after() callbacks run each time a table is created or dropped on the current schema.
When inserting data to more than one table youll need to flush the database cache after each table is created.
Cache can be disable by setting $db->cacheSources = false in the before action().
public $connection = default;
public function before($event = array()) {
$db = ConnectionManager::getDataSource($this->connection);
$db->cacheSources = false;
return true;
}
If you use models in your callbacks make sure to initialize them with the correct datasource, lest they
fallback to their default datasources:
820
More topics
821
Columns
Each column is encoded as a key value associative array. The field name is the key of the field, the value is
another array with some of the following attributes.
Example column
id => array(
type => integer,
null => false,
default => null,
length => 10,
key => primary
),
822
float
string
text
binary
Table key indexes
The key name indexes is put in the table array instead of a field name.
column This is either a single column name or an array of columns.
e.g. Single
indexes => array(
PRIMARY => array(
column => id,
unique => 1
)
)
e.g. Multiple
indexes => array(
AB_KEY => array(
column => array(
a_id,
b_id
),
unique => 1
)
)
More topics
823
See below for examples on how to use table and field parameters in your schema files.
Using tableParameters in schema files
You use tableParameters just as you would any other key in a schema file. Much like indexes:
var $comments => array(
id => array(
type => integer,
null => false,
default => 0,
key => primary
),
post_id => array(type => integer, null => false, default => 0),
comment => array(type => text),
indexes => array(
PRIMARY => array(column => id, unique => true),
post_id => array(column => post_id),
),
tableParameters => array(
engine => InnoDB,
charset => latin1,
collate => latin1_general_ci
)
);
is an example of a table using tableParameters to set some database specific settings. If you use
a schema file that contains options and features your database does not implement, those options will be
ignored.
Migrations with CakePHP schema shell
Migrations allow for versioning of your database schema, so that as you develop features you have an easy
and database agnostic way to distribute database changes. Migrations are achieved through either SCM
controlled schema files or schema snapshots. Versioning a schema file with the schema shell is quite easy.
If you already have a schema file created running:
$ Console/cake schema generate
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:
824
Where 2 is the snapshot number you wish to run. The schema shell will prompt you to confirm you wish
to perform the ALTER statements that represent the difference between the existing database the currently
executing schema file.
You can perform a dry run by adding a --dry to your command.
Workflow examples
Create schema and commit
On a project which use versioning, the usage of cake schema would follow these steps:
1. Create or modify your database tables
2. Execute cake schema to export a full description of your database
3. Commit the created or updated schema.php file:
$ # once your database has been updated
$ Console/cake schema generate
$ git commit -a
Note: If the project is not versioned, managing schemas would be done through snapshots. (see previous
section to manage snapshots)
When you pull the last changes of your repository, and discover changes in the structure of the database
(possibly because of an error message saying you are missing a table):
1. Execute cake schema to update your database:
$ git pull
$ Console/cake schema create
$ Console/cake schema update
If at some point you need to revert and get back to the state in which you were before updating your database,
you should be informed that this is currently not supported by cake schema.
More specifically, you cant automatically drop your tables once they have been created.
More topics
825
Using update will, on the contrary, drop any field which differ from the schema file:
$ git revert HEAD
$ Console/cake schema update
I18N shell
The i18n features of CakePHP use po files2 as their translation source. This makes them easily to integrate
with tools like poedit3 and other common translation tools.
The i18n shell provides a quick and easy way to generate po template files. These templates files can then
be given to translators so they can translate the strings in your application. Once you have translations done,
pot files can be merged with existing translations to help update your translations.
Generating POT files
POT files can be generated for an existing application using the extract command. This command will
scan your entire application for __() style function calls, and extract the message string. Each unique string
in your application will be combined into a single POT file:
./Console/cake i18n extract
The above will run the extraction shell. In addition to extracting strings in __() methods, validation messages in models will be extracted as well. The result of this command will be the file
app/Locale/default.pot. You use the pot file as a template for creating po files. If you are manually
creating po files from the pot file, be sure to correctly set the Plural-Forms header line.
Generating POT files for plugins
This will generate the required POT files used in the plugins.
2
3
http://en.wikipedia.org/wiki/GNU_gettext
http://www.poedit.net/
826
You can set the domain to be used for extracted validation messages in your models. If the model already
has a $validationDomain property, the given validation domain will be ignored:
./Console/cake i18n extract --validation-domain validation_errors
You can also prevent the shell from extracting validation messages:
./Console/cake i18n extract --ignore-model-validation
Excluding folders
You can pass a comma separated list of folders that you wish to be excluded. Any path containing a path
segment with the provided values will be ignored:
./Console/cake i18n extract --exclude Test,Vendor
More topics
827
ACL Shell
The AclShell is useful for managing and inspecting your Acl databases records. Its often more convenient
than adding one time modifications to your controllers.
Most acl shell subcommands involve referencing aco/aro nodes. As there are two forms of these nodes,
there is two notations in the shell:
# A Model + foreign_key reference
./Console/cake acl view aro Model.1
# An alias path reference
./Console/cake acl view aco root/controllers
Using a . indicates that you are going to use a bound record style reference while using a / indicates an
alias path.
Installing the database tables
Before using the database ACL youll need to setup the tables. You can do that using:
./Console/cake acl initdb
Would create an aco record using an alias path. You could do the following as well:
./Console/cake acl create aro Group.1
The above would grant all privileges. You could grant only the read privilege using the following:
./Console/cake acl grant Group.1 controllers/Posts read
Denying permission works in the exact same way. The only difference is you switch deny in for grant.
828
Check permissions
Use this command to check ACL permissions.
./Console/cake acl check Group.1 controllers/Posts read
Test shell
Once youve started writing Tests you can run them using the test shell.
For more information on basic usage of the test shell see Running tests from command line.
Changed in version 2.1: The test shell was added in 2.1. The 2.0 testsuite shell is still available but
the new syntax is preferred.
Upgrade shell
The upgrade shell will do most of the work to upgrade your CakePHP application from 1.3 to 2.0.
To run all upgrade steps:
./Console/cake upgrade all
If you would like to see what the shell will do without modifying files perform a dry run first with dry-run:
./Console/cake upgrade all --dry-run
You are able to run each upgrade step individually. To see all the steps available run the command:
./Console/cake upgrade --help
http://api.cakephp.org/2.4/class-UpgradeShell.html
More topics
829
The first step is to download or git clone the new version of CakePHP into another folder outside of your
mywebsite folder, well call it cakephp. We dont want the downloaded app folder to overwrite your
app folder. Now is a good time to make a backup of your app folder, eg.: cp -R app app-backup.
Copy the cakephp/lib folder to your mywebsite/lib to setup the new CakePHP version in your
app, eg.: cp -R ../cakephp/lib .. Symlinking is a good alternative to copy as well, eg.: ln -s
/var/www/cakephp/lib.
Before we can run the upgrade shell we need the new console scripts
Copy the cakephp/app/Console folder into your mywebsite/app, eg.:
../cakephp/app/Console ./app.
as
well.
cp -R
<<<<<-
Your App
Copied app/Console Folder
Backup Copy of Your App
1.3 Version of CakePHP
2.x Version of CakePHP
Now we can run the upgrade shell by cding into your app folder and running the command:
./Console/cake upgrade all
This will do most of the work to upgrade your app to 2.x. Check things over in your upgraded app folder.
If everything looks good then congratulate yourself and delete your mywebsite/cake folder. Welcome
to 2.x!
830
CHAPTER 10
Development
In this section well cover the various aspects of developing a CakePHP application. Topics like Configuration, handling errors & exceptions, debugging, and testing will be covered.
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.
Database Configuration
CakePHP expects database configuration details to be in a file at app/Config/database.php. An example database configuration file can be found at app/Config/database.php.default. A finished
configuration should look something like this:
class DATABASE_CONFIG
public $default =
datasource
persistent
host
login
password
database
prefix
);
}
{
array(
=> Database/Mysql,
=> false,
=> localhost,
=> cakephpuser,
=> c4k3roxx!,
=> my_cakephp_project,
=>
The $default connection array is used unless another connection is specified by the $useDbConfig property in a model. For example, if my application has an additional legacy database in addition to the default
831
one, I could use it in my models by creating a new $legacy database connection array similar to the $default
array, and by setting public $useDbConfig = legacy; in the appropriate models.
Fill out the key/value pairs in the configuration array to best suit your needs.
datasource The name of the datasource this configuration array is for. Examples: Database/Mysql,
Database/Sqlserver, Database/Postgres, Database/Sqlite. You can use plugin syntax to indicate plugin
datasource to use.
persistent Whether or not to use a persistent connection to the database.
host The database servers hostname (or IP address).
login The username for the account.
password The password for the account.
database The name of the database for this connection to use.
prefix (optional) The string that prefixes every table name in the database. If your tables dont have prefixes,
set this to an empty string.
port (optional) The TCP port or Unix socket used to connect to the server.
encoding Indicates the character set to use when sending SQL statements to the server. This defaults to the
databases 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.
unix_socket Used by drivers that support it to connect via unix socket files. If you are using postgres and
want to use unix sockets, leave the host key blank.
ssl_key The file path to the SSL key file. (Only supported by MySQL, requires PHP 5.3.7+).
ssl_cert The file path to the SSL certificate file. (Only supported by MySQL, requires PHP 5.3.7+).
ssl_ca The file path to the SSL certificate authority. (Only supported by MySQL, requires PHP 5.3.7+).
settings An array of key/value pairs that should be sent to the database server as SET commands when the
connection is created. This option is only supported by MySQL, Postgres, and SQLserver at this time.
Changed in version 2.4: The settings, ssl_key, ssl_cert and ssl_ca keys was added in 2.4.
Note: The prefix setting is for tables, not models. For example, if you create a join table for your Apple
and Flavor models, you name it prefix_apples_flavors (not prefix_apples_prefix_flavors), and set your prefix
setting to prefix_.
At this point, you might want to take a look at the CakePHP Conventions. The correct naming for your
tables (and the addition of some columns) can score you some free functionality and help you avoid configuration. For example, if you name your database table big_boxes, your model BigBox, your controller
BigBoxesController, everything just works together automatically. By convention, use underscores, lower
case, and plural forms for your database table names - for example: bakers, pastry_stores, and savory_cakes.
832
Configuration
833
),
Console => array(
/path/to/consoles,
/next/path/to/consoles
),
Console/Command => array(
/path/to/commands,
/next/path/to/commands
),
Console/Command/Task => array(
/path/to/tasks,
/next/path/to/tasks
),
Lib => array(
/path/to/libs,
/next/path/to/libs
),
Locale => array(
/path/to/locales,
/next/path/to/locales
),
Vendor => array(
/path/to/vendors,
/next/path/to/vendors
),
Plugin => array(
/path/to/plugins,
/next/path/to/plugins
),
));
Note: All additional path configuration should be done at the top of your applications bootstrap.php. This
will ensure that the paths are available for the rest of your application.
Core Configuration
Each application in CakePHP contains a configuration file, app/Config/core.php, to determine
CakePHPs internal behavior. This file is a collection of Configure class variable definitions and constant
definitions that determine how your application behaves. Before we dive into those particular variables,
youll need to be familiar with Configure, CakePHPs configuration registry class.
CakePHP Core Configuration
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.
debug Changes CakePHP debugging output.
834
835
Session Contains an array of settings to use for session configuration. The defaults key is used to define a
default preset to use for sessions, any settings declared here will override the settings of the default
config.
Sub-keys
name - The name of the cookie to use. Defaults to CAKEPHP
timeout - The number of minutes you want sessions to live for. This timeout is handled by
CakePHP
cookieTimeout - The number of minutes you want session cookies to live for.
checkAgent - Do you want the user agent to be checked when starting sessions? You might
want to set the value to false, when dealing with older versions of IE, Chrome Frame or certain
web-browsing devices and AJAX
defaults - The default configuration set to use as a basis for your session. There are four
builtins: php, cake, cache, database.
handler - Can be used to enable a custom session handler. Expects an array of callables,
that can be used with session_save_handler. Using this option will automatically add session.save_handler to the ini array.
autoRegenerate - Enabling this setting, turns on automatic renewal of sessions, and sessionids that change frequently. See CakeSession::$requestCountdown.
ini - An associative array of additional ini values to set.
The built-in defaults are:
php - Uses settings defined in your php.ini.
cake - Saves session files in CakePHPs /tmp directory.
database - Uses CakePHPs database sessions.
cache - Use the Cache class to save sessions.
To define a custom session handler, save it at app/Model/Datasource/Session/<name>.php.
Make sure the class implements CakeSessionHandlerInterface and set Session.handler to
<name>
To use database sessions, run the app/Config/Schema/sessions.php schema using the cake
shell command: cake schema create Sessions
Security.salt A random string used in security hashing.
Security.cipherSeed A random numeric string (digits only) used to encrypt/decrypt strings.
Asset.timestamp Appends a timestamp which is last modified time of the particular file at the end of asset
files URLs (CSS, JavaScript, Image) when using proper helpers. Valid values: (boolean) false Doesnt do anything (default) (boolean) true - Appends the timestamp when debug > 0 (string) force
- Appends the timestamp when debug >= 0
Acl.classname, Acl.database Constants used for CakePHPs Access Control List functionality. See the
Access Control Lists chapter for more information.
836
Note: Cache configuration is also found in core.php Well 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.
Configuration Constants
While most configuration options are handled by Configure, there are a few constants that CakePHP uses
during runtime.
constant LOG_ERROR
Error constant. Used for differentiating error logging and debugging. Currently PHP supports
LOG_DEBUG.
Core Cache Configuration
CakePHP uses two cache configurations internally.
_cake_model_ and _cake_core_.
_cake_core_ is used to store file paths, and object locations. _cake_model_ is used to store schema
descriptions, and source listings for datasources. Using a fast cache storage like APC or Memcached is
recommended for these configurations, as they are read on every request. By default both of these configurations expire every 10 seconds when debug is greater than 0.
As with all cached data stored in Cache you can clear data using Cache::clear().
Configure Class
class Configure
Despite few things needing to be configured in CakePHP, its sometimes useful to have your own configuration rules for your application. In the past you may have defined custom configuration values by defining
variable or constants in some files. Doing so forces you to include that configuration file every time you
needed to use those values.
CakePHPs Configure class can be used to store and retrieve application or runtime specific values. Be
careful, this class allows you to store anything in it, then use it in any other part of your code: a sure
temptation to break the MVC pattern CakePHP was designed for. The main goal of Configure class is to
keep centralized variables that can be shared between many objects. Remember to try to live by convention
over configuration and you wont end up breaking the MVC structure weve set in place.
This class can be called from anywhere within your application, in a static context:
Configure::read(debug);
837
Note: The dot notation used in the $key parameter can be used to organize your configuration
settings into logical groups.
The above example could also be written in a single call:
Configure::write(
Company,
array(
name => Pizza, Inc.,
slogan => Pizza for your body and soul
)
);
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.
static Configure::read($key = null)
Parameters
$key (string) The key to read, can be a dot notation value
Used to read configuration data from the application. Defaults to CakePHPs important debug value.
If a key is supplied, the data is returned. Using our examples from write() above, we can read that
data back:
Configure::read(Company.name);
Configure::read(Company.slogan);
Configure::read(Company);
//yields:
array(name => Pizza, Inc., slogan => Pizza for your body and soul);
Parameters
$key (string) The key to delete, can be a dot notation value
Used to delete information from the applications configuration:
Configure::delete(Company.name);
static Configure::version
Returns the CakePHP version for the current application.
static Configure::config($name, $reader)
Parameters
$name (string) The name of the reader being attached.
$reader (ConfigReaderInterface) The reader instance being attached.
Attach a configuration reader to Configure. Attached readers can then be used to load configuration
files. See Loading configuration files for more information on how to read configuration files.
static Configure::configured($name = null)
Parameters
$name (string) The name of the reader to check, if null a list of all attached
readers will be returned.
Either check that a reader with a given name is attached, or get the list of attached readers.
static Configure::drop($name)
Drops a connected reader object.
You can have multiple readers attached to Configure, each reading different kinds of configuration files, or
reading from different types of sources. You can interact with attached readers using a few other methods
on Configure. To see which reader aliases are attached you can use Configure::configured():
1
http://php.net/parse_ini_file
Configuration
839
You can also remove attached readers. Configure::drop(default) would remove the default
reader alias. Any future attempts to load configuration files with that reader would fail.
Loading configuration files
static Configure::load($key, $config = default, $merge = true)
Parameters
$key (string) The identifier of the configuration file to load.
$config (string) The alias of the configured reader.
$merge (boolean) Whether or not the contents of the read file should be merged,
or overwrite the existing values.
Once youve attached a config reader to Configure you can load configuration files:
// Load my_file.php using the default reader object.
Configure::load(my_file, default);
Loaded configuration files merge their data with the existing runtime configuration in Configure. This allows
you to overwrite and add new values into the existing runtime configuration. By setting $merge to true,
values will not ever overwrite the existing configuration.
Creating or modifying configuration files
static Configure::dump($key, $config = default, $keys = array())
Parameters
$key (string) The name of the file/stored configuration to be created.
$config (string) The name of the reader to store the data with.
$keys (array) The list of top-level keys to save. Defaults to all keys.
Dumps all or some of the data in Configure into a file or storage system supported by a config reader. The
serialization format is decided by the config reader attached as $config. For example, if the default adapter
is a PhpReader, the generated file will be a PHP configuration file loadable by the PhpReader
Given that the default reader is an instance of PhpReader.
my_config.php:
Configure::dump(my_config.php, default);
840
Configure::dump() can be used to either modify or overwrite configuration files that are readable with
Configure::load()
New in version 2.2: Configure::dump() was added in 2.2.
Storing runtime configuration
static Configure::store($name, $cacheConfig = default, $data = null)
Parameters
$name (string) The storage key for the cache file.
$cacheConfig (string) The name of the cache configuration to store the configuration data with.
$data (mixed) Either the data to store, or leave null to store all data in Configure.
You can also store runtime configuration values for use in a future request. Since configure only remembers
values for the current request, you will need to store any modified configuration information if you want to
use it in subsequent requests:
// Store the current configuration in the user_1234 key in the default cache.
Configure::store(user_1234, default);
Stored configuration data is persisted in the Cache class. This allows you to store Configuration information
in any storage engine that Cache can talk to.
Restoring runtime configuration
static Configure::restore($name, $cacheConfig = default)
Parameters
$name (string) The storage key to load.
$cacheConfig (string) The cache configuration to load the data from.
Once youve stored runtime configuration, youll probably need to restore it so you can access it again.
Configure::restore() does exactly that:
// restore runtime configuration from the cache.
Configure::restore(user_1234, default);
When restoring configuration information its important to restore it with the same key, and cache configuration as was used to store it. Restored information is merged on top of the existing runtime configuration.
Configuration
841
In your app/Config/bootstrap.php you could attach this reader and use it:
App::uses(MyXmlReader, Configure);
Configure::config(xml, new MyXmlReader());
...
Configure::load(my_xml);
Warning: It is not a good idea to call your custom configure class XmlReader because that class name
is an internal PHP one already: XMLReadera
a
http://php.net/manual/en/book.xmlreader.php
The read() method of a config reader, must return an array of the configuration information that the
resource named $key contains.
interface ConfigReaderInterface
Defines the interface used by classes that read configuration data and store it in Configure
ConfigReaderInterface::read($key)
Parameters
$key (string) The key name or identifier to load.
842
This method should load/parse the configuration data identified by $key and return an array of data
in the file.
ConfigReaderInterface::dump($key, $data)
Parameters
$key (string) The identifier to write to.
$data (array) The data to dump.
This method should dump/store the provided configuration data to a key identified by $key.
New in version 2.3: ConfigReaderInterface::dump() was added in 2.3.
exception ConfigureException
Thrown
when
errors
occur
when
loading/storing/restoring
configuration
data.
ConfigReaderInterface implementations should throw this exception when they encounter
an error.
Built-in Configuration readers
class PhpReader
Allows you to read configuration files that are stored as plain PHP files. You can read either files from
your app/Config or from plugin configs directories by using plugin syntax. Files must contain a
$config variable. An example configuration file would look like:
$config = array(
debug => 0,
Security => array(
salt => its-secret
),
Exception => array(
handler => ErrorHandler::handleException,
renderer => ExceptionRenderer,
log => true
)
);
class IniReader
Allows you to read configuration files that are stored as plain .ini files. The ini files must be compatible
with PHPs parse_ini_file function, and benefit from the following improvements
dot separated values are expanded into arrays.
boolean-ish values like on and off are converted to booleans.
An example ini file would look like:
Configuration
843
debug = 0
Security.salt = its-secret
[Exception]
handler = ErrorHandler::handleException
renderer = ExceptionRenderer
log = true
The above ini file, would result in the same end configuration data as the PHP example above. Array
structures can be created either through dot separated values, or sections. Sections can contain dot
separated keys for deeper nesting.
Inflection Configuration
CakePHPs 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 CakePHPs Inflector (the class that pluralizes, singularizes, camelCases, and under_scores)
might not work as youd like. If CakePHP wont recognize your Foci or Fish, you can tell CakePHP about
your special cases.
Loading custom inflections
You can use Inflector::rules() in the file app/Config/bootstrap.php to load custom inflections:
Inflector::rules(singular, array(
rules => array(
/^(bil)er$/i => \1,
/^(inflec|contribu)tors$/i => \1ta
),
uninflected => array(singulars),
irregular => array(spins => spinor)
));
or:
Inflector::rules(plural, array(irregular => array(phylum => phyla)));
Will merge the supplied rules into the inflection sets defined in lib/Cake/Utility/Inflector.php, with the added
rules taking precedence over the core rules.
844
Bootstrapping CakePHP
If you have any additional configuration needs, use CakePHPs bootstrap file,
app/Config/bootstrap.php. This file is executed just after CakePHPs core bootstrapping.
found in
Routing
Routing is a feature that maps URLs to controller actions. It was added to CakePHP to make pretty URLs
more configurable and flexible. Using Apaches mod_rewrite is not required for using routes, but it will
make your address bar look much more tidy.
Routing in CakePHP also encompasses the idea of reverse routing, where an array of parameters can be reversed into a string URL. By using reverse routing, you can easily re-factor your applications URL structure
without having to update all your code.
Routes Configuration
Routes in an application are configured in app/Config/routes.php. This file is included by the
Dispatcher when handling routes and allows you to define application specific routes you want used.
Routes declared in this file are processed top to bottom when incoming requests are matched. This means
that the order you place routes can affect how routes are parsed. Its generally a good idea to place most
frequently visited routes at the top of the routes file if possible. This will save having to check a number of
routes that wont match on each request.
Routes are parsed and matched in the order they are connected in. If you define two similar routes, the
first defined route will have higher priority over the one defined latter. After connecting routes you can
manipulate the order of routes using Router::promote().
Routing
845
CakePHP also comes with a few default routes to get you started. These can be disabled later on once you
are sure you dont need them. See Disabling the Default Routes on how to disable the default routing.
Default Routing
Before you learn about configuring your own routes, you should know that CakePHP comes configured with
a default set of routes. CakePHPs default routing will get you pretty far in any application. You can access
an action directly via the URL by putting its name in the request. You can also pass parameters to your
controller actions using the URL.
URL pattern default routes:
http://example.com/controller/action/param1/param2/param3
The URL /posts/view maps to the view() action of the PostsController, and /products/view_clearance maps
to the view_clearance() action of the ProductsController. If no action is specified in the URL, the index()
method is assumed.
The default routing setup also allows you to pass parameters to your actions using the URL. A request for
/posts/view/25 would be equivalent to calling view(25) on the PostsController, for example. The default
routing also provides routes for plugins, and prefix routes should you choose to use those features.
The built-in routes live in Cake/Config/routes.php. You can disable the default routing by removing
them from your applications routes.php file.
Connecting Routes
Defining your own routes allows you to define how your application will respond to a given URL. Define
your own routes in the app/Config/routes.php file using the Router::connect() method.
The connect() method takes up to three parameters: the URL you wish to match, the default values for
your route elements, and regular expression rules to help the router match elements in the URL.
The basic format for a route definition is:
Router::connect(
URL,
array(default => defaultValue),
array(option => matchingRegex)
);
The first parameter is used to tell the router what sort of URL youre trying to control. The URL is a normal
slash delimited string, but can also contain a wildcard (*) or Route Elements. Using a wildcard tells the
router that you are willing to accept any additional arguments supplied. Routes without a * only match the
exact template pattern supplied.
Once youve 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. Lets look at some
basic examples before we start using the third parameter of connect():
846
Router::connect(
/pages/*,
array(controller => pages, action => display)
);
This route is found in the routes.php file distributed with CakePHP. This route matches any URL starting with /pages/ and hands it to the display() action of the PagesController(); The request
/pages/products would be mapped to PagesController->display(products).
In addition to the greedy star /* there is also the /** trailing star syntax. Using a trailing double star,
will capture the remainder of a URL as a single passed argument. This is useful when you want to use an
argument that included a / in it:
Router::connect(
/pages/**,
array(controller => pages, action => show)
);
The incoming URL of /pages/the-example-/-and-proof would result in a single passed argument of the-example-/-and-proof.
New in version 2.1: The trailing double star was added in 2.1.
You can use the second parameter of Router::connect() to provide any routing parameters that are
composed of the default values of the route:
Router::connect(
/government,
array(controller => pages, action => display, 5)
);
This 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 /pages/display/5.
Note: Although you can connect alternate routes, the default routes will continue to work. This could
create situations, where content could end up with 2 URLs. See Disabling the Default Routes to disable
default routes, and only provide the URLs you define.
Another common use for the Router is to define an alias for a controller. Lets say that instead
of accessing our regular URL at /users/some_action/5, wed like to be able to access it by
/cooks/some_action/5. The following route easily takes care of that:
Router::connect(
/cooks/:action/*, array(controller => users)
);
This is telling the Router that any url beginning with /cooks/ should be sent to the users controller. The
action called will depend on the value of the :action parameter. By using Route Elements, you can create
variable routes, that accept user input or variables. The above route also uses the greedy star. The greedy
star indicates to Router that this route should accept any additional positional arguments given. These
arguments will be made available in the Passed Arguments array.
Routing
847
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().
Note: Patterns used for route elements must not contain any capturing groups. If they do, Router will not
function correctly.
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->request->params[id].
If you have a single controller in your application and you do not want the controller name to appear in the
URL, you can map all URLs to actions in your controller. For example, to map all URLs to actions of the
home controller, e.g have URLs like /demo instead of /home/demo, you can do the following:
Router::connect(/:action, array(controller => home));
If you would like to provide a case insensitive URL, you can use regular expression inline modifiers:
Router::connect(
/:userShortcut,
array(controller => teachers, action => profile, 1),
848
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: its 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.
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, handing the requests to the index() actions of their respective controllers, with the date parameters in
$this->request->params.
There are several route elements that have special meaning in CakePHP, and should not be used unless you
want the special meaning
controller Used to name the controller for a route.
action Used to name the controller action for a route.
plugin Used to name the plugin a controller is located in.
prefix Used for Prefix Routing
ext Used for File Extensions routing.
Passing Parameters to Action
When connecting routes using Route Elements you may want to have routed elements be passed arguments
instead. By using the 3rd argument of Router::connect() you can define which route elements should
also be made available as passed arguments:
// SomeController.php
public function view($articleId = null, $slug = null) {
// some code here...
}
// routes.php
Routing
849
Router::connect(
/blog/:id-:slug, // E.g. /blog/3-CakePHP_Rocks
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 CakePHP
will know how to form the URL as defined in the routes:
// view.ctp
// this will return a link to /blog/3-CakePHP_Rocks
echo $this->Html->link(CakePHP Rocks, array(
controller => blog,
action => view,
id => 3,
slug => CakePHP_Rocks
));
The above route definition uses the named key to define how several named parameters should be treated.
Lets go through each of the various rules one-by-one:
wibble has no additional information. This means it will always parse if found in a URL matching
this route.
fish has an array of conditions, containing the action key. This means that fish will only be parsed
as a named parameter if the action is also index.
fizz also has an array of conditions. However, it contains two controllers, this means that fizz will
850
only be parsed if the controller matches one of the names in the array.
buzz has a string condition. String conditions are treated as regular expression fragments. Only
values for buzz matching the pattern will be parsed.
If a named parameter is used and it does not match the provided criteria, it will be treated as a passed
argument instead of a named parameter.
Prefix Routing
Many applications require an administration section where privileged users can make changes. This is
often done through a special URL such as /admin/users/edit/5. In CakePHP, prefix routing can
be enabled from within the core configuration file by setting the prefixes with Routing.prefixes. Note that
prefixes, although related to the router, are to be configured in app/Config/core.php:
Configure::write(Routing.prefixes, array(admin));
In your controller, any action with an admin_ prefix will be called. Using our users example, accessing
the URL /admin/users/edit/5 would call the method admin_edit of our UsersController
passing 5 as the first parameter. The view file used would be app/View/Users/admin_edit.ctp
You can map the URL /admin to your admin_index action of pages controller using following route:
Router::connect(
/admin,
array(controller => pages, action => index, admin => true)
);
CakePHP will automatically generate routes for both the admin and manager prefixes. Each configured
prefix will have the following routes generated for it:
Router::connect(
"/{$prefix}/:plugin/:controller",
array(action => index, prefix => $prefix, $prefix => true)
);
Router::connect(
"/{$prefix}/:plugin/:controller/:action/*",
array(prefix => $prefix, $prefix => true)
);
Router::connect(
"/{$prefix}/:controller",
array(action => index, prefix => $prefix, $prefix => true)
);
Router::connect(
"/{$prefix}/:controller/:action/*",
array(prefix => $prefix, $prefix => true)
);
Routing
851
Much like admin routing all prefix actions should be prefixed with the prefix name.
/manager/posts/add would map to PostsController::manager_add().
Additionally, the current prefix
$this->request->prefix
will
be
available
from
the
controller
methods
So
through
When using prefix routes its important to remember, using the HTML helper to build your links will help
maintain the prefix calls. Heres how to build this link using the HTML helper:
// Go into a prefixed route.
echo $this->Html->link(
Manage posts,
array(manager => true, controller => posts, action => add)
);
// leave a prefix
echo $this->Html->link(
View Post,
array(manager => false, controller => posts, action => view, 5)
);
Plugin Routing
Plugin routing uses the plugin key. You can create links that point to a plugin, but adding the plugin key to
your URL array:
echo $this->Html->link(
New todo,
array(plugin => todo, controller => todo_items, action => create)
);
Conversely if the active request is a plugin request and you want to create a link that has no plugin you can
do the following:
echo $this->Html->link(
New todo,
array(plugin => null, controller => users, action => profile)
);
By setting plugin => null you tell the Router that you want to create a link that is not part of a plugin.
File Extensions
To handle different file extensions with your routes, you need one extra line in your routes config file:
Router::parseExtensions(html, rss);
This will tell the router to remove any matching file extensions, and then parse what remains.
If you want to create a URL such as /page/title-of-page.html you would create your route as illustrated
below:
852
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:
$this->Html->link(
Link title,
array(
controller => pages,
action => view,
title => super-article,
ext => html
)
);
The above route will only match for PUT requests. Using these conditions, you can create custom REST
routing, or other request data dependent information.
Passed Arguments
Passed arguments are additional arguments or path segments that are used when making a request. They are
often used to pass parameters to your controller methods.
Routing
853
http://localhost/calendars/view/recent/mark
In
the
above
example,
both
recent
and
mark
are
passed
arguments
to
CalendarsController::view().
Passed arguments are given to your controllers in
three ways.
First as arguments to the action method called, and secondly they are available
in $this->request->params[pass] as a numerically indexed array.
Lastly there is
$this->passedArgs available in the same way as the second one. When using custom routes
you can force particular parameters to go into the passed arguments as well.
If you were to visit the previously mentioned URL, and you had a controller action that looked like:
CalendarsController extends AppController {
public function view($arg1, $arg2) {
debug(func_get_args());
}
}
Note: $this->passedArgs may also contain named parameters as a named array mixed with Passed arguments.
When generating URLs, using a routing array you add passed arguments as values without string keys in
the array:
array(controller => posts, action => view, 5)
854
Named Parameters
You can name parameters and send their values using the URL. A request for
/posts/view/title:first/category:general would result in a call to the view() action
of the PostsController. In that action, youd find the values of the title and category parameters inside
$this->params[named]. They are also available inside $this->passedArgs. In both cases
you can access named parameters using their name as an index. If named parameters are omitted, they will
not be set.
Note: What is parsed as a named parameter is controlled by Router::connectNamed(). If your
named parameters are not reverse routing, or parsing correctly, you will need to inform Router about
them.
Some summarizing examples for default routes might prove helpful:
URL to controller action mapping using default routes:
URL: /monkeys/jump
Mapping: MonkeysController->jump();
URL: /products
Mapping: ProductsController->index();
URL: /tasks/view/45
Mapping: TasksController->view(45);
URL: /donations/view/recent/2001
Mapping: DonationsController->view(recent, 2001);
URL: /contents/view/chapter:models/section:associations
Mapping: ContentsController->view();
$this->passedArgs[chapter] = models;
$this->passedArgs[section] = associations;
$this->params[named][chapter] = models;
$this->params[named][section] = associations;
When making custom routes, a common pitfall is that using named parameters will break your custom
routes. In order to solve this you should inform the Router about which parameters are intended to be
named parameters. Without this knowledge the Router is unable to determine whether named parameters are
intended to actually be named parameters or routed parameters, and defaults to assuming you intended them
to be routed parameters. To connect named parameters in the router use Router::connectNamed():
Router::connectNamed(array(chapter, section));
Will ensure that your chapter and section parameters reverse route correctly.
When generating URLs, using a routing array you add named parameters as values with string keys matching the name:
array(controller => posts, action => view, chapter => association)
Since chapter doesnt match any defined route elements, its treated as a named parameter.
Routing
855
Note: Both named parameters and route elements share the same key-space. Its best to avoid re-using a
key for both a route element and a named parameter.
Named parameters also support using arrays to generate and parse URLs. The syntax works very similar to
the array syntax used for GET parameters. When generating URLs you can use the following syntax:
$url = Router::url(array(
controller => posts,
action => index,
filter => array(
published => 1,
frontpage => 1
)
));
Arrays can be deeply nested as well, allowing you even more flexibility in passing arguments:
$url = Router::url(array(
controller => posts,
action => search,
models => array(
post => array(
order => asc,
filter => array(
published => 1
)
),
comment => array(
order => desc,
filter => array(
spam => 0
)
),
),
users => array(1, 2, 3)
));
You would end up with a pretty long url like this (wrapped for easy reading):
posts/search
/models[post][order]:asc/models[post][filter][published]:1
/models[comment][order]:desc/models[comment][filter][spam]:0
/users[]:1/users[]:2/users[]:3
856
And the resulting array that would be passed to the controller would match that which you passed to the
router:
$this->passedArgs[models] = array(
post => array(
order => asc,
filter => array(
published => 1
)
),
comment => array(
order => desc,
filter => array(
spam => 0
)
),
);
Routing
857
Parse only the page parameter if the current action is index and the controller is pages:
Router::connectNamed(
array(page => array(action => index, controller => pages)),
array(default => false, greedy => false)
);
Reverse Routing
Reverse routing is a feature in CakePHP that is used to allow you to easily change your URL structure
without having to modify all your code. By using routing arrays to define your URLs, you can later configure
routes and the generated URLs will automatically update.
If you create URLs using strings like:
$this->Html->link(View, /posts/view/ + $id);
And then later decide that /posts should really be called articles instead, you would have to go through
your entire application renaming URLs. However, if you defined your link like:
$this->Html->link(
View,
array(controller => posts, action => view, $id)
);
Then when you decided to change your URLs, you could do so by defining a route. This would change both
the incoming URL mapping, as well as the generated URLs.
When using array URLs, you can define both query string parameters and document fragments using special
keys:
Router::url(array(
controller => posts,
action => index,
? => array(page => 1),
# => top
));
// will generate a URL like.
/posts/index?page=1#top
858
Redirect Routing
Redirect routing allows you to issue HTTP status 30x redirects for incoming routes, and point them at
different URLs. This is useful when you want to inform client applications that a resource has moved and
you dont want to expose two URLs for the same content
Redirection routes are different from normal routes as they perform an actual header redirection if a match
is found. The redirection can occur to a destination within your application or an outside location:
Router::redirect(
/home/*,
array(controller => posts, action => view),
// or array(persist=>array(id)) for default routing where the
// view action expects $id as an argument
array(persist => true)
);
Redirects /home/* to /posts/view and passes the parameters to /posts/view. Using an array as
the redirect destination allows you to use other routes to define where a URL string should be redirected to.
You can redirect to external locations using string URLs as the destination:
Router::redirect(/posts/*, http://google.com, array(status => 302));
Routing
859
This route would create an instance of SlugRoute and allow you to implement custom parameter handling.
Router API
class Router
Router manages generation of outgoing URLs, and parsing of incoming request uris into parameter
sets that CakePHP can dispatch.
static Router::connect($route, $defaults = array(), $options = array())
Parameters
$route (string) A string describing the template of the route
$defaults (array) An array describing the default route parameters. These parameters will be used by default and can supply routing parameters that are not
dynamic.
$options (array) An array matching the named elements in the route to regular
expressions which that element should match. Also contains additional parameters such as which routed parameters should be shifted into the passed arguments,
supplying patterns for routing parameters and supplying the name of a custom
routing class.
Routes are a way of connecting request URLs to objects in your application. At their core routes are
a set of regular expressions that are used to match requests to destinations.
Examples:
Router::connect(/:controller/:action/*);
The first parameter will be used as a controller name while the second is used as the action name.
The /* syntax makes this route greedy in that it will match requests like /posts/index as well
as requests like /posts/edit/1/foo/bar .
Router::connect(
/home-page,
array(controller => pages, action => display, home)
);
The above shows the use of route parameter defaults. And providing routing parameters for a static
route.
Router::connect(
/:lang/:controller/:action/:id,
array(),
array(id => [0-9]+, lang => [a-z]{3})
);
Shows connecting a route with custom route parameters as well as providing patterns for those parameters. Patterns for routing parameters do not need capturing groups, as one will be added for each
route params.
860
$options offers three special keys. pass, persist and routeClass have special meaning in
the $options array.
pass is used to define which of the routed parameters should be shifted into the pass array.
Adding a parameter to pass will remove it from the regular route array. Ex. pass =>
array(slug)
persist is used to define which route parameters should be automatically included when
generating new URLs. You can override persistent parameters by redefining them in a URL or
remove them by setting the parameter to false. Ex. persist => array(lang)
routeClass is used to extend and change how individual routes parse requests and handle
reverse routing, via a custom routing class. Ex. routeClass => SlugRoute
named is used to configure named parameters at the route level. This key uses the same options
as Router::connectNamed()
static Router::redirect($route, $url, $options = array())
Parameters
$route (string) A route template that dictates which URLs should be redirected.
$url (mixed) Either a routing array or a string url for the destination of the
redirect.
$options (array) An array of options for the redirect.
Connects a new redirection Route in the router. See Redirect Routing for more information.
static Router::connectNamed($named, $options = array())
Parameters
$named (array) A list of named parameters. Key value pairs are accepted where
values are either regex strings to match, or arrays.
$options (array) Allows control of all settings: separator, greedy, reset, default
Specifies what named parameters CakePHP should be parsing out of incoming URLs. By default
CakePHP will parse every named parameter out of incoming URLs. See Controlling Named Parameters for more information.
static Router::promote($which = null)
Parameters
$which (integer) A zero-based array index representing the route to move. For
example, if 3 routes have been added, the last route would be 2.
Promote a route (by default, the last one added) to the beginning of the list.
static Router::url($url = null, $full = false)
Parameters
$url (mixed) Cake-relative URL, like /products/edit/92 or /presidents/elect/4 or a routing array
Routing
861
$full (mixed) If (boolean) true, the full base URL will be prepended to the result.
If an array accepts the following keys
escape - used when making URLs embedded in HTML escapes query string
&
full - if true the full base URL will be prepended.
Generate a URL for the specified action. Returns a URL pointing to a combination of controller and
action. $url can be:
Empty - the method will find the address to the actual controller/action.
/ - the method will find the base URL of application.
A combination of controller/action - the method will find the URL for it.
There are a few special parameters that can change the final URL string that is generated:
base - Set to false to remove the base path from the generated URL. If your application is not
in the root directory, this can be used to generate URLs that are CakePHP relative. CakePHP
relative URLs are required when using requestAction.
? - Takes an array of query string parameters
# - Allows you to set URL hash fragments.
full_base - If true the value of Router::fullBaseUrl() will be prepended to generated URLs.
static Router::mapResources($controller, $options = array())
Creates REST resource routes for the given controller(s). See the REST section for more information.
static Router::parseExtensions($types)
Used in routes.php to declare which File Extensions your application supports. By providing no
arguments, all file extensions will be supported.
static Router::setExtensions($extensions, $merge = true)
New in version 2.2.
Set or add valid extensions. To have the extensions parsed, you are still required to call
Router::parseExtensions().
static Router::defaultRouteClass($classname)
New in version 2.1.
Set the default route to be used when connecting routes in the future.
static Router::fullBaseUrl($url = null)
New in version 2.4.
Get or set the baseURL used for generating URLs. When setting this value you should be sure to
include the fully qualified domain name including protocol.
Setting values with this method will also update App.fullBaseUrl in Configure.
class CakeRoute
The base class for custom routes to be based on.
862
CakeRoute::parse($url)
Parameters
$url (string) The string URL to parse.
Parses an incoming URL, and generates an array of request parameters that Dispatcher can act upon.
Extending this method allows you to customize how incoming URLs are converted into an array.
Return false from URL to indicate a match failure.
CakeRoute::match($url)
Parameters
$url (array) The routing array to convert into a string URL.
Attempt to match a URL array. If the URL matches the route parameters and settings, then return a
generated string URL. If the URL doesnt match the route parameters, false will be returned. This
method handles the reverse routing or conversion of URL arrays into string URLs.
CakeRoute::compile()
Force a route to compile its regular expression.
Sessions
CakePHP provides a wrapper and suite of utility features on top of PHPs native session extension.
Sessions allow you to identify unique users across the requests and store persistent data for specific users.
Unlike Cookies, session data is not available on the client side. Usage of $_SESSION is generally avoided
in CakePHP, and instead usage of the Session classes is preferred.
Session Configuration
Session configuration is stored in Configure under the top level Session key, and a number of options
are available:
Session.cookie - Change the name of the session cookie.
Session.timeout - The number of minutes before CakePHPs session handler expires the session.
This affects Session.autoRegenerate (below), and is handled by CakeSession.
Session.cookieTimeout - The number of minutes before the session cookie expires. If this is
undefined, it will use the same value as Session.timeout. This affects the session cookie, and is
handled by PHP itself.
Session.checkAgent - Should the user agent be checked, on each request. If the user agent does
not match the session will be destroyed.
Session.autoRegenerate - Enabling this setting, turns on automatic renewal of sessions, and
session ids that change frequently. Enabling this value will use the sessions Config.countdown
value to keep track of requests. Once the countdown reaches 0, the session id will be regenerated.
This is a good option to use for applications that need frequently changing session ids for security
Sessions
863
reasons. You can control the number of requests needed to regenerate the session by modifying
CakeSession::$requestCountdown.
Session.defaults - Allows you to use one the built-in default session configurations as a base
for your session configuration.
Session.handler - Allows you to define a custom session handler. The core database and cache
session handlers use this. This option replaces Session.save in previous versions. See below for
additional information on Session handlers.
Session.ini - Allows you to set additional session ini settings for your config. This combined
with Session.handler replace the custom session handling features of previous versions
CakePHPs defaults to setting session.cookie_secure to true, when your application is on an SSL
protocol. If your application serves from both SSL and non-SSL protocols, then you might have problems
with sessions being lost. If you need access to the session on both SSL and non-SSL domains you will want
to disable this:
Configure::write(Session, array(
defaults => php,
ini => array(
session.cookie_secure => false
)
));
Session cookie paths default to / in 2.0, to change this you can use the session.cookie_path ini flag
to the directory path of your application:
Configure::write(Session, array(
defaults => php,
ini => array(
session.cookie_path => /app/dir
)
));
The above will use the built-in php session configuration. You could augment part or all of it by doing the
following:
Configure::write(Session, array(
defaults => php,
cookie => my_app,
864
The above overrides the timeout and cookie name for the php session configuration. The built-in configurations are:
php - Saves sessions with the standard settings in your php.ini file.
cake - Saves sessions as files inside app/tmp/sessions. This is a good option when on hosts
that dont allow you to write outside your own home dir.
database - Use the built-in database sessions. See below for more information.
cache - Use the built-in cache sessions. See below for more information.
Session Handlers
Session handlers can also be defined in the session config array. When defined they allow you to map the
various session_save_handler values to a class or object you want to use for session saving. There
are two ways to use the handler. The first is to provide an array with 5 callables. These callables are then
applied to session_set_save_handler:
Configure::write(Session, array(
userAgent => false,
cookie => my_cookie,
timeout => 600,
handler => array(
array(Foo, open),
array(Foo, close),
array(Foo, read),
array(Foo, write),
array(Foo, destroy),
array(Foo, gc),
),
ini => array(
cookie_secure => 1,
use_trans_sid => 0
)
));
The second mode is to define an engine key. This key should be a class name that implements
CakeSessionHandlerInterface. Implementing this interface will allow CakeSession to automatically map the methods for the handler. Both the core Cache and Database session handlers use this method
for saving sessions. Additional settings for the handler should be placed inside the handler array. You can
then read those values out from inside your handler.
You can also use session handlers from inside plugins. By setting the engine to something like
MyPlugin.PluginSessionHandler. This will load and use the PluginSessionHandler class
from inside the MyPlugin of your application.
Sessions
865
CakeSessionHandlerInterface
This interface is used for all custom session handlers inside CakePHP, and can be used to
create custom user land session handlers.
Simply implement the interface in your class and
set Session.handler.engine to the class name youve created.
CakePHP will attempt
to load the handler from inside app/Model/Datasource/Session/$classname.php.
So
if
your
class
name
is
AppSessionHandler
the
file
should
be
app/Model/Datasource/Session/AppSessionHandler.php.
Database sessions
The changes in session configuration change how you define database sessions. Most of the time you
will only need to set Session.handler.model in your configuration as well as choose the database
defaults:
Configure::write(Session, array(
defaults => database,
handler => array(
model => CustomSession
)
));
The above will tell CakeSession to use the built-in database defaults, and specify that a model called
CustomSession will be the delegate for saving session information to the database.
If you do not need a fully custom session handler, but still require database-backed session storage, you can
simplify the above code to:
Configure::write(Session, array(
defaults => database
));
This configuration will require a database table to be added with at least these fields:
CREATE TABLE cake_sessions (
id varchar(255) NOT NULL DEFAULT ,
data text,
expires int(11) DEFAULT NULL,
PRIMARY KEY (id)
);
You can also use the schema shell to create this table using the schema file provided in the default app
skeleton:
$ Console/cake schema create sessions
Cache Sessions
The Cache class can be used to store sessions as well. This allows you to store sessions in a cache like APC,
memcache, or Xcache. There are some caveats to using cache sessions, in that if you exhaust the cache
866
This will configure CakeSession to use the CacheSession class as the delegate for saving the sessions. You can use the config key which cache configuration to use. The default cache configuration
is default.
Sessions
867
Our class extends the built-in DatabaseSession so we dont have to duplicate all of its logic and behavior. We wrap each operation with a Cache operation. This lets us fetch sessions from the fast cache,
and not have to worry about what happens when we fill the cache. Using this session handler is also easy.
In your core.php make the session block look like the following:
Configure::write(Session, array(
defaults => database,
handler => array(
engine => ComboSession,
model => Session,
cache => apc
)
));
// Make sure to add a apc cache config
Cache::config(apc, array(engine => Apc));
Now our application will start using our custom session handler for reading & writing session data.
class CakeSession
static CakeSession::read($key)
You can read values from the session using Set::classicExtract() compatible syntax:
CakeSession::read(Config.language);
static CakeSession::delete($key)
When you need to delete data from the session, you can use delete:
CakeSession::delete(Config.language);
You should also see the documentation on Sessions and SessionHelper for how to access Session data in the
controller and view.
Exceptions
Exceptions can be used for a variety of uses in your application. CakePHP uses exceptions internally to
indicate logic errors or misuse. All of the exceptions CakePHP raises extend CakeException, and there
are class/task specific exceptions that extend this base class.
CakePHP also provides a number of exception classes that you can use for HTTP errors. See the section on
Built-in Exceptions for CakePHP for more information.
Exception configuration
There are a few keys available for configuring exceptions:
Configure::write(Exception, array(
handler => ErrorHandler::handleException,
renderer => ExceptionRenderer,
log => true
));
handler - callback - The callback to handle exceptions. You can set this to any callback type,
including anonymous functions.
renderer - string - The class responsible for rendering uncaught exceptions. If you choose a custom class you should place the file for that class in app/Lib/Error. This class needs to implement a
render() method.
log - boolean - When true, exceptions + their stack traces will be logged to CakeLog.
consoleHandler - callback - The callback used to handle exceptions, in a console context. If
undefined, CakePHPs default handler will be used.
Exceptions
869
Exception rendering by default displays an HTML page, you can customize either the handler or the renderer
by changing the settings. Changing the handler, allows you to take full control over the exception handling
process, while changing the renderer allows you to easily change the output type/contents, as well as add in
application specific exception handling.
New in version 2.2: The Exception.consoleHandler option was added in 2.2.
Exception classes
There are a number of exception classes in CakePHP. Each exception replaces a cakeError() error
messages from the past. Exceptions offer additional flexibility in that they can be extended and contain
some logic. The built in exception handling will capture any uncaught exceptions and render a useful page.
Exceptions that do not specifically use a 400 range code, will be treated as an Internal Server Error.
By using exceptions for HTTP errors, you can keep your code both clean, and give RESTful responses to
client applications and users.
870
In addition, the following framework layer exceptions are available, and will be thrown from a number of
CakePHP core components:
exception MissingViewException
The chosen view file could not be found.
exception MissingLayoutException
The chosen layout could not be found.
exception MissingHelperException
A helper was not found.
exception MissingBehaviorException
A configured behavior could not be found.
exception MissingComponentException
A configured component could not be found.
exception MissingTaskException
A configured task was not found.
exception MissingShellException
The shell class could not be found.
exception MissingShellMethodException
The chosen shell class has no method of that name.
exception MissingDatabaseException
The configured database is missing.
exception MissingConnectionException
A models connection is missing.
exception MissingTableException
A models table is missing from CakePHPs cache or the datasource. Upon adding a new table to a
datasource, the model cache (found in tmp/cache/models by default) must be removed.
exception MissingActionException
The requested controller action could not be found.
exception MissingControllerException
The requested controller could not be found.
exception PrivateActionException
Private action access. Either accessing private/protected/_ prefixed actions, or trying to access prefixed
routes incorrectly.
exception CakeException
Base exception class in CakePHP. All framework layer exceptions thrown by CakePHP will extend
this class.
These exception classes all extend CakeException. By extending CakeException, you can create your
own framework errors. All of the standard Exceptions that CakePHP will throw also extend CakeException.
New in version 2.3: CakeBaseException was added
Exceptions
871
exception CakeBaseException
Base exception class in CakePHP. All CakeExceptions and HttpExceptions above extend this class.
CakeBaseException::responseHeader($header = null, $value = null)
See CakeResponse::header()
All Http and CakePHP exceptions extend the CakeBaseException class, which has a method to add headers
to the response. For instance when throwing a 405 MethodNotAllowedException the rfc2616 says: The
response MUST include an Allow header containing a list of valid methods for the requested resource.
The above would cause the configured Exception.handler to catch and process the
NotFoundException. By default this will create an error page, and log the exception.
Exception Renderer
class ExceptionRenderer(Exception $exception)
The ExceptionRenderer class with the help of CakeErrorController takes care of rendering the error
pages for all the exceptions thrown by you application.
The error page views are located at app/View/Errors/. For all 4xx and 5xx errors the view files
error400.ctp and error500.ctp are used respectively. You can customize them as per your needs.
By default your app/Layouts/default.ctp is used for error pages too. If for eg. you want to use
another layout app/Layouts/my_error.ctp for your error pages, then simply edit the error views and
add the statement $this->layout = my_error; to the error400.ctp and error500.ctp.
Each framework layer exception has its own view file located in the core templates but you really dont need
to bother customizing them as they are used only during development. With debug turned off all framework
layer exceptions are converted to InternalErrorException.
http://php.net/manual/en/spl.exceptions.php
872
are coerced into either 500 or 404 errors depending on the code they use. When in development mode
CakeException objects simply need a new template that matches the class name in order to provide
useful information. If your application contained the following exception:
class MissingWidgetException extends CakeException {};
When caught by the built-in exception handler, you would get a $widget variable in your error view
template. In addition if you cast the exception as a string or use its getMessage() method you will
get Seems that Pointy is missing.. This allows you easily and quickly create your own rich
development errors, just like CakePHP uses internally.
Creating custom status codes
You can create custom HTTP status codes by changing the code used when creating an exception:
throw new MissingWidgetHelperException(Its not here, 501);
Will create a 501 response code, you can use any HTTP status code you want. In development, if your
exception doesnt have a specific template, and you use a code equal to or greater than 500 you will see
the error500 template. For any other error code youll get the error400 template. If you have defined
an error template for your custom exception, that template will be used in development mode. If youd like
your own exception handling logic even in production, see the next section.
Exceptions
873
You can run any code you wish inside handleException. The code above would simple print Oh noes!
plus the exception message. You can define exception handlers as any type of callback, even an anonymous
function if you are using PHP 5.3:
Configure::write(Exception.handler, function ($error) {
echo Ruh roh . $error->getMessage();
});
By creating a custom exception handler you can provide custom error handling for application exceptions.
In the method provided as the exception handler you could do the following:
// in app/Lib/AppErrorHandler.php
class AppErrorHandler {
public static function handleException($error) {
if ($error instanceof MissingWidgetException) {
return self::handleMissingWidget($error);
}
// do other stuff.
}
}
Using AppController::appError();
Implementing this method is an alternative to implementing a custom exception handler. Its primarily
provided for backwards compatibility, and is not recommended for new applications. This controller method
is called instead of the default exception rendering. It receives the thrown exception as its only argument.
You should implement your error handling in that method:
874
The above would handle any exceptions of the type MissingWidgetException, and allow you to
provide custom display/handling logic for those application exceptions. Exception handling methods get
the exception being handled as their argument.
Note: Your custom renderer should expect an exception in its constructor, and implement a render method.
Failing to do so will cause additional errors.
Note: If you are using a custom Exception.handler this setting will have no effect. Unless you
reference it inside your implementation.
Exceptions
875
}
}
Alternatively, you could just override the core CakeErrorController, by including one in
app/Controller. If you are using a custom controller for error handling, make sure you do all
the setup you need in your constructor, or the render method. As those are the only methods that the built-in
ErrorHandler class directly call.
Logging exceptions
Using the built-in exception handling, you can log all the exceptions that are dealt with by ErrorHandler by
setting Exception.log to true in your core.php. Enabling this will log every exception to CakeLog and
the configured loggers.
Note: If you are using a custom Exception.handler this setting will have no effect. Unless you
reference it inside your implementation.
Error Handling
For 2.0 Object::cakeError() has been removed. Instead it has been replaced with a number of
exceptions. All of the core classes that previously called cakeError are now throwing exceptions. This lets
you either choose to handle the errors in your application code, or let the built-in exception handling deal
with them.
There is more control than ever for error and exception handling in CakePHP 2.0. You can configure which
methods you want to set as the default error handler, and exception handler using configure.
Error configuration
Error configuration is done inside your applications app/Config/core.php file. You can define a
callback to be fired each time your application triggers any PHP error - exceptions are handled Exceptions
separately. The callback can be any PHP callable, including an anonymous function. The default error
handling configuration looks like:
Configure::write(Error, array(
handler => ErrorHandler::handleError,
level => E_ALL & ~E_DEPRECATED,
trace => true
));
trace - boolean - Include stack traces for errors in log files. Stack traces will be included in the log
after each error. This is helpful for finding where/when errors are being raised.
consoleHandler - callback - The callback used to handle errors when running in the console. If
undefined, CakePHPs default handlers will be used.
ErrorHandler by default, displays errors when debug > 0, and logs errors when debug = 0. The type
of errors captured in both cases is controlled by Error.level. The fatal error handler will be called
independent of debug level or Error.level configuration, but the result will be different based on
debug level.
Note: If you use a custom error handler, the trace setting will have no effect, unless you refer to it in your
error handling function.
New in version 2.2: The Error.consoleHandler option was added in 2.2.
Changed in version 2.2: The Error.handler and Error.consoleHandler will receive the fatal
error codes as well. The default behavior is show a page to internal server error (debug disabled) or a page
with the message, file and line (debug enabled).
This class/method will print out There has been an error! each time an error occurs. Since you can define
an error handler as any callback type, you could use an anonymous function if you are using PHP5.3 or
greater.
Configure::write(Error.handler, function($code, $description, $file = null,
$line = null, $context = null) {
echo Oh no something bad happened;
});
It is important to remember that errors captured by the configured error handler will be php errors, and that
if you need custom error handling, you probably also want to configure Exceptions handling as well.
Error Handling
877
If
you
want
to
keep
the
default
fatal
error
behavior,
ErrorHandler::handleFatalError() from your custom handler.
you
can
call
Debugging
Debugging is an inevitable and necessary part of any development cycle. While CakePHP doesnt 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.
Basic Debugging
debug(mixed $var, boolean $showHtml = null, $showFrom = true)
Parameters
$var (mixed) The contents to print out. Arrays and objects work well.
$showHTML (boolean) Set to true, to enable escaping. Escaping is enabled by
default in 2.0 when serving web requests.
$showFrom (boolean) Show the line and file the debug() occurred on.
878
The debug() function is a globally available function that works similarly to the PHP function print_r(). The
debug() function allows you to show the contents of a variable in a number of different ways. First, if youd
like data to be shown in an HTML-friendly way, set the second parameter to true. The function also prints
out the line and file it is originating from by default.
Output from this function is only shown if the core debug variable has been set to a value greater than 0.
Changed in version 2.1: The output of debug() more resembles var_dump(), and uses Debugger
internally.
Debugger Class
The debugger class was introduced with CakePHP 1.2 and offers even more options for obtaining debugging
information. It has several functions which are invoked statically, and provide dumping, logging, and error
handling functions.
The Debugger Class overrides PHPs default error handling, replacing it with far more useful error reports. The Debuggers 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.
Debugging
879
// outputs
Car
Car::colour = red
Car::make = Toyota
Car::model = Camry
Car::mileage = 15000
Car::accelerate()
Car::decelerate()
Car::stop()
Changed in version 2.1: In 2.1 forward the output was updated for readability.
Debugger::exportVar()
See
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().
static Debugger::excerpt($file, $line, $context)
Grab an excerpt from the file at $path (which is an absolute filepath), highlights line number $line
with $context number of lines around it.
pr(Debugger::excerpt(ROOT . DS . LIBS . debugger.php, 321, 2));
//will output the following.
Array
(
[0] => <code><span style="color: #000000"> * @access public</span></code>
[1] => <code><span style="color: #000000"> */</span></code>
880
Although this method is used internally, it can be handy if youre creating your own error messages
or log entries for custom situations.
static Debugger::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.
Changed in version 2.1: This function generates different output in 2.1 forward.
static Debugger::invoke($debugger)
Replace the CakePHP Debugger with a new instance.
static Debugger::getType($var)
Get the type of a variable. Objects will return their class name
New in version 2.1.
The above would write Got here into the debug log. You can use log entries to help debug methods that
involve redirects or complicated loops. You can also use CakeLog::write() to write log messages.
This method can be called statically anywhere in your application anywhere CakeLog has been loaded:
// In app/Config/bootstrap.php
App::uses(CakeLog, Log);
// Anywhere in your application
CakeLog::write(debug, Got here);
Debug Kit
DebugKit is a plugin that provides a number of good debugging tools. It primarily provides a toolbar in the
rendered HTML, that provides a plethora of information about your application and the current request. You
can download DebugKit3 from GitHub.
3
https://github.com/cakephp/debug_kit
Debugging
881
xdebug
If your environment supplies the xdebug php extension, fatal errors will show additional xdebug stack trace
details. Details about xdebug4 .
Testing
CakePHP comes with comprehensive testing support built-in. CakePHP comes with integration for PHPUnit5 . In addition to the features offered by PHPUnit, CakePHP offers some additional features to make
testing easier. This section will cover installing PHPUnit, and getting started with Unit Testing, and how
you can use the extensions that CakePHP offers.
Installing PHPUnit
CakePHP uses PHPUnit as its underlying test framework. PHPUnit is the de-facto standard for unit testing
in PHP. It offers a deep and powerful set of features for making sure your code does what you think it does.
Install via Composer
The newer versions of phpunit do not currently work with cake:
"phpunit/phpunit": "3.7.32"
http://xdebug.org
http://phpunit.de
882
Note: Its a good idea to make the test database and your actual database different databases. This will
prevent any embarrassing mistakes later.
883
For example, would run the tests for your Post model.
This is a very simple example, but it will be useful to show how you can create a simple test case.
After creating and saving our helper, well create the test case file in
app/Test/Case/View/Helper/ProgressHelperTest.php. In that file well start with the following:
App::uses(Controller, Controller);
App::uses(View, View);
App::uses(ProgressHelper, View/Helper);
class ProgressHelperTest extends CakeTestCase {
public function setUp() {
}
public function testBar() {
}
}
Well flesh out this skeleton in a minute. Weve added two methods to start with. First is setUp(). This
method is called before every test method in a test case class. Setup methods should initialize the objects
needed for the test, and do any configuration needed. In our setup method well add the following:
public function setUp() {
parent::setUp();
$Controller = new Controller();
$View = new View($Controller);
$this->Progress = new ProgressHelper($View);
}
Calling the parent method is important in test cases, as CakeTestCase::setUp() does a number things like
backing up the values in Configure and, storing the paths in App.
884
Next, well fill out the test method. Well use some assertions to ensure that our code creates the output we
expect:
public function testBar() {
$result = $this->Progress->bar(90);
$this->assertContains(width: 90%, $result);
$this->assertContains(progress-bar, $result);
$result = $this->Progress->bar(33.3333333);
$this->assertContains(width: 33%, $result);
}
The above test is a simple one but shows the potential benefit of using test cases. We use
assertContains() to ensure that our helper is returning a string that contains the content we expect.
If the result did not contain the expected content the test would fail, and we would know that our code is
incorrect.
By using test cases you can easily describe the relationship between a set of known inputs and their expected
output. This helps you be more confident of the code youre writing as you can easily check that the code
you wrote fulfills the expectations and assertions your tests make. Additionally because tests are code, they
are easy to re-run whenever you make a change. This helps prevent the creation of new bugs.
Running Tests
Once you have PHPUnit installed and some test cases written, youll want to run the test cases very frequently. Its a good idea to run tests before committing any changes to help ensure you havent broken
anything.
Running tests from a browser
CakePHP provides a web interface for running tests, so you can execute your tests through a browser
if youre more comfortable in that environment. You can access the web runner by going to
http://localhost/your_app/test.php. The exact location of test.php will change depending
on your setup. But the file is at the same level as index.php.
Once youve loaded up the test runner, you can navigate App, Core and Plugin test suites. Clicking an
individual test case will run that test and display the results.
Viewing code coverage
If you have XDebug6 installed, you can view code coverage results. Code coverage is useful for telling you
what parts of your code your tests do not reach. Coverage is useful for determining where you should add
tests in the future, and gives you one measurement to track your testing progress with.
6
http://xdebug.org
Testing
885
The inline code coverage uses green lines to indicate lines that have been run. If you hover over a green line
a tooltip will indicate which tests covered the line. Lines in red did not run, and have not been exercised by
your tests. Grey lines are considered unexecutable code by xdebug.
Running tests from command line
CakePHP provides a test shell for running tests. You can run app, core and plugin tests easily using the
test shell. It accepts all the arguments you would expect to find on the normal PHPUnit command line tool
as well. From your app directory you can do the following to run tests:
# Run a model tests in the app
./Console/cake test app Model/Article
# Run a component test in a plugin
./Console/cake test DebugKit Controller/Component/ToolbarComponent
# Run the configure class test in CakePHP
./Console/cake test core Core/Configure
Note: If you are running tests that interact with the session its generally a good idea to use the --stderr
option. This will fix issues with tests failing because of headers_sent warnings.
Changed in version 2.1: The test shell was added in 2.1. The 2.0 testsuite shell is still available but
the new syntax is preferred.
You can also run test shell in the project root directory. This shows you a full list of all the tests that you
886
currently have. You can then freely choose what test(s) to run:
# Run test in project root directory for application folder called app
lib/Cake/Console/cake test app
# Run test in project root directory for an application in ./myapp
lib/Cake/Console/cake test --app myapp app
When you have larger test cases, you will often want to run a subset of the test methods when you are trying
to work on a single failing case. With the CLI runner you can use an option to filter test methods:
./Console/cake test core Console/ConsoleOutput --filter testWriteArray
The filter parameter is used as a case-sensitive regular expression for filtering which test methods to run.
Generating code coverage
You can generate code coverage reports from the command line using PHPUnits built-in code coverage
tools. PHPUnit will generate a set of static HTML files containing the coverage results. You can generate
coverage for a test case by doing the following:
./Console/cake test app Model/Article --coverage-html webroot/coverage
This will put the coverage results in your applications webroot directory. You should be able to view the
results by going to http://localhost/your_app/coverage.
Running tests that use sessions
When running tests on the command line that use sessions youll need to include the --stderr flag.
Failing to do so will cause sessions to not work. PHPUnit outputs test progress to stdout by default, this
causes PHP to assume that headers have been sent which prevents sessions from starting. By switching
PHPUnit to output on stderr, this issue is avoided.
Testing
887
tearDownAfterClass is called once after test methods in a case are started. This method must
be static.
Fixtures
When testing code that depends on models and the database, one can use fixtures as a way to generate
temporary data tables loaded with sample data that can be used by the test. The benefit of using fixtures is
that your test has no chance of disrupting live application data. In addition, you can begin testing your code
prior to actually developing live content for an application.
CakePHP uses the connection named $test in your app/Config/database.php configuration file.
If this connection is not usable, an exception will be raised and you will not be able to use database fixtures.
CakePHP performs the following during the course of a fixture based test case:
1. Creates tables for each of the fixtures needed.
2. Populates tables with data, if data is provided in fixture.
3. Runs test methods.
4. Empties the fixture tables.
5. Removes fixture tables from database.
Creating fixtures
When creating a fixture you will mainly define two things: how the table is created (which fields are part
of the table), and which records will be initially populated to the table. Lets create our first fixture,
that will be used to test our own Article model. Create a file named ArticleFixture.php in your
app/Test/Fixture directory, with the following content:
class ArticleFixture extends CakeTestFixture {
// Optional.
// Set this property to load fixtures to a different test datasource
public $useDbConfig = test;
public $fields = array(
id => array(type => integer, key => primary),
title => array(
type => string,
length => 255,
null => false
),
body => text,
published => array(
type => integer,
default => 0,
null => false
),
created => datetime,
updated => datetime
888
);
public $records = array(
array(
id => 1,
title => First Article,
body => First Article Body,
published => 1,
created => 2007-03-18 10:39:23,
updated => 2007-03-18 10:41:31
),
array(
id => 2,
title => Second Article,
body => Second Article Body,
published => 1,
created => 2007-03-18 10:41:23,
updated => 2007-03-18 10:43:31
),
array(
id => 3,
title => Third Article,
body => Third Article Body,
published => 1,
created => 2007-03-18 10:43:23,
updated => 2007-03-18 10:45:31
)
);
}
The $useDbConfig property defines the datasource of which the fixture will use. If your application
uses multiple datasources, you should make the fixtures match the models datasources but prefixed with
test_. For example if your model uses the mydb datasource, your fixture should use the test_mydb
datasource. If the test_mydb connection doesnt exist, your models will use the default test datasource.
Fixture datasources must be prefixed with test to reduce the possibility of accidentally truncating all your
applications data when running tests.
We use $fields to specify which fields will be part of this table, and how they are defined. The format
used to define these fields is the same used with CakeSchema. The keys available for table definition are:
type
CakePHP internal data type. Currently supported:
string: maps to VARCHAR
text: maps to TEXT
integer: maps to INT
float: maps to FLOAT
datetime: maps to DATETIME
timestamp: maps to TIMESTAMP
time: maps to TIME
Testing
889
890
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 will only import the Article
schema, and does not import records. To import records you can do the following:
class ArticleFixture extends CakeTestFixture {
public $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:
class ArticleFixture extends CakeTestFixture {
public $import = array(table => articles);
}
Will import table definition from a table called articles using your CakePHP database connection named
default. If you want to use a different connection use:
class ArticleFixture extends CakeTestFixture {
public $import = array(table => articles, connection => other);
}
Since it uses your CakePHP database connection, if theres 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 the import to:
class ArticleFixture extends CakeTestFixture {
public $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:
class ArticleFixture extends CakeTestFixture {
public $import = Article;
public $records = array(
array(
id => 1,
Testing
891
The above will load the Article and Comment fixtures from the applications Fixture directory. You can also
load fixtures from CakePHP core, or plugins:
class ArticleTest extends CakeTestCase {
public $fixtures = array(plugin.debug_kit.article, core.comment);
}
Using the core prefix will load fixtures from CakePHP, and using a plugin name as the prefix, will load the
fixture from the named plugin.
You can control when your fixtures are loaded by setting CakeTestCase::$autoFixtures to false
and later load them using CakeTestCase::loadFixtures():
class ArticleTest extends CakeTestCase {
public $fixtures = array(app.article, app.comment);
public $autoFixtures = false;
892
As of 2.5.0, you can load fixtures in subdirectories. Using multiple directories can make it easier to organize your fixtures if you have a larger application. To load fixtures in subdirectories, simply include the
subdirectory name in the fixture name:
class ArticleTest extends CakeTestCase {
public $fixtures = array(app.blog/article, app.blog/comment);
}
Testing Models
Lets say we already have our Article model defined on app/Model/Article.php, which looks like
this:
class Article extends AppModel {
public function published($fields = null) {
$params = array(
conditions => array(
$this->name . .published => 1
),
fields => $fields
);
return $this->find(all, $params);
}
}
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 model - in this case the Article model which we already defined.
Lets now create a file named ArticleTest.php in your app/Test/Case/Model directory, with the
following contents:
App::uses(Article, Model);
class ArticleTest extends CakeTestCase {
public $fixtures = array(app.article);
}
In our test cases variable $fixtures we define the set of fixtures that well use. You should remember to
include all the fixtures that will have queries run against them.
Note: You can override the test model database by specifying the $useDbConfig property. Ensure that
Testing
893
the relevant fixture uses the same value so that the table is created in the correct database.
App::uses(Article, Model);
class ArticleTest extends CakeTestCase {
public $fixtures = array(app.article);
public function setUp() {
parent::setUp();
$this->Article = ClassRegistry::init(Article);
}
public function testPublished() {
$result = $this->Article->published(array(id,
$expected = array(
array(Article => array(id => 1, title
array(Article => array(id => 2, title
array(Article => array(id => 3, title
);
title));
=> First Article)),
=> Second Article)),
=> Third Article))
$this->assertEquals($expected, $result);
}
}
You can see we have added a method called testPublished(). We start by creating an instance of
our 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 initially populated to the
article table.) We test that the result equals our expectation by using the assertEquals method. See the
Running Tests section for more information on how to run your test case.
Note:
When
setting
up
your
Model
for
testing
be
sure
to
use
ClassRegistry::init(YourModelName); as it knows to use your test database connection.
894
->method(send)
->will($this->returnValue(true));
$model->verifyEmail([email protected]);
}
Testing Controllers
While you can test controller classes in a similar fashion to Helpers, Models, and Components, CakePHP offers a specialized ControllerTestCase class. Using this class as the base class for your controller test
cases allows you to use testAction() for simpler test cases. ControllerTestCase allows you to
easily mock out components and models, as well as potentially difficult to test methods like redirect().
Say you have a typical Articles controller, and its corresponding model. The controller code looks like:
App::uses(AppController, Controller);
class ArticlesController extends AppController {
public $helpers = array(Form, Html);
public function index($short = null) {
if (!empty($this->request->data)) {
$this->Article->save($this->request->data);
}
if (!empty($short)) {
$result = $this->Article->find(all, array(id, title));
} else {
$result = $this->Article->find(all);
}
if (isset($this->params[requested])) {
return $result;
}
$this->set(title, Articles);
$this->set(articles, $result);
}
}
Create a file named ArticlesControllerTest.php in your app/Test/Case/Controller directory and put the following inside:
class ArticlesControllerTest extends ControllerTestCase {
public $fixtures = array(app.article);
public function testIndex() {
$result = $this->testAction(/articles/index);
debug($result);
}
Testing
895
This example shows a few of the ways you can use testAction to test your controllers. The first parameter
of testAction should always be the URL you want to test. CakePHP will create a request and dispatch
the controller and action.
When testing actions that contain redirect() and other code following the redirect it is generally a good
idea to return when redirecting. The reason for this, is that redirect() is mocked in testing, and does
not exit like normal. And instead of your code exiting, it will continue to run code following the redirect.
For example:
App::uses(AppController, Controller);
class ArticlesController extends AppController {
public function add() {
if ($this->request->is(post)) {
896
if ($this->Article->save($this->request->data)) {
$this->redirect(array(action => index));
}
}
// more code
}
}
When testing the above code, you will still run // more code even when the redirect is reached. Instead,
you should write the code like:
App::uses(AppController, Controller);
class ArticlesController extends AppController {
public function add() {
if ($this->request->is(post)) {
if ($this->Article->save($this->request->data)) {
return $this->redirect(array(action => index));
}
}
// more code
}
}
In this case // more code will not be executed as the method will return once the redirect is reached.
Simulating GET requests
As seen in the testIndexPostData() example above, you can use testAction() to test POST
actions as well as GET actions. By supplying the data key, the request made to the controller will be
POST. By default all requests will be POST requests. You can simulate a GET request by setting the method
key:
public function testAdding() {
$data = array(
Post => array(
title => New post,
body => Secret sauce
)
);
$this->testAction(/posts/add, array(data => $data, method => get));
// some assertions.
}
The data key will be used as query string parameters when simulating a GET request.
Choosing the return type
You can choose from a number of ways to inspect the success of your controller action. Each offers a
different way to ensure your code is doing what you expect:
Testing
897
The above would create a mocked PostsController, stubbing out the isAuthorized method. The
attached Post model will have save() stubbed, and the attached components would have their respective
methods stubbed. You can choose to stub an entire class by not passing methods to it, like Session in the
example above.
Generated controllers are automatically used as the testing controller to test. To enable automatic generation,
set the autoMock variable on the test case to true. If autoMock is false, your original controller will be
used in the test.
The response object in the generated controller is always replaced with a mock that does not send
headers. After using generate() or testAction() you can access the controller object at
$this->controller.
898
This example shows a slightly more complex use of the testAction() and generate() methods.
First, we generate a testing controller and mock the SessionComponent. Now that the SessionComponent is mocked, we have the ability to run testing methods on it. Assuming PostsController::add()
redirects us to index, sends an email and sets a flash message, the test will pass. A second test was added to
do basic sanity testing when fetching the add form. We check to see if the layout was loaded by checking
the entire rendered contents, and checks the view for a form tag. As you can see, your freedom to test
controllers and easily mock its classes is greatly expanded with these changes.
When doing controller tests using mocks that use static methods youll have to use a different method to
register your mock expectations. For example if you wanted to mock out AuthComponent::user()
youd have to do the following:
Testing
899
By using staticExpects you will be able to mock and manipulate static methods on components and
models.
Testing a JSON Responding Controller
JSON is a very friendly and common format to use when building a web service. Testing the endpoints of
your web service is very simple with CakePHP. Let us begin with a simple example controller that responds
in JSON:
class MarkersController extends AppController {
public $autoRender = false;
public function index() {
$data = $this->Marker->find(first);
$this->response->body(json_encode($data));
}
}
Testing Views
Generally most applications will not directly test their HTML code. Doing so is often results in fragile, difficult to maintain test suites that are prone to breaking. When writing functional tests using
ControllerTestCase you can inspect the rendered view content by setting the return option to
900
view. While it is possible to test view content using ControllerTestCase, more robust and maintainable
integration/view testing can be accomplished using tools like Selenium webdriver7 .
Testing Components
Lets pretend we have a component called PagematronComponent in our application. This component helps
us set the pagination limit value across all the controllers that use it. Here is our example component located
in app/Controller/Component/PagematronComponent.php:
class PagematronComponent extends Component {
public $Controller = null;
public function startup(Controller $controller) {
parent::startup($controller);
$this->Controller = $controller;
// Make sure the controller is using pagination
if (!isset($this->Controller->paginate)) {
$this->Controller->paginate = array();
}
}
public function adjust($length = short) {
switch ($length) {
case long:
$this->Controller->paginate[limit] = 100;
break;
case medium:
$this->Controller->paginate[limit] = 50;
break;
default:
$this->Controller->paginate[limit] = 20;
break;
}
}
}
Now we can write tests to ensure our paginate limit parameter is being
set correctly by the adjust method in our component.
We create the file
app/Test/Case/Controller/Component/PagematronComponentTest.php:
App::uses(Controller, Controller);
App::uses(CakeRequest, Network);
App::uses(CakeResponse, Network);
App::uses(ComponentCollection, Controller);
App::uses(PagematronComponent, Controller/Component);
// A fake controller to test against
class PagematronControllerTest extends Controller {
public $paginate = null;
}
7
http://seleniumhq.org
Testing
901
Testing Helpers
Since a decent amount of logic resides in Helper classes, its important to make sure those classes are covered
by test cases.
First we create an example helper to test. The CurrencyRendererHelper will help us display currencies in our views and for simplicity only has one method usd().
// app/View/Helper/CurrencyRendererHelper.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.
902
Here, we call usd() with different parameters and tell the test suite to check if the returned values are equal
to what is expected.
Save this in and execute the test. You should see a green bar and messaging indicating 1 pass and 4 assertions.
Testing
903
}
}
The code above will group all test cases found in the /app/Test/Case/Model/ folder. To add an
individual file, use $suite->addTestFile($filename);. You can recursively add a directory for
all tests using:
$suite->addTestDirectoryRecursive(TESTS . Case/Model);
Would recursively add all test cases in the app/Test/Case/Model directory. You can use test suites to
build a suite that runs all your applications tests:
class AllTestsTest extends CakeTestSuite {
public static function suite() {
$suite = new CakeTestSuite(All tests);
$suite->addTestDirectoryRecursive(TESTS . Case);
return $suite;
}
}
You can then run this test on the command line using:
$ Console/cake test app AllTests
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 BlogPost model from the plugins chapter of
this manual. A difference from other tests is in the first line where Blog.BlogPost is imported. You also
need to prefix your plugin fixtures with plugin.blog.blog_post:
App::uses(BlogPost, Blog.Model);
class BlogPostTest extends CakeTestCase {
// Plugin fixtures located in /app/Plugin/Blog/Test/Fixture/
public $fixtures = array(plugin.blog.blog_post);
public $BlogPost;
public function testSomething() {
// ClassRegistry makes the model use the test database connection
$this->BlogPost = ClassRegistry::init(Blog.BlogPost);
904
If you want to use plugin fixtures in the app tests you can
plugin.pluginName.fixtureName syntax in the $fixtures array.
reference
them
using
This ensures that youll always have the correct database configuration that Jenkins requires. Do the same
for any other configuration files you need to. Its often a good idea to drop and re-create the database before
each build as well. This insulates you from chained failures, where one broken build causes others to fail.
Add another shell script step to the build that contains the following:
8
9
http://jenkins-ci.org
http://jenkins-ci.org/
Testing
905
If you use clover coverage, or the junit results, make sure to configure those in Jenkins as well. Failing to
configure those steps will mean you wont see the results.
Run a build
You should be able to run a build now. Check the console output and make any necessary changes to get a
passing build.
REST
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 youve created in your
application. Its simple, usually XML-based (were talking simple XML, nothing like a SOAP envelope),
and depends on HTTP headers for direction. Exposing an API via REST in CakePHP is simple.
The first line sets up a number of default routes for easy REST access where method specifies the desired
result format (e.g. xml, json, rss). These routes are HTTP Request Method sensitive.
906
HTTP format
GET
GET
POST
PUT
DELETE
POST
URL.format
/recipes.format
/recipes/123.format
/recipes.format
/recipes/123.format
/recipes/123.format
/recipes/123.format
CakePHPs Router class uses a number of different indicators to detect the HTTP method being used. Here
they are in order of preference:
1. The _method POST variable
2. The X_HTTP_METHOD_OVERRIDE
3. The REQUEST_METHOD header
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:
// Controller/RecipesController.php
class RecipesController extends AppController {
public $components = array(RequestHandler);
public function index() {
$recipes = $this->Recipe->find(all);
$this->set(array(
recipes => $recipes,
_serialize => array(recipes)
));
}
public function view($id) {
$recipe = $this->Recipe->findById($id);
$this->set(array(
recipe => $recipe,
_serialize => array(recipe)
));
}
public function add() {
$this->Recipe->create();
if ($this->Recipe->save($this->request->data)) {
$message = Saved;
} else {
$message = Error;
}
$this->set(array(
message => $message,
_serialize => array(message)
));
REST
907
}
public function edit($id) {
$this->Recipe->id = $id;
if ($this->Recipe->save($this->request->data)) {
$message = Saved;
} else {
$message = Error;
}
$this->set(array(
message => $message,
_serialize => array(message)
));
}
public function delete($id) {
if ($this->Recipe->delete($id)) {
$message = Deleted;
} else {
$message = Error;
}
$this->set(array(
message => $message,
_serialize => array(message)
));
}
}
Since weve added a call to Router::parseExtensions(), the CakePHP router is already primed to
serve up different views based on different kinds of requests. Since were dealing with REST requests, well
be making XML views. You can also easily make JSON views using CakePHPs built-in JSON and XML
views. By using the built in XmlView we can define a _serialize view variable. This special view
variable is used to define which view variables XmlView should serialize into XML.
If we wanted to modify the data before it is converted into XML we should not define the _serialize
view variable, and instead use view files. We place the REST views for our RecipesController inside
app/View/recipes/xml. We can also use the Xml for quick-and-easy XML output in those views.
Heres what our index view might look like:
// app/View/Recipes/xml/index.ctp
// Do some formatting and manipulation on
// the $recipes array.
$xml = Xml::fromArray(array(response => $recipes));
echo $xml->asXML();
When serving up a specific content type using parseExtensions(), CakePHP automatically looks for a view
helper that matches the type. Since were using XML as the content type, there is no built-in helper, however
if you were to create one it would automatically be loaded for our use in those views.
The rendered XML will end up looking something like this:
<recipes>
<recipe id="234" created="2008-06-13" modified="2008-06-14">
908
Creating the logic for the edit action is a bit trickier, but not by much. Since youre providing an API
that outputs XML, its a natural choice to receive XML as input. Not to worry, the RequestHandler
and Router classes make things much easier. If a POST or PUT request has an XML content-type,
then the input is run through CakePHPs Xml class, and the array representation of the data is assigned
to $this->request->data. Because of this feature, handling XML and POST data in parallel is seamless: no changes are required to the controller or model code. Everything you need should end up in
$this->request->data.
By overwriting the default resource map, future calls to mapResources() will use the new values.
REST
909
Dispatcher Filters
New in version 2.2.
There are several reasons to want a piece of code to be run before any controller code is executed or right
before the response is sent to the client, such as response caching, header tuning, special authentication or
just to provide access to a mission-critical API response in lesser time than a complete request dispatching
cycle would take.
CakePHP provides for such cases a clean and extensible interface for attaching filters to this dispatching
cycle, similar to a middleware layer thought to provide stackable services or routines for every request. We
call them Dispatcher Filters
Configuring Filters
Filters are usually configured in the bootstrap.php file, but you could easily load them from any other
configuration file before the request is dispatched. Adding and removing filters is done through the Configure
class, using the special key Dispatcher.filters. By default CakePHP comes with a couple filter
classes already enabled for all requests, lets take a look at how they are added:
Configure::write(Dispatcher.filters, array(
AssetDispatcher,
CacheDispatcher
));
Each of those array values are class names that will be instantiated and added as listeners for the events
generated at dispatcher level. The first one, AssetDispatcher is meant to check whether the request
is referring to a theme or plugin asset file, such as a CSS, JavaScript or image stored on either a plugins
webroot folder or the corresponding one for a Theme. It will serve the file accordingly if found, stopping
the rest of the dispatching cycle. The CacheDispatcher filter, when Cache.check config variable is
910
enabled, will check if the response was already cached in the file system for a similar request and serve the
cached code immediately.
As you can see, both provided filters have the responsibility of stopping any further code and send the
response right away to the client. But filters are not limited to this role, as we will show shortly in this
section.
You can add your own class names to the list of filters, and they will get executed in the order they
were defined. There is also an alternative way for attaching filters that do not involve the special
DispatcherFilter classes:
Configure::write(Dispatcher.filters, array(
my-filter => array(
callable => array($classInstance, methodName),
on => after
)
));
As shown above, you can pass any valid PHP callback10 type, as you may remember, a callback is anything
that PHP can execute with call_user_func. We do make a little exception, if a string is provided it will
be treated as a class name, not as a possible function name. This of course gives the ability to PHP 5.3 users
to attach anonymous functions as filters:
Configure::write(Dispatcher.filters, array(
my-filter => array(callable => function($event) {...}, on => before),
//more filters here
));
The on key only takes before and after as valid values, and evidently means whether the filter should
run before or after any controller code is executed. Additionally to defining filters with the callable key,
you also get the chance to define a priority for your filters, if none is specified then a default of 10 is selected
for you
As all filters will have default priority 10, should you want to run a filter before any other in the list, select
lower priority numbers as needed:
Configure::write(Dispatcher.filters, array(
my-filter => array(
callable => function($event) {...},
on => before,
priority => 5
),
other-filter => array(
callable => array($class, method),
on => after,
priority => 1
),
//more filters here
));
Obviously, when defining priorities the order in which filters are declared does not matter but for those
having the same. When defining filters as class names there is no option to define priority in-line, we will
10
http://php.net/callback
Dispatcher Filters
911
get into that soon. Finally, CakePHPs plugin notation can be used to define filters located in plugins:
Configure::write(Dispatcher.filters, array(
MyPlugin.MyFilter,
));
Feel free to remove the default attached filters if you choose to use a more advanced/faster way of serving
theme and plugin assets or if you do not wish to use built-in full page caching, or just implement your own.
If you need to pass constructor parameters or settings to you dispatch filter classes you can do that by
providing an array of settings:
Configure::write(Dispatcher.filters, array(
MyAssetFilter => array(service => google.com)
));
When the filter key is a valid classname, the value can be an array of parameters that are passed to the
dispatch filter. By default the base class will assign these settings to the $settings property after merging
them with the defaults in the class.
Changed in version 2.5: You can now provide constructor settings to dispatch filters in 2.5.
Filter Classes
Dispatcher filters, when defined as class names in configuration, should extend the class
DispatcherFilter provided in the Routing CakePHPs directory. Lets create a simple filter to respond to a specific URL with a Hello World text:
App::uses(DispatcherFilter, Routing);
class HelloWorldFilter extends DispatcherFilter {
public $priority = 9;
public function beforeDispatch(CakeEvent $event) {
$request = $event->data[request];
$response = $event->data[response];
if ($request->url === hello-world) {
$response->body(Hello World);
$event->stopPropagation();
return $response;
}
}
}
This class should be saved in a file in app/Routing/Filter/HelloWorldFilter.php and configured in the bootstrap file according to how it was explained in the previous section. There is plenty to
explain here, lets begin with the $priority value.
As mentioned before, when using filter classes you can only define the order in which they are run using
the $priority property in the class, default value is 10 if the property is declared, this means that it will
get executed _after_ the Router class has parsed the request. We do not want this to happen in our previous
912
example, because most probably you do not have any controller set up for answering to that URL, hence we
chose 9 as our priority.
DispatcherFilter exposes two methods that can be overridden in subclasses, they are
beforeDispatch and afterDispatch, and are executed before or after any controller is executed
respectively. Both methods receive a CakeEvent object containing the request and response
objects (CakeRequest and CakeResponse instances) along with an additionalParams array
inside the data property. The latter contains information used for internal dispatching when calling
requestAction.
In our example we conditionally returned the $response object as a result, this will tell the Dispatcher to
not instantiate any controller and return such object as response immediately to the client. We also added
$event->stopPropagation() to prevent other filters from being executed after this one.
Lets now create another filter for altering response headers in any public page, in our case it would be
anything served from the PagesController:
App::uses(DispatcherFilter, Routing);
class HttpCacheFilter extends DispatcherFilter {
public function afterDispatch(CakeEvent $event) {
$request = $event->data[request];
$response = $event->data[response];
if ($request->params[controller] !== pages) {
return;
}
if ($response->statusCode() === 200) {
$response->sharable(true);
$response->expires(strtotime(+1 day));
}
}
}
This filter will send a expiration header to 1 day in the future for all responses produced by the pages
controller. You could of course do the same in the controller, this is just an example of what could be done
with filters. For instance, instead of altering the response you could cache it using the Cache class and
serve the response from the beforeDispatch callback.
Inline Filters
Our last example will use an anonymous function (only available on PHP 5.3+) to serve a list of posts in
JSON format, we encourage you to do so using controllers and the JsonView class, but lets imagine you
need to save a few milliseconds for this mission-critical API endpoint:
$postsList = function($event) {
if ($event->data[request]->url !== posts/recent.json) {
return;
}
App::uses(ClassRegistry, Utility);
$postModel = ClassRegistry::init(Post);
$event->data[response]->body(json_encode($postModel->find(recent)));
Dispatcher Filters
913
$event->stopPropagation();
return $event->data[response];
};
Configure::write(Dispatcher.filters, array(
AssetDispatcher,
CacheDispatcher,
recent-posts => array(
callable => $postsList,
priority => 9,
on=> before
)
));
In previous example we have selected a priority of 9 for our filter, so to skip any other logic either placed in
custom or core filters such as CakePHP internal routing system. Although it is not required, it shows how to
make your important code run first in case you need to trim as much fat as possible from some requests.
For obvious reasons this has the potential of making your app very difficult to maintain. Filters are an
extremely powerful tool when used wisely, adding response handlers for each URL in your app is not a
good use for it. But if you got a valid reason to do so, then you have a clean solution at hand. Keep in mind
that not everything needs to be a filter, Controllers and Components are usually a more accurate choice for
adding any request handling code to your app.
914
CHAPTER 11
Deployment
Once your application is complete, or even before that youll want to deploy it. There are a few things you
should do when deploying a CakePHP application.
Update core.php
Updating core.php, specifically the value of debug is extremely important. Turning debug = 0 disables
a number of development features that should never be exposed to the Internet at large. Disabling debug
changes the following types of things:
Debug messages, created with pr() and debug() are disabled.
915
Core CakePHP caches are by default flushed every 999 days, instead of every 10 seconds as in development.
Error views are less informative, and give generic error messages instead.
Errors are not displayed.
Exception stack traces are disabled.
In addition to the above, many plugins and application extensions use debug to modify their behavior.
You can check against an environment variable to set the debug level dynamically between environments.
This will avoid deploying an application with debug > 0 and also save yourself from having to change the
debug level each time before deploying to a production environment.
For example, you can set an environment variable in your Apache configuration:
SetEnv CAKEPHP_DEBUG 2
And then you can set the debug level dynamically in core.php:
if (getenv(CAKEPHP_DEBUG)) {
Configure::write(debug, 2);
} else {
Configure::write(debug, 0);
}
916
CHAPTER 12
We have adhered to the CakePHP conventions in naming tables, but were also taking advantage of another
convention: by using the username and password columns in a users table, CakePHP will be able to auto
configure most things for us when implementing the user login.
Next step is to create our User model, responsible for finding, saving and validating any user data:
// app/Model/User.php
App::uses(AppModel, Model);
class User extends AppModel {
public $validate = array(
username => array(
required => array(
917
Lets also create our UsersController, the following contents correspond to a basic baked UsersController
class using the code generation utilities bundled with CakePHP:
// app/Controller/UsersController.php
App::uses(AppController, Controller);
class UsersController extends AppController {
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow(add);
}
public function index() {
$this->User->recursive = 0;
$this->set(users, $this->paginate());
}
public function view($id = null) {
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__(Invalid user));
}
$this->set(user, $this->User->read(null, $id));
}
public function add() {
if ($this->request->is(post)) {
$this->User->create();
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__(The user has been saved));
return $this->redirect(array(action => index));
}
918
$this->Session->setFlash(
__(The user could not be saved. Please, try again.)
);
}
}
public function edit($id = null) {
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__(Invalid user));
}
if ($this->request->is(post) || $this->request->is(put)) {
if ($this->User->save($this->request->data)) {
$this->Session->setFlash(__(The user has been saved));
return $this->redirect(array(action => index));
}
$this->Session->setFlash(
__(The user could not be saved. Please, try again.)
);
} else {
$this->request->data = $this->User->read(null, $id);
unset($this->request->data[User][password]);
}
}
public function delete($id = null) {
// Prior to 2.5 use
// $this->request->onlyAllow(post);
$this->request->allowMethod(post);
$this->User->id = $id;
if (!$this->User->exists()) {
throw new NotFoundException(__(Invalid user));
}
if ($this->User->delete()) {
$this->Session->setFlash(__(User deleted));
return $this->redirect(array(action => index));
}
$this->Session->setFlash(__(User was not deleted));
return $this->redirect(array(action => index));
}
}
919
920
There is not much to configure, as we used the conventions for the users table. We just set up the URLs that
will be loaded after the login and logout actions is performed, in our case to /posts/ and / respectively.
What we did in the beforeFilter function was to tell the AuthComponent to not require a login for all
index and view actions, in every controller. We want our visitors to be able to read and list the entries
without registering in the site.
Now, we need to be able to register new users, save their username and password, and, more importantly,
hash their password so it is not stored as plain text in our database. Lets tell the AuthComponent to let
un-authenticated users access the users add function and implement the login and logout action:
// app/Controller/UsersController.php
public function beforeFilter() {
parent::beforeFilter();
// Allow users to register and logout.
$this->Auth->allow(add, logout);
}
public function login() {
if ($this->request->is(post)) {
if ($this->Auth->login()) {
return $this->redirect($this->Auth->redirectUrl());
}
$this->Session->setFlash(__(Invalid username or password, try again));
}
}
public function logout() {
return $this->redirect($this->Auth->logout());
}
Password hashing is not done yet, open your app/Model/User.php model file and add the following:
// app/Model/User.php
App::uses(AppModel, Model);
App::uses(BlowfishPasswordHasher, Controller/Component/Auth);
class User extends AppModel {
// ...
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias][password])) {
$passwordHasher = new BlowfishPasswordHasher();
$this->data[$this->alias][password] = $passwordHasher->hash(
$this->data[$this->alias][password]
);
}
return true;
}
// ...
921
Note: The BlowfishPasswordHasher uses a stronger hashing algorithm (bcrypt) than SimplePasswordHasher (sha1) and provides per user salts. The SimplePasswordHasher will be removed as of CakePHP
version 3.0
So, now every time a user is saved, the password is hashed using the BlowfishPasswordHasher class. Were
just missing a template view file for the login function. Open up your app/View/Users/login.ctp
file and add the following lines:
//app/View/Users/login.ctp
<div class="users form">
<?php echo $this->Session->flash(auth); ?>
<?php echo $this->Form->create(User); ?>
<fieldset>
<legend>
<?php echo __(Please enter your username and password); ?>
</legend>
<?php echo $this->Form->input(username);
echo $this->Form->input(password);
?>
</fieldset>
<?php echo $this->Form->end(__(Login)); ?>
</div>
You can now register a new user by accessing the /users/add URL and log-in with the newly created
credentials by going to /users/login URL. Also try to access any other URL that was not explicitly
allowed such as /posts/add, you will see that the application automatically redirects you to the login
page.
And thats it! It looks too simple to be truth. Lets go back a bit to explain what happened. The
beforeFilter function is telling the AuthComponent to not require a login for the add action in addition to the index and view actions that were already allowed in the AppControllers beforeFilter
function.
The login action calls the $this->Auth->login() function in the AuthComponent, and it works
without any further config because we are following conventions as mentioned earlier. That is, having a
User model with a username and a password column, and use a form posted to a controller with the user
data. This function returns whether the login was successful or not, and in the case it succeeds, then we
redirect the user to the configured redirection URL that we used when adding the AuthComponent to our
application.
The logout works by just accessing the /users/logout URL and will redirect the user to the configured
logoutUrl formerly described. This URL is the result of the AuthComponent::logout() function on
success.
Also, a small change in the PostsController is required to store the currently logged in user as a reference
for the created post:
// app/Controller/PostsController.php
public function add() {
if ($this->request->is(post)) {
//Added this line
$this->request->data[Post][user_id] = $this->Auth->user(id);
if ($this->Post->save($this->request->data)) {
$this->Session->setFlash(__(Your post has been saved.));
return $this->redirect(array(action => index));
}
}
}
The user() function provided by the component returns any column from the currently logged in user.
We used this method to add the data into the request info that is saved.
Lets secure our app to prevent some authors from editing or deleting the others posts. Basic rules for our
app are that admin users can access every URL, while normal users (the author role) can only access the
permitted actions. Open again the AppController class and add a few more options to the Auth config:
// app/Controller/AppController.php
public $components = array(
Session,
Auth => array(
loginRedirect => array(controller => posts, action => index),
logoutRedirect => array(
controller => pages,
action => display,
home
),
authenticate => array(
Form => array(
passwordHasher => Blowfish
)
),
authorize => array(Controller) // Added this line
)
);
public function isAuthorized($user) {
// Admin can access every action
if (isset($user[role]) && $user[role] === admin) {
return true;
}
// Default deny
return false;
}
923
We just created a very simple authorization mechanism. In this case the users with role admin will be able
to access any URL in the site when logged in, but the rest of them (i.e the role author) cant do anything
different from not logged in users.
This is not exactly what we wanted, so we need to supply more rules to our isAuthorized() method.
But instead of doing it in AppController, lets delegate each controller to supply those extra rules. The rules
were going to add to PostsController should allow authors to create posts but prevent the edition of posts if
the author does not match. Open the file PostsController.php and add the following content:
// app/Controller/PostsController.php
public function isAuthorized($user) {
// All registered users can add posts
if ($this->action === add) {
return true;
}
// The owner of a post can edit and delete it
if (in_array($this->action, array(edit, delete))) {
$postId = (int) $this->request->params[pass][0];
if ($this->Post->isOwnedBy($postId, $user[id])) {
return true;
}
}
return parent::isAuthorized($user);
}
Were now overriding the AppControllers isAuthorized() call and internally checking if the parent
class is already authorizing the user. If he isnt, then just allow him to access the add action, and conditionally
access edit and delete. A final thing is left to be implemented, to tell whether the user is authorized to edit
the post or not, were calling a isOwnedBy() function in the Post model. It is in general a good practice
to move as much logic as possible into models. Lets then implement the function:
// app/Model/Post.php
public function isOwnedBy($post, $user) {
return $this->field(id, array(id => $post, user_id => $user)) !== false;
}
This concludes our simple authentication and authorization tutorial. For securing the UsersController you
can follow the same technique we did for PostsController. You could also be more creative and code something more general in AppController based on your own rules.
Should you need more control, we suggest you read the complete Auth guide in the Authentication section
where you will find more about configuring the component, creating custom Authorization classes, and
much more.
924
925
926
CHAPTER 13
Note: This isnt a beginner level tutorial. If you are just starting out with CakePHP we would advise you
to get a better overall experience of the frameworks features before trying out this tutorial.
In this tutorial you will create a simple application with Authentication and Access Control Lists. This
tutorial assumes you have read the /tutorials-and-examples/blog/blog tutorial, and you are
familiar with Code Generation with Bake. You should have some experience with CakePHP, and be familiar
with MVC concepts. This tutorial is a brief introduction to the AuthComponent and AclComponent.
What you will need
1. A running web server. Were going to assume youre using Apache, though the instructions for using
other servers should be very similar. We might have to play a little with the server configuration, but
most folks can get CakePHP up and running without any configuration at all.
2. A database server. Were going to be using MySQL in this tutorial. Youll need to know enough about
SQL in order to create a database: CakePHP will be taking the reins from there.
3. Basic PHP knowledge. The more object-oriented programming youve done, the better: but fear not
if youre a procedural fan.
http://git-scm.com/
927
Once youve got a fresh copy of CakePHP 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:
CREATE TABLE users (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
password CHAR(40) NOT NULL,
group_id INT(11) NOT NULL,
created DATETIME,
modified DATETIME
);
CREATE TABLE groups (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
created DATETIME,
modified DATETIME
);
CREATE TABLE posts (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id INT(11) NOT NULL,
title VARCHAR(255) NOT NULL,
body TEXT,
created DATETIME,
modified DATETIME
);
CREATE TABLE widgets (
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
part_no VARCHAR(12),
quantity INT(11)
);
These are the tables we will be using to build the rest of our application. Once we have the table structure
in the database we can start cooking. Use Code Generation with Bake to quickly create your models,
controllers, and views.
To use cake bake, call cake bake all and this will list the 4 tables you inserted into MySQL. Select 1.
Group, and follow the prompts. Repeat for the other 3 tables, and this will have generated the 4 controllers,
models and your views for you.
Avoid using Scaffold here. The generation of the ACOs will be seriously affected if you bake the controllers
with the Scaffold feature.
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 youll 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. Well be doing
that soon enough. You should now have models, controllers, and baked views for your users, groups, posts
928
and widgets.
Next well have to update our User model to hash passwords before they go into the database. Storing
plaintext passwords is extremely insecure and AuthComponent will expect that your passwords are hashed.
In app/Model/User.php add the following:
App::uses(AuthComponent, Controller/Component);
class User extends AppModel {
// other code.
public function beforeSave($options = array()) {
$this->data[User][password] = AuthComponent::password(
$this->data[User][password]
);
return true;
}
}
929
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 your UsersController Add the following:
public function beforeFilter() {
parent::beforeFilter();
// For CakePHP 2.0
$this->Auth->allow(*);
// For CakePHP 2.1 and up
$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. Dont add any users or groups just yet
though.
930
This schema will prompt you to drop and create the tables. Say yes to dropping and creating the tables.
If you dont have shell access, or are having trouble using the console, you can run the sql file found in
/path/to/app/Config/Schema/db_acl.sql.
With the controllers setup for data entry, and the Acl tables initialized we are ready to go right? Not entirely,
we still have a bit of work to do in the user and group models. Namely, making them auto-magically attach
to the Acl.
Acts As a Requester
For Auth and Acl to work properly we need to associate our users and groups to rows in the Acl tables. In
order to do this we will use the AclBehavior. The AclBehavior allows for the automagic connection
of models with the Acl tables. Its use requires an implementation of parentNode() on your model. In
our User model we will add the following:
class User extends AppModel {
public $belongsTo = array(Group);
public $actsAs = array(Acl => array(type => requester));
public function parentNode() {
if (!$this->id && empty($this->data)) {
return null;
}
if (isset($this->data[User][group_id])) {
$groupId = $this->data[User][group_id];
} else {
$groupId = $this->field(group_id);
}
if (!$groupId) {
return null;
}
return array(Group => array(id => $groupId));
}
}
931
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 prepared 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 dont 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.
Group-only ACL
In case we want simplified per-group only permissions, we need to implement bindNode() in User
model:
public function bindNode($user) {
return array(model => Group, foreign_key => $user[User][group_id]);
}
Then modify the actsAs for the model User and disable the requester directive:
public $actsAs = array(Acl => array(type => requester, enabled => false));
These two changes will tell ACL to skip checking User Aros and to check only Group Aros. This also
avoids the afterSave being called.
932
Note: Every user has to have group_id assigned for this to work.
Now the aros table will look like this:
+----+-----------+-------+-------------+-------+------+------+
| id | parent_id | model | foreign_key | alias | lft | rght |
+----+-----------+-------+-------------+-------+------+------+
| 1 |
NULL | Group |
1 | NULL |
1 |
2 |
| 2 |
NULL | Group |
2 | NULL |
3 |
4 |
| 3 |
NULL | Group |
3 | NULL |
5 |
6 |
+----+-----------+-------+-------------+-------+------+------+
3 rows in set (0.00 sec)
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
ensure that your $components array contains the actionPath defined earlier:
class AppController extends Controller {
public $components = array(
Acl,
Auth => array(
authorize => array(
Actions => array(actionPath => controllers)
)
),
Session
);
933
934
CHAPTER 14
You can get a complete guide for all available commands like this:
./Console/cake AclExtras.AclExtras -h
./Console/cake AclExtras.AclExtras aco_sync -h
Once populated your acos table proceed to create your application permissions.
Setting up permissions
Creating permissions much like creating ACOs has no magic solution, nor will I be providing one. To allow
AROs access to ACOs from the shell interface use the AclShell. For more information on how to use it
1
2
https://github.com/markstory/acl_extras/
https://github.com/markstory/acl_extras/zipball/master
935
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 1s and -1s. Once youve confirmed your permissions are set, remove the function:
public function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow(initDB); // We can remove this line after were finished
}
public function initDB() {
$group = $this->User->Group;
// Allow admins to everything
$group->id = 1;
$this->Acl->allow($group, controllers);
// allow managers to posts and widgets
$group->id = 2;
$this->Acl->deny($group, controllers);
$this->Acl->allow($group, controllers/Posts);
$this->Acl->allow($group, controllers/Widgets);
// allow users to only add and edit on posts and widgets
$group->id = 3;
$this->Acl->deny($group, controllers);
$this->Acl->allow($group, controllers/Posts/add);
$this->Acl->allow($group, controllers/Posts/edit);
$this->Acl->allow($group, controllers/Widgets/add);
$this->Acl->allow($group, controllers/Widgets/edit);
// allow basic users to log out
$this->Acl->allow($group, controllers/users/logout);
// we add an exit to avoid an ugly "missing views" error message
echo "all done";
exit;
}
We now have set up some basic access rules. Weve allowed administrators to everything. Managers can
access everything in posts and widgets. While users can only access add and edit in posts & widgets.
We had to get a reference of a Group model and modify its id to be able to specify the ARO we wanted,
this is due to how AclBehavior works. AclBehavior does not set the alias field in the aros table so
936
This removes the off switches we put in earlier on the users and groups controllers, and
gives public access on the index and view actions in posts and widgets controllers.
In
AppController::beforeFilter() add the following:
$this->Auth->allow(display);
This makes the display action public. This will keep our PagesController::display() public. This is important as often the default routing has this action as the home page for your application.
Logging in
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/View/Users/login.ctp if you havent done so already:
<h2>Login</h2>
<?php
echo $this->Form->create(User, array(
url => array(
controller => users,
action => login
)
));
echo $this->Form->input(User.username);
echo $this->Form->input(User.password);
echo $this->Form->end(Login);
?>
If a user is already logged in, redirect him by adding this to your UsersController:
public function login() {
if ($this->Session->read(Auth.User)) {
$this->Session->setFlash(You are logged in!);
return $this->redirect(/);
}
}
Logging in
937
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 $this->Session->flash(auth)
Logout
Now onto the logout.
Earlier we left this function blank, now is the time to fill it.
UsersController::logout() add the following:
In
$this->Session->setFlash(Good-Bye);
$this->redirect($this->Auth->logout());
This sets a Session flash message and logs out the User using Auths logout method. Auths 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.
All done
You should now have an application controlled by Auth and Acl. Users permissions are set at the group
level, but you can set them by user at the same time. You can also set permissions on a global and percontroller and per-action basis. Furthermore, you have a reusable block of code to easily expand your ACO
table as your app grows.
938
CHAPTER 15
Contributing
There are a number of ways you can contribute to CakePHP. The following sections cover the various ways
you can contribute to CakePHP:
Documentation
Contributing to the documentation is simple. The files are hosted on https://github.com/cakephp/docs. Feel
free to fork the repo, add your changes/improvements/translations and give back by issuing a pull request.
You can even edit the docs online with GitHub, without ever downloading the files the Improve this Doc
button on any given page will direct you to GitHubs online editor for that page.
Translations
Email the docs team (docs at cakephp dot org) or hop on IRC (#cakephp on freenode) 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 wont see
what has already been translated.
Feel free to dive right in if your chosen language already exists on the book.
Use Informal Form1 .
Translate both the content and the title at the same time.
Do compare to the English content before submitting a correction (if you correct something, but dont
integrate an upstream change your submission wont be accepted).
If you need to write an English term, wrap it in <em> tags. E.g. asdf asdf Controller asdf or asdf
asdf Kontroller (Controller) asfd as appropriate.
1
http://en.wikipedia.org/wiki/Register_(linguistics)
939
http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
http://en.wikipedia.org/wiki/ReStructuredText
940
Indented lists can also be created, by indenting sections and separating them with an empty line:
* First line
* Second line
* Going deeper
* Whoah
* Back to the first level.
Terms cannot be more than one line, but definitions can be multi-line and all lines should be indented
consistently.
Documentation
941
Links
There are several kinds of links, each with their own uses.
External links
:doc:
Other pages in the documentation can be linked to using the :doc: role. You can link to the specified
document using either an absolute or relative path reference. You should omit the .rst extension.
For example, if the reference :doc:form appears in the document core-helpers/html,
then the link references core-helpers/form. If the reference was :doc:/core-helpers,
it would always reference /core-helpers regardless of where it was used.
Cross referencing links
:ref:
You can cross reference any arbitrary title in any document using the :ref: role. Link label targets
must be unique across the entire documentation. When creating labels for class methods, its best to
use class-method as the format for your link label.
The most common use of labels is above a title. Example:
.. _label-name:
Section heading
--------------More content here.
Elsewhere you could reference the above section using :ref:label-name. The links text
would be the title that the link preceded. You can also provide custom link text using :ref:Link
text <label-name>.
Describing classes and their contents
The CakePHP documentation uses the phpdomain4 to provide custom directives for describing PHP objects
and constructs. Using these directives and roles is required to give proper indexing and cross referencing
features.
4
http://pypi.python.org/pypi/sphinxcontrib-phpdomain
942
Attributes, methods and constants dont need to be nested. They can also just follow the class declaration:
.. php:class:: MyClass
Text about the class
. .
php:method:: methodName()
Text about the method
See also:
php:method, php:attr, php:const
.. php:method:: name(signature)
Describe a class method, its arguments, return value, and exceptions:
.. php:method:: instanceMethod($one, $two)
:param string $one: The first parameter.
:param string $two: The second parameter.
:returns: An array of stuff.
:throws: InvalidArgumentException
Documentation
943
.. php:staticmethod:: ClassName::methodName(signature)
Describe a static method, its arguments, return value and exceptions, see php:method for options.
.. php:attr:: name
Describe an property/attribute on a class.
Cross Referencing
The following roles refer to php objects and links are generated if a matching directive is found:
:php:func:
Reference a PHP function.
:php:global:
Reference a global variable whose name has $ prefix.
:php:const:
Reference either a global constant, or a class constant. Class constants should be preceded by the
owning class:
DateTime has an :php:const:DateTime::ATOM constant.
:php:class:
Reference a class by name:
:php:class:ClassName
:php:meth:
Reference a method of a class. This role supports both kinds of methods:
:php:meth:DateTime::setDate
:php:meth:Classname::staticMethod
:php:attr:
Reference a property on an object:
:php:attr:ClassName::$propertyName
:php:exc:
Reference an exception.
Source code
Literal code blocks are created by ending a paragraph with ::. The literal block must be indented, and like
all paragraphs be separated by single lines:
This is a paragraph::
while ($i--) {
944
doStuff()
}
This is regular text again.
Literal text is not modified or formatted, save that one level of indentation is removed.
Notes and warnings
There are often times when you want to inform the reader of an important tip, special note or a potential
hazard. Admonitions in sphinx are used for just that. There are three kinds of admonitions.
.. tip:: Tips are used to document or re-iterate interesting or important information. The content of the directive should be written in complete sentences and include all appropriate punctuation.
.. note:: Notes are used to document an especially important piece of information. The content
of the directive should be written in complete sentences and include all appropriate punctuation.
.. warning:: Warnings are used to document potential stumbling blocks, or information pertaining to security. The content of the directive should be written in complete sentences and include
all appropriate punctuation.
All admonitions are made the same:
.. note::
Indented and preceded and followed by a blank line. Just like a paragraph.
This text is not part of the note.
Samples
Tickets
Getting feedback and help from the community in the form of tickets is an extremely important part of the
CakePHP development process. All of CakePHPs tickets are hosted on GitHub5 .
5
https://github.com/cakephp/cakephp/issues
Tickets
945
Reporting bugs
Well written bug reports are very helpful. There are a few steps to help create the best bug report possible:
Do search6 for a similar existing ticket, and ensure someone hasnt already reported your issue, or
that it hasnt already been fixed in the repository.
Do include detailed instructions on how to reproduce the bug. This could be in the form of test cases
or a code snippet that demonstrates the issue. Not having a way to reproduce an issue, means its less
likely to get fixed.
Do give as many details as possible about your environment: (OS, PHP version, CakePHP version).
Dont use the ticket system to ask support questions. Use the Google Group7 , the #cakephp IRC
channel or Stack Overflow <https://stackoverflow.com/questions/tagged/cakephp> for that.
Code
Patches and pull requests are a great way to contribute code back to CakePHP. Pull requests can be created
in GitHub, and are prefered over patch files in ticket comments.
Initial setup
Before working on patches for CakePHP, its a good idea to get your environment setup. Youll need the
following software:
Git
PHP 5.2.8 or greater
6
7
https://github.com/cakephp/cakephp/search?q=it+is+broken&ref=cmdform&type=Issues
http://groups.google.com/group/cake-php
946
Note: If you are new to Git, we highly recommend you to read the excellent and free ProGit8 book.
Get a clone of the CakePHP source code from GitHub:
If you dont have a GitHub9 account, create one.
Fork the CakePHP repository10 by clicking the Fork button.
After your fork is made, clone your fork to your local machine:
git clone [email protected]:YOURNAME/cakephp.git
Add the original CakePHP repository as a remote repository. Youll use this later to fetch changes from the
CakePHP repository. This will let you stay up to date with CakePHP:
cd cakephp
git remote add upstream git://github.com/cakephp/cakephp.git
Now that you have CakePHP setup you should be able to define a $test database connection, and run all
the tests.
Working on a patch
Each time you want to work on a bug, feature or enhancement create a topic branch.
The branch you create should be based on the version that your fix/enhancement is for. For example if you
are fixing a bug in 2.3 you would want to use the 2.3 branch as the base for your branch. If your change
is a bug fix for the current stable release, you should use the master branch. This makes merging your
changes in later much simpler:
# fixing a bug on 2.3
git fetch upstream
git checkout -b ticket-1234 upstream/2.3
Tip: Use a descriptive name for your branch, referencing the ticket or feature name is a good convention.
e.g. ticket-1234, feature-awesome
The above will create a local branch based on the upstream (CakePHP) 2.3 branch. Work on your fix, and
make as many commits as you need; but keep in mind the following:
Follow the Coding Standards.
8
http://git-scm.com/book/
http://github.com
10
http://github.com/cakephp/cakephp
9
Code
947
Add a test case to show the bug is fixed, or that the new feature works.
Keep your commits logical, and write good clear and concise commit messages.
checkout 2.3
fetch upstream
merge upstream/2.3
checkout <branch_name>
rebase 2.3
This will fetch + merge in any changes that have happened in CakePHP since you started. It will then rebase
- or replay your changes on top of the current code. You might encounter a conflict during the rebase. If
the rebase quits early you can see which files are conflicted/un-merged with git status. Resolve each
conflict, and then continue the rebase:
git add <filename> # do this for each conflicted file.
git rebase --continue
Check that all your tests continue to pass. Then push your branch to your fork:
git push origin <branch-name>
Once your branch is on GitHub, you can discuss it on the cakephp-core11 mailing list or submit a pull request
on GitHub.
Choosing where your changes will be merged into
When making pull requests you should make sure you select the correct base branch, as you cannot edit it
once the pull request is created.
If your change is a bugfix and doesnt introduce new functionality and only corrects existing behavior
that is present in the current release. Then choose master as your merge target.
If your change is a new feature or an addition to the framework, then you should choose the branch
with the next version number. For example if the current stable release is 2.2.2, the branch accepting
new features will be 2.3
If your change is a breaks existing functionality, or APIs then youll have to choose then next major
release. For example, if the current release is 2.2.2 then the next time existing behavior can be
broken will be in 3.0 so you should target that branch.
Note: Remember that all code you contribute to CakePHP will be licensed under the MIT License, and the
11
http://groups.google.com/group/cakephp-core
948
Cake Software Foundation12 will become the owner of any contributed code. Contributors should follow
the CakePHP Community Guidelines13 .
All bug fixes merged into a maintenance branch will also be merged into upcoming releases periodically by
the core team.
Coding Standards
CakePHP developers will use the following coding standards.
It is recommended that others developing CakeIngredients follow the same standards.
You can use the CakePHP Code Sniffer14 to check that your code follows required standards.
Language
All code and comments should be written in English.
Indentation
One tab will be used for indentation.
So, indentation should look like this:
// base level
// level 1
// level 2
// level 1
// base level
Or:
$booleanVariable = true;
$stringVariable = moose;
if ($booleanVariable) {
echo Boolean value is true;
if ($stringVariable === moose) {
echo We have encountered a moose;
}
}
12
http://cakefoundation.org/pages/about
http://community.cakephp.org/guidelines
14
https://github.com/cakephp/cakephp-codesniffer
13
Coding Standards
949
Line Length
It is recommended to keep lines at approximately 100 characters long for better code readability. Lines must
not be longer than 120 characters.
In short:
100 characters is the soft limit.
120 characters is the hard limit.
Control Structures
Control structures are for example if, for, foreach, while, switch etc. Below, an example with if:
if ((expr_1) || (expr_2)) {
// action_1;
} elseif (!(expr_3) && (expr_4)) {
// action_2;
} else {
// default_action;
}
In the control structures there should be 1 (one) space before the first parenthesis and 1 (one) space
between the last parenthesis and the opening bracket.
Always use curly brackets in control structures, even if they are not needed. They increase the readability of the code, and they give you fewer logical errors.
Opening curly brackets should be placed on the same line as the control structure. Closing curly
brackets should be placed on new lines, and they should have same indentation level as the control
structure. The statement included in curly brackets should begin on a new line, and code contained
within it should gain a new level of indentation.
Inline assignments should not be used inside of the control structures.
// wrong = no brackets, badly placed statement
if (expr) statement;
// wrong = no brackets
if (expr)
statement;
// good
if (expr) {
statement;
}
// wrong = inline assignment
if ($variable = Class::function()) {
statement;
}
950
// good
$variable = Class::function();
if ($variable) {
statement;
}
Ternary Operator
Ternary operators are permissible when the entire ternary operation fits on one line. Longer ternaries should
be split into if else statements. Ternary operators should not ever be nested. Optionally parentheses can
be used around the condition check of the ternary for clarity:
// Good, simple and readable
$variable = isset($options[variable]) ? $options[variable] : true;
View Files
In view files (.ctp files) developers should use keyword control structures. Keyword control structures are
easier to read in complex view files. Control structures can either be contained in a larger PHP block, or in
separate PHP tags:
<?php
if ($isAdmin):
echo <p>You are the admin user.</p>;
endif;
?>
<p>The following is also acceptable:</p>
<?php if ($isAdmin): ?>
<p>You are the admin user.</p>
<?php endif; ?>
Comparison
Always try to be as strict as possible. If a none strict test is deliberate it might be wise to comment it as such
to avoid confusing it for a mistake.
For testing if a variable is null, it is recommended to use a strict check:
if ($value === null) {
// ...
}
Coding Standards
951
// not recommended
if (null === $this->foo()) {
// ...
}
// recommended
if ($this->foo() === null) {
// ...
}
Function Calls
Functions should be called without space between functions name and starting bracket. There should be
one space between every parameter of a function call:
$var = foo($bar, $bar2, $bar3);
As you can see above there should be one space on both sides of equals sign (=).
Method Definition
Example of a method definition:
public function someFunction($arg1, $arg2 = ) {
if (expr) {
statement;
}
return $var;
}
Parameters with a default value, should be placed last in function definition. Try to make your functions
return something, at least true or false, so it can be determined whether the function call was successful:
public function connection($dns, $persistent = false) {
if (is_array($dns)) {
$dnsInfo = $dns;
} else {
$dnsInfo = BD::parseDNS($dns);
}
if (!($dnsInfo) || !($dnsInfo[phpType])) {
return $this->addError();
}
return true;
}
952
Typehinting
Arguments that expect objects or arrays can be typehinted:
/**
* Some method description.
*
* @param Model $Model The model to use.
* @param array $array Some array value.
* @param bool $boolean Some boolean value.
*/
public function foo(Model $Model, array $array, $boolean) {
}
Method Chaining
Method chaining should have multiple methods spread across separate lines, and indented with one tab:
$email->from([email protected])
->to([email protected])
->subject(A great message)
->send();
DocBlocks
All comment blocks, with the exception of the first block in a file, should always be preceded by a newline.
File Header DocBlock
All PHP files should contain a file header DocBlock, which should look like this:
<?php
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
Coding Standards
953
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @copyright
http://cakephp.org CakePHP(tm) Project
* @link
X.Y.Z
* @since
http://www.opensource.org/licenses/mit-license.php MIT License
* @license
*/
http://phpdoc.org
http://phpdoc.org/docs/latest/references/phpdoc/tags/copyright.html
17
http://phpdoc.org/docs/latest/references/phpdoc/tags/link.html
18
http://phpdoc.org/docs/latest/references/phpdoc/tags/since.html
19
http://phpdoc.org/docs/latest/references/phpdoc/tags/license.html
20
http://phpdoc.org
21
http://phpdoc.org/docs/latest/references/phpdoc/tags/deprecated.html
22
http://phpdoc.org/docs/latest/references/phpdoc/tags/internal.html
23
http://phpdoc.org/docs/latest/references/phpdoc/tags/link.html
16
954
@property24
@see25
@since26
@uses27
Property DocBlocks
Property DocBlocks should look like this:
/**
* @var string|null Description of property.
*
* @deprecated 3.0.0 Deprecated as of 2.5.0. Will be removed in 3.0.0. Use $_bla instead.
* @see Bar::$_bla
* @link http://book.cakephp.org/2.0/en/foo.html#properties
*/
protected $_bar = null;
http://phpdoc.org/docs/latest/references/phpdoc/tags/property.html
http://phpdoc.org/docs/latest/references/phpdoc/tags/see.html
26
http://phpdoc.org/docs/latest/references/phpdoc/tags/since.html
27
http://phpdoc.org/docs/latest/references/phpdoc/tags/uses.html
28
http://phpdoc.org
29
http://phpdoc.org/docs/latest/references/phpdoc/tags/deprecated.html
30
http://phpdoc.org/docs/latest/references/phpdoc/tags/internal.html
31
http://phpdoc.org/docs/latest/references/phpdoc/tags/link.html
32
http://phpdoc.org/docs/latest/references/phpdoc/tags/see.html
33
http://phpdoc.org/docs/latest/references/phpdoc/tags/since.html
34
http://phpdoc.org/docs/latest/references/phpdoc/tags/var.html
25
Coding Standards
955
Method and function DocBLocks may contain the following phpDocumentor35 tags:
@deprecated36 Using the @version <vector> <description> format, where version
and description are mandatory.
@internal37
@link38
@param39
@return40
@throws41
@see42
@since43
@uses44
Variable Types
Variable types for use in DocBlocks:
Type Description
mixed A variable with undefined (or multiple) type.
int Integer type variable (whole number).
35
http://phpdoc.org
http://phpdoc.org/docs/latest/references/phpdoc/tags/deprecated.html
37
http://phpdoc.org/docs/latest/references/phpdoc/tags/internal.html
38
http://phpdoc.org/docs/latest/references/phpdoc/tags/link.html
39
http://phpdoc.org/docs/latest/references/phpdoc/tags/param.html
40
http://phpdoc.org/docs/latest/references/phpdoc/tags/return.html
41
http://phpdoc.org/docs/latest/references/phpdoc/tags/throws.html
42
http://phpdoc.org/docs/latest/references/phpdoc/tags/see.html
43
http://phpdoc.org/docs/latest/references/phpdoc/tags/since.html
44
http://phpdoc.org/docs/latest/references/phpdoc/tags/uses.html
36
956
For more than two types it is usually best to just use mixed.
When returning the object itself, e.g. for chaining, one should use $this instead:
/**
* Foo function.
*
* @return $this
*/
public function foo() {
return $this;
}
Including Files
include, require, include_once and require_once do not have parentheses:
// wrong = parentheses
require_once(ClassFileName.php);
require_once ($class);
// good = no parentheses
require_once ClassFileName.php;
require_once $class;
When including files with classes or libraries, use only and always the require_once45 function.
PHP Tags
Always use long tags (<?php ?>) Instead of short tags (<?
45
?>).
http://php.net/require_once
Coding Standards
957
Naming Convention
Functions
Write all functions in camelBack:
function longFunctionName() {
}
Classes
Class names should be written in CamelCase, for example:
class ExampleClass {
}
Variables
Variable names should be as descriptive as possible, but also as short as possible. Normal variables should
start with a lowercase letter, and should be written in camelBack in case of multiple words. Variables
referencing objects should start with a capital letter, and in some way associate to the class the variable is an
object of. Example:
$user = John;
$users = array(John, Hans, Arne);
$Dispatcher = new Dispatcher();
Member Visibility
Use PHP5s private and protected keywords for methods and variables. Additionally, protected method or
variable names start with a single underscore (_). Example:
class A {
protected $_iAmAProtectedVariable;
protected function _iAmAProtectedMethod() {
/* ... */
}
}
Private methods or variable names start with double underscore (__). Example:
class A {
private $__iAmAPrivateVariable;
private function __iAmAPrivateMethod() {
/* ... */
958
}
}
Try to avoid private methods or variables, though, in favor of protected ones. The latter can be accessed
or modified by subclasses, whereas private ones prevent extension or re-use. Private visibility also makes
testing much more difficult.
Example Addresses
For all example URL and mail addresses use example.com, example.org and example.net, for example:
Email: [email protected]
WWW: http://www.example.com
FTP: ftp://ftp.example.com
The example.com domain name has been reserved for this (see RFC 260647 ) and is recommended for use
in documentation or as examples.
Files
File names which do not contain classes should be lowercased and underscored, for example:
long_file_name.php
Casting
For casting we use:
Type Description
(bool) Cast to boolean.
(int) Cast to integer.
(float) Cast to float.
(string) Cast to string.
(array) Cast to array.
(object) Cast to object.
Please use (int)$var instead of intval($var) and (float)$var instead of floatval($var)
when applicable.
46
47
[email protected]
http://tools.ietf.org/html/rfc2606.html
Coding Standards
959
Constants
Constants should be defined in capital letters:
define(CONSTANT, 1);
If a constant name consists of multiple words, they should be separated by an underscore character, for
example:
define(LONG_NAMED_CONSTANT, 2);
Migration Guides
For each major and minor release, the CakePHP team will provide a migration guide. These guides explain
the new features and any breaking changes that are in each release. They can be found in the Appendices
section of the cookbook.
Using CakePHP
If you are building your application with CakePHP, the following guidelines explain the stability you can
expect.
Interfaces
Outside of major releases, interfaces provided by CakePHP will not have any existing methods changed.
New methods may be added, but no existing methods will be changed.
48
http://semver.org/
960
Classes
Classes provided by CakePHP can be constructed and have their public methods and properties used by
application code and outside of major releases backwards compatibility is ensured.
Note: Some classes in CakePHP are marked with the @internal API doc tag. These classes are not
stable and do not have any backwards compatibility promises.
In minor releases, new methods may be added to classes, and existing methods may have new arguments
added. Any new arguments will have default values, but if youve overidden methods with a differing
signature you may see fatal errors. Methods that have new arguments added will be documented in the
migration guide for that release.
The following table outlines several use cases and what compatibility you can expect from CakePHP:
If you...
Typehint against the class
Create a new instance
Extend the class
Access a public property
Call a public method
Extend a class and...
Override a public property
Access a protected property
Override a protected property
Override a protected method
Call a protected method
Add a public property
Add a public method
Add an argument to an overridden
method
Add a default argument to an
existing method
Backwards compatibility?
Yes
Yes
Yes
Yes
Yes
Yes
No 49
No 1
No 1
No 1
No
No
No 1
Yes
Working on CakePHP
If you are helping make CakePHP even better please keep the following guidelines in mind when
adding/changing functionality:
In a minor release you can:
49
Your code may be broken by minor releases. Check the migration guide for details.
961
You can change a class/method name as long as the old name remains available. This is generally avoided unless renaming has
significant benefit.
51
Avoid whenever possible. Any removals need to be documented in the migration guide.
962
development team52 and discuss ideas, the latest code, and make general comments. If something more
formal needs to be proposed or there is a problem with a release, the ticket system is the best place to share
your thoughts.
We currently maintain 4 versions of CakePHP.
stable : Tagged releases intended for production where stability is more important than features.
Issues filed against these releases will be fixed in the related branch, and be part of the next release.
maintenance branch : Development branches become maintenance branches once a stable release
point has been reached. Maintenance branches are where all bugfixes are committed before making
their way into a stable release. Maintenance branches have the same name as the major version they
are for example 1.2. If you are using a stable release and need fixes that havent made their way into
a stable release check here.
development branches : Development branches contain leading edge fixes and features. They are
named after the version number they are for example 1.3. Once development branches have reached
a stable release point they become maintenance branches, and no further new features are introduced
unless absolutely necessary.
feature branches : Feature branches contain unfinished or possibly unstable features and are recommended only for power users interested in the most advanced feature set and willing to contribute
back to the community. Feature branches are named with the following convention version-feature.
An example would be 1.3-router Which would contain new features for the Router for 1.3.
Hopefully this will help you understand what version is right for you. Once you pick your version you may
feel compelled to contribute a bug report or make general comments on the code.
If you are using a stable version or maintenance branch, please submit tickets or discuss with us on
IRC.
If you are using the development branch or feature branch, the first place to go is IRC. If you have a
comment and cannot reach us in IRC after a day or two, please submit a ticket.
If you find an issue, the best answer is to write a test. The best advice we can offer in writing tests is to look
at the ones included in the core.
As always, if you have any questions or comments, visit us at #cakephp on irc.freenode.net.
52
https://github.com/cakephp?tab=members
963
964
CHAPTER 16
Appendices
Appendices contain information regarding the new features introduced in 2.x, and the migration path from
1.3 to 2.0.
965
Console
ConsoleOptionParser
overwrite() has been added to allow generating progress bars or to avoid outputting too many
lines by replacing text that has been already outputted to the screen.
Controller
AuthComponent
Datasource
Mysql
The RLIKE wildcard operator has been added to allow regular expression pattern lookups this way.
Schema migrations with MySQL now support an after key when adding a column. This key allows
you to specify which column the new one should be added after.
Model
Model
966
Model::afterFind() now always uses a consistent format for afterFind(). When $primary
is false, the results will always be located under $data[0][ModelName]. You can set the
useConsistentAfterFind property to false on your models to restore the original behavior.
Network
CakeRequest
CakeRequest::param() can now read values using Hash path syntax like data().
CakeRequest:setInput() was added.
HttpSocket
CakeTime::timeAgoInWords() now supports strftime() compatible absolute date formats. This helps make localizing formatted times easier.
Hash
Validation::between
has
been
Validation::lengthBetween instead.
deprecated,
you
should
use
967
View
HtmlHelper
HtmlHelper::css() had the once option added. It works the same as the once option for
HtmlHelper::script(). The default value is false to maintain backwards compatibility.
The $confirmMessage argument of HtmlHelper::link() has been deprecated. You should
instead use key confirm in $options to specify the message.
FormHelper
PaginatorHelper::meta() has been added to output the meta-links (rel prev/next) for a paginated result set.
968
Console
SchemaShell
The create and update subcommands now have a yes option. The yes option allows you to
skip the various interactive questions forcing a yes reply.
CompletionShell
The CompletionShell was added. It aims to assist in the creation of autocompletion libraries for shell
environments like bash, or zsh. No shell scripts are included in CakePHP, but the underlying tools are
now available.
Controller
AuthComponent
CookieComponent can use the new AES-256 encryption offered by Security. You can enable
this by calling CookieComponent::type() with aes.
RequestHandlerComponent
It
AclComponent
ACL node lookup failures are now logged directly. The call to trigger_error() has been removed.
Scaffold
969
Core
App
CakePlugin::loadAll() now merges the defaults and plugin specific options as intuitively
expected. See the test cases for details.
Event
EventManager
Events bound to the global manager are now fired in priority order with events bound to a local manager.
This can cause listeners to be fired in a different order than they were in previous releases. Instead of all
global listeners being triggered, and then instance listeners being fired afterwards, the two sets of listeners
are combined into one list of listeners based on their priorities and then fired as one set. Global listeners of
a given priority are still fired before instance listeners.
I18n
The I18n class has several new constants. These constants allow you to replace hardcoded integers
with readable values. e.g. I18n::LC_MESSAGES.
Model
Unsigned integers are now supported by datasources that provide them (MySQL). You can set the
unsigned option to true in your schema/fixture files to start using this feature.
Joins included in queries are now added after joins from associations are added. This makes it easier
to join tables that depend on generated associations.
Network
CakeEmail
Email addresses in CakeEmail are now validated with filter_var by default. This relaxes the
email address rules allowing internal email addresses like root@localhost for example.
You can now specify layout key in email config array without having to specify template key.
970
CakeRequest
CakeRequest::addDetector() now supports options which accepts an array of valid options when creating param based detectors.
CakeRequest::onlyAllow() has been deprecated. As replacement a new method named
CakeRequest::allowMethod() has been added with identical functionality. The new method
name is more intuitive and better conveys what the method does.
CakeSession
Sessions will not be started if they are known to be empty. If the session cookie cannot be found, a
session will not be started until a write operation is done.
Routing
Router
Debugger::dump() and Debugger::log() now support a $depth parameter. This new parameter makes it easy to output more deeply nested object structures.
Hash
Hash::insert() and Hash::remove() now support matcher expressions in their path selectors.
File
File::replaceText() was added. This method allows you to easily replace text in a file using
str_replace.
Folder
971
Security
CakeLog does not auto-configure itself anymore. As a result log files will not be auto-created anymore
if no stream is listening. Please make sure you got at least one default engine set up if you want to
listen to all types and levels.
Error
ExceptionRenderer
The ExceptionRenderer now populates the error templates with code, message and url variables.
name has been deprecated but is still available. This unifies the variables across all error templates.
Testing
Fixture files can now be placed in sub-directories.
You can use fixtures in subdirectories by including the directory name after the .. For example, app.my_dir/article will load
App/Test/Fixture/my_dir/ArticleFixture. It should be noted that the fixture directory
will not be inflected or modified in any way.
Fixtures can now set $canUseMemory to false to disable the memory storage engine being used in
MySQL.
972
View
View
$title_for_layout is deprecated.
Use $this->fetch(title);
$this->assign(title, your-page-title); instead.
and
FormHelper will now generate file inputs for binary field types now.
FormHelper::end() had a second parameter added. This parameter lets you pass additional
properties to the fields used for securing forms in conjunction with SecurityComponent.
FormHelper::end() and FormHelper::secure() allow you to pass additional options that
are turned into attributes on the generated hidden inputs. This is useful when you want to use the
HTML5 form attribute.
FormHelper::postLink() now allows you to buffer the generated form tag instead of returning
it with the link. This helps avoiding nested form tags.
PaginationHelper
PaginatorHelper::sort() now has a lock option to create pagination sort links with the
default direction only.
ScaffoldView
973
SchemaShell
cake bake model now supports baking $behaviors. Finding lft, rght and parent_id fields in
your table it will add the Tree behavior, for example. You can also extend the ModelTask to support
your own behaviors to be recognized.
cake bake for views, models, controllers, tests and fixtures now supports a -f or --force parameter to force overwriting of files.
Tasks in core can now be aliased in the same way you would Helpers, Components and Behaviors
FixtureTask
cake bake fixture now supports a --schema parameter to allow baking all fixtures with
noninteractive all while using schema import.
Core
Constants
Constants IMAGES_URL, JS_URL, CSS_URL have been deprecated and replaced with config variables App.imageBaseUrl, App.jsBaseUrl, App.cssBaseUrl respectively.
Constants IMAGES, JS, CSS have been deprecated.
Object
AuthComponent now supports proper stateless mode when using Basic or Digest authenticators.
Starting of session can be prevented by setting AuthComponent::$sessionKey to false. Also
now when using only Basic or Digest you are no longer redirected to login page. For more info
check the AuthComponent page.
Property AuthComponent::$authError can be set to boolean false to suppress flash message from being displayed.
974
PasswordHasher
Authenticating objects now use new password hasher objects for password hash generation and checking. See Hashing passwords for more info.
DbAcl
DbAcl now uses INNER joins instead of LEFT joins. This improves performance for some database
vendors.
Model
Models
Model::save(),
Model::saveField(),
Model::saveAll(),
Model::saveAssociated(), Model::saveMany() now take a new counterCache
option. You can set it to false to avoid updating counter cache values for the particular save operation.
Model::clear() was added.
Datasource
Mysql, Postgres, and SQLserver now support a settings array in the connection definition. This key
=> value pair will be issued as SET commands when the connection is created.
Mysql driver now supports SSL options.
View
JsonView
975
HtmlHelper
The API for HtmlHelper::css() has been been simplified. You can now provide an array of
options as the second argument. When you do, the rel attribute defaults to stylesheet.
New option escapeTitle added to HtmlHelper::link() to control escaping of only link title
and not attributes.
TextHelper
The round option was added to FormHelper::dateTime(). Can be set to up or down to force
rounding in either direction. Defaults to null which rounds half up according to interval.
Network
CakeRequest
CakeResponse::location() has been added to get or set the redirect location header.
CakeEmail
Logged email messages now have the scope of email by default. If you are not seeing email contents
in your logs, be sure to add the email scope to your logging configuration.
976
CakeEmail::emailPattern() was added. This method can be used to relax email validation
rules. This is useful when dealing with certain Japanese hosts that allow non-compliant addresses to
be used.
CakeEmail::attachments() now allows you to provide the file contents directly using the
data key.
Configuration data is now correctly merged with transport classes.
HttpSocket
ell is now the default locale for Greek as specified by ISO 639-3 and gre its alias. The locale
folders have to be adjusted accordingly (from /Locale/gre/ to /Locale/ell/ ).
fas is now the default locale for Farsi as specified by ISO 639-3 and per its alias. The locale folders
have to be adjusted accordingly (from /Locale/per/ to /Locale/fas/ ).
sme is now the default locale for Sami as specified by ISO 639-3 and smi its alias. The locale folders
have to be adjusted accordingly (from /Locale/smi/ to /Locale/sme/ ).
mkd replaces mk as default locale for Macedonian as specified by ISO 639-3. The corresponding
locale folders have to be adjusted, as well.
Catalog code in has been dropped in favor of id (Indonesian), e has been dropped in favor of el
(Greek), n has been dropped in favor of nl (Dutch), p has been dropped in favor of pl (Polish), sz
has been dropped in favor of se (Sami).
Kazakh has been added with kaz as locale and kk as catalog code.
Kalaallisut has been added with kal as locale and kl as catalog code.
The constant DEFAULT_LANGUAGE has been deprecated in favor of Configure value
Config.language.
Logging
Log engines do not need the suffix Log anymore in their setup configuration. So for the FileLog
engine it suffices to define engine => File now. This unifies the way engines are
named in configuration (see Cache engines for example). Note: If you have a Log engine like
DatabaseLogger that does not follow the convention to use a suffix Log for your class name
you have to adjust your class name to DatabaseLog. You should also avoid class names like
SomeLogLog which include the suffix twice at the end.
977
FileLog
Two new config options size and rotate have been added for FileLog engine.
In debug mode missing directories will now be automatically created to avoid unnecessary errors
thrown.
SyslogLog
The new logging engine SyslogLog was added to stream messages to syslog.
Cache
FileEngine
In debug mode missing directories will now be automatically created to avoid unnecessary errors
thrown.
Utility
General
978
CakeTime
New option pretty has been added to Xml::fromArray() to return nicely formatted Xml
Error
ErrorHandler
New configuration option skipLog has been added, to allow skipping certain Exception types to be logged.
Configure::write(Exception.skipLog,
array(NotFoundException, ForbiddenException)); will avoid these exceptions and the ones extending them to be be logged when Exception.log config is
true
Routing
Router
979
Caching
FileEngine is always the default cache engine. In the past a number of people had difficulty setting up
and deploying APC correctly both in cli + web. Using files should make setting up CakePHP simpler
for new developers.
Configure::write(Cache.viewPrefix, YOURPREFIX); has been added to core.php to allow multiple
domains/languages per setup.
Component
AuthComponent
been
deprecated.
Use
PaginatorComponent
PaginatorComponent now supports the findType option. This can be used to specify what find
method you want used for pagination. This is a bit easier to manage and set than the 0th index.
PaginatorComponent now throws a NotFoundException when trying to access a page which is out of
range (i.e. requested page is greater than total page count).
SecurityComponent
SecurityComponent now supports the unlockedActions option. This can be used to disable all
security checks for any actions listed in this option.
RequestHandlerComponent
980
CookieComponent
CookieComponent::check()
CakeSession::check() does.
was
added.
This
method
works
the
same
as
Console
The server shell was added. You can use this to start the PHP5.4 webserver for your CakePHP
application.
Baking a new project now sets the applications cache prefix to the name of the application.
I18n
L10n
nld is now the default locale for Dutch as specified by ISO 639-3 and dut its alias. The locale
folders have to be adjusted accordingly (from /Locale/dut/ to /Locale/nld/ ).
Albanian is now sqi, Basque is now eus, Chinese is now zho, Tibetan is now bod, Czech is
now ces, Farsi is now fas, French is now fra, Icelandic is now isl, Macedonian is now mkd,
Malaysian is now msa, Romanian is now ron, Serbian is now srp and Slovak is now slk. The
corresponding locale folders have to be adjusted, as well.
Core
CakePlugin
CakePlugin::load() can now take a new ignoreMissing option. Setting it to true will prevent file include errors when you try to load routes or bootstrap but they dont exist for a plugin. So essentially you can now use the following statement which will load all plugins and their routes and bootstrap for whatever plugin it can find:: CakePlugin::loadAll(array(array(routes
=> true, bootstrap => true, ignoreMissing => true)))
Configure
981
Error
Exceptions
CakeBaseException was added, which all core Exceptions now extend. The base exception class also
introduces the responseHeader() method which can be called on created Exception instances to
add headers for the response, as Exceptions dont reuse any response instance.
Model
Support for the biginteger type was added to all core datasources, and fixtures.
Support for FULLTEXT indexes was added for the MySQL driver.
Models
Model::find(list) now sets the recursive based on the max containment depth or recursive value. When list is used with ContainableBehavior.
Model::find(first) will now return an empty array when no records are found.
Validation
Missing validation methods will always trigger errors now instead of only in development mode.
Network
SmtpTransport
982
CakeEmail
HttpSocket now verifies SSL certificates by default. If you are using self-signed certificates or
connecting through proxies you may need to use some of the new options to augment this behavior.
See Handling SSL certificates for more information.
HttpResponse was renamed to HttpSocketResponse. This avoids a common issue with
the HTTP PECL extension. There is an HttpResponse class provided as well for compatibility
reasons.
Routing
Router
983
Layout
The doctype for layout files in the app folder and the bake templates in the cake package has been
changed from XHTML to HTML5.
Helpers
New property Helper::$settings has been added for your helper setting. The $settings
parameter of Helper::__construct() is merged with Helper::$settings.
FormHelper
FormHelper::select() now accepts a list of values in the disabled attribute. Combined with
multiple => checkbox, this allows you to provide a list of values you want disabled.
FormHelper::postLink() now accepts a method key. This allows you to create link forms
using HTTP methods other than POST.
When creating inputs with FormHelper::input() you can now set the errorMessage option
to false. This will disable the error message display, but leave the error class names intact.
The FormHelper now also adds the HTML5 required attribute to your input elements based on
validation rules for a field. If you have a Cancel button in your form which submits the form then
you should add formnovalidate => true to your button options to prevent the triggering
of validation in HTML. You can also prevent the validation triggering for the whole form by adding
novalidate => true in your FormHelper::create() options.
FormHelper::input() now generates input elements of type tel and email based on field
names if type option is not specified.
HtmlHelper
984
For methods: PaginatorHelper::prev() and PaginatorHelper::next() it is now possible to set the tag option to false to disable the wrapper. Also a new option disabledTag has been
added for these two methods.
Testing
A core fixture for the default cake_sessions table was added. You can use it by adding
core.cake_sessions to your fixture list.
CakeTestCase::getMockForModel() was added. This simplifies getting mock objects for
models.
Utility
CakeNumber
Folder::copy() and Folder::move() now support the ability to merge the target and source
directories in addition to skip/overwrite.
String
Debugger::exportVar() now outputs private and protected properties in PHP >= 5.3.0.
Security
Support for bcrypt1 was added. See the Security::hash() documentation for more information
on how to use bcrypt.
1
http://codahale.com/how-to-safely-store-a-password/
985
Validation
You will also need to modify app/Config/core.php. Change the value of LOG_ERROR to LOG_ERR:
define(LOG_ERROR, LOG_ERR);
When using Model::validateAssociated() or Model::saveAssociated() and primary model validation fails, the validation errors of associated models are no longer wiped out.
986
Model::$validationErrors will now always show all the errors. You might need to update your
test cases to reflect this change.
Console
I18N extract shell
Models
Model::find(count) will now call the custom find methods with $state = before
and $queryData[operation] = count. In many cases custom finds already return correct counts for pagination, but operation key allows more flexibility to build other queries, or
drop joins which are required for the custom finder itself. As the pagination of custom find methods
never worked quite well it required workarounds for this in the model level, which are now no longer
needed.
Model::find(first) will now return an empty array when no records are found.
Datasources
Dbo
datasources
now
supports
real
nested
transactions.
If
you
need
to
use
this
feature
in
your
application,
enable
it
using
ConnectionManager::getDataSource(default)->useNestedTransactions
= true;
Testing
The webrunner now includes links to re-run a test with debug output.
Generated test cases for Controller now subclass ControllerTestCase.
Error Handling
When repeat exceptions, or exception are raised when rendering error pages, the new error layout
will be used. Its recommended to not use additional helpers in this layout as its intended for development level errors only. This fixes issues with fatal errors in rendering error pages due to helper usage
in the default layout.
It is important to copy the app/View/Layouts/error.ctp into your app directory. Failing to
do so will make error page rendering fail.
987
Configure::dump() was added. It is used to persist configuration data in durable storage like
files. Both PhpReader and IniReader work with it.
A new config parameter Config.timezone is available in which you can set users timezone string. eg.
You can do Configure::write(Config.timezone, Europe/Paris). If a method
of CakeTime class is called with $timezone parameter as null and Config.timezone is set, then
the value of Config.timezone will be used. This feature allows you to set users timezone just once
instead of passing it each time in function calls.
Controller
AuthComponent
You can now encrypt cookie values with the rijndael cipher. This requires the mcrypt2 extension to be
installed. Using rijndael gives cookie values actual encryption, and is recommended in place of the
XOR cipher available in previous releases. The XOR cipher is still the default cipher scheme to maintain compatibility with previous releases. You can read more in the Security::rijndael()
documentation.
Pagination
Paginating custom finders will now return correct counts, see Model changes for more info.
2
http://php.net/mcrypt
988
Network
CakeEmail
CakeRequest will now automatically decode application/x-www-form-urlencoded request bodies on PUT and DELETE requests. This data will be available as $this->data just like
POST data is.
Utility
Set
The Set class is now deprecated, and replaced by the Hash class. Set will not be removed until 3.0.
Set::expand() was added.
Hash
The Hash class was added in 2.2. It replaced Set providing a more consistent, reliable and performant API
to doing many of the same tasks Set does. See the Hash page for more detail.
CakeTime
The $userOffset parameter has been replaced with $timezone parameter in all relevant functions. So instead of numeric offset you can now pass in a timezone string or DateTimeZone object.
Passing numeric offsets for $timezone parameter is still possible for backwards compatibility.
CakeTime::timeAgoInWords() had the accuracy option added. This option allows you to
specify how accurate formatted times should be.
New methods added:
CakeTime::toServer()
2.2 Migration Guide
989
CakeTime::timezone()
CakeTime::listTimezones()
The $dateString parameter in all methods now accepts a DateTime object.
Helpers
FormHelper
FormHelper now better handles adding required classes to inputs. It now honors the on key.
FormHelper::radio() now supports an empty which works similar to the empty option on
select().
Added FormHelper::inputDefaults() to set common properties for each of the inputs generated by the helper
TimeHelper
Since 2.1, TimeHelper uses the CakeTime class for all its relevant methods. The $userOffset
parameter has been replaced with $timezone parameter.
TimeHelper::timeAgoInWords() has the element option added. This allows you to specify
an HTML element to wrap the formatted time.
HtmlHelper
Event listeners can now be attached to the dispatcher calls, those will have the ability to change the
request information or the response before it is sent to the client. Check the full documentation for
this new features in Dispatcher Filters
With the addition of Dispatcher Filters youll need to update app/Config/bootstrap.php.
See Required steps to upgrade.
Router
Router::setExtensions() has been added. With the new method you can now add more
extensions to be parsed, for example within a plugin routes file.
990
Cache
Redis Engine
A new caching engine was added using the phpredis extension3 it is configured similarly to the Memcache
engine.
Cache groups
It is now possible to tag or label cache keys under groups. This makes it simpler to mass-delete cache entries
associated to the same label. Groups are declared at configuration time when creating the cache engine:
Cache::config(array(
engine => Redis,
...
groups => array(post, comment, user)
));
You can have as many groups as you like, but keep in mind they cannot be dynamically modified.
The Cache::clearGroup() class method was added. It takes the group name and deletes all entries
labeled with the same string.
Log
Changes
in
CakeLog
now
require,
some
additional
configuration
app/Config/bootstrap.php. See Required steps to upgrade, and Logging.
in
your
The CakeLog class now accepts the same log levels as defined in RFC 54244 . Several convenience
methods have also been added:
CakeLog::emergency($message, $scope = array())
CakeLog::alert($message, $scope = array())
CakeLog::critical($message, $scope = array())
CakeLog::error($message, $scope = array())
CakeLog::warning($message, $scope = array())
CakeLog::notice($message, $scope = array())
CakeLog::info($message, $scope = array())
CakeLog::debug($message, $scope = array())
A third argument $scope has been added to CakeLog::write. See Logging Scopes.
A new log engine: ConsoleLog has been added.
3
4
https://github.com/nicolasff/phpredis
http://tools.ietf.org/html/rfc5424
991
Model Validation
A new object ModelValidator was added to delegate the work of validating model data, it should
be transparent to the application and fully backwards compatible. It also exposes a rich API to add,
modify and remove validation rules. Check docs for this object in Data Validation.
Custom validation functions in your models need to have public visibility so that they are accessible
by ModelValidator.
New validation rules added:
Validation::naturalNumber()
Validation::mimeType()
Validation::uploadError()
If your application already has these files/classes you dont need to do anything. Additionally if you were
using the core PagesController, you would need to copy this to your app/Controller directory as well.
992
.htaccess files
The default .htaccess files have changed, you should remember to update them or update your webservers URL re-writing scheme to match the changes done in .htaccess
Models
The beforeDelete callback will be fired before behaviors beforeDelete callbacks. This makes it
consistent with the rest of the events triggered in the model layer.
Model::find(threaded) now accepts $options[parent] if using other field then
parent_id. Also if the model has TreeBehavior attached and set up with other parent field, the
threaded find will by default use that.
Parameters for queries using prepared statements will now be part of the SQL dump.
Validation arrays can now be more specific with when a field is required. The required key now
accepts create and update. These values will make a field required when creating or updating.
Model now has a schemaName property.
If your application switches datasources
by modifying Model::$useDbConfig you should also modify schemaName or use
Model::setDataSource() method which handles this for you.
CakeSession
Changed in version 2.1.1: CakeSession no longer sets the P3P header, as this is the responsibility of your
application. More info see ticket #25155 in lighthouse
Behaviors
TranslateBehavior
Debugger::getType() has been added. It can be used to get the type of variables.
5
http://cakephp.lighthouseapp.com/projects/42648/tickets/2515-cakephp-20-session-p3p-header-doesnt-work-in-an-iframe
993
Debugger::exportVar() has been modified to create more readable and useful output.
debug()
debug() now uses Debugger internally. This makes it consistent with Debugger, and takes advantage of
improvements made there.
Set
Set::nest() has been added. It takes in a flat array and returns a nested array
File
App::build() now has the ability to register new packages using App::REGISTER. See Add
new packages to an application for more information.
Classes that could not be found on configured paths will be searched inside APP as a fallback path.
This makes autoloading nested directories in app/Vendor easier.
Console
Test Shell
A new TestShell has been added. It reduces the typing required to run unit tests, and offers a file path based
UI:
./Console/cake test app Model/Post
./Console/cake test app Controller/PostsController
./Console/cake test Plugin View/Helper/MyHelper
994
The old testsuite shell and its syntax are still available.
General
Routes can now use a special /** syntax to include all trailing arguments as a single passed argument.
See the section on Connecting Routes for more information.
Router::resourceMap() was added.
Router::defaultRouteClass() was added. This method allows you to set the default route
class used for all future routes that are connected.
Network
CakeRequest
Controller::$uses was modified the default value is now true instead of false. Additionally
different values are handled slightly differently, but will behave the same in most cases.
true Will load the default model and merge with AppController.
An array will load those models and merge with AppController.
An empty array will not load any models other than those declared in the base class.
false will not load any models, and will not merge with the base class either.
995
Components
AuthComponent
AclComponent no longer lowercases and inflects the class name used for Acl.classname. Instead it uses the provided value as is.
Acl backend implementations should now be put in Controller/Component/Acl.
Acl implementations should be moved into the Component/Acl directory from Component.
For example if your Acl class was called CustomAclComponent, and was
in Controller/Component/CustomAclComponent.php.
It should be moved into
Controller/Component/Acl/CustomAcl.php, and be named CustomAcl.
DbAcl has been moved into a separate file.
IniAcl has been moved into a separate file.
AclInterface has been moved into a separate file.
Helpers
TextHelper
TextHelper::autoLink(),
TextHelper::autoLinkUrls(),
TextHelper::autoLinkEmails() now HTML escape their input by default. You can
control this with the escape option.
HtmlHelper
996
$scripts_for_layout is still available, but the view blocks API gives a more extensible &
flexible replacement.
The Plugin.view syntax is now available everywhere. You can use this syntax anywhere you
reference the name of a view, layout or element.
The $options[plugin] option for element() is deprecated.
Plugin.element_name instead.
Two new view classes have been added to CakePHP. A new JsonView and XmlView allow you to easily
generate XML and JSON views. You can learn more about these classes in the section on JSON and XML
views
Extending views
View has a new method allowing you to wrap or extend a view/element/layout with another file. See the
section on Extending Views for more information on this feature.
Themes
The ThemeView class is deprecated in favor of the View class. Simply setting $this->theme =
MyTheme will enable theme support, and all custom View classes which extend from ThemeView
should extend View.
997
View blocks
View blocks are a flexible way to create slots or blocks in your views.
Blocks replace
$scripts_for_layout with a more robust and flexible API. See the section on Using view blocks
for more information.
Helpers
New callbacks
FormHelper now omits disabled fields from the secured fields hash. This makes working with
SecurityComponent and disabled inputs easier.
The between option when used in conjunction with radio inputs, now behaves differently. The
between value is now placed between the legend and first input elements.
The hiddenField option with checkbox inputs can now be set to a specific value such as N rather
than just 0.
The for attribute for date + time inputs now reflects the first generated input. This may result in the
for attribute changing for generated datetime inputs.
The type attribute for FormHelper::button() can be removed now. It still defaults to submit.
FormHelper::radio() now allows you to disable all options. You can do this by setting either
disabled => true or disabled => disabled in the $attributes array.
PaginatorHelper
998
Model::saveAll() and friends now support passing the fieldList for multiple models. Example:
$this->SomeModel->saveAll($data, array(
fieldList => array(
SomeModel => array(field_1),
AssociatedModel => array(field_2, field_3)
)
));
Model::saveAll() and friends now can save unlimited levels deep. Example:
$data = array(
Article => array(title => My first article),
Comment => array(
array(body => Comment 1, user_id => 1),
array(
body => Save a new user as well,
User => array(first => mad, last => coder)
)
),
);
$this->SomeModel->saveAll($data, array(deep => true));
999
View
View Blocks
View Blocks are a mechanism to allow the inclusion of slots of content, whilst allowing child view classes
or elements to provide custom content for that block.
Blocks are output by calling the fetch method on the View. For example, the following can be placed in
your View/Layouts/default.ctp file:
<?php echo $this->fetch(my_block); ?>
This will echo the content of the block if available, or an empty string if it is undefined.
Setting the content of a block can be done in a number of ways. A simple assignment of data can be done
using assign:
<?php $this->assign(my_block, Hello Block); ?>
ThemeView
In 2.1, the use of ThemeView is deprecated in favor of using the View class itself. ThemeView is now a
stub class.
All custom pathing code has been moved into the View class, meaning that it is now possible for classes
extending the View class to automatically support themes. Whereas before we might set the $viewClass
Controller property to Theme, it is now possible to enable themes by simply setting the $theme property.
Example:
App::uses(Controller, Controller);
class AppController extends Controller {
public $theme = Example;
}
1000
All View classes which extended ThemeView in 2.0 should now simply extend View.
JsonView
Once you have setup the controller, you need to identify what content should be serialized as JSON, by
setting the view variable _serialize. Example:
$this->set(compact(users, posts, tags));
$this->set(_serialize, array(users, posts));
The above example would result in only the users and posts variables being serialized for the JSON
output, like so:
{"users": [...], "posts": [...]}
There is no longer any need to create view ctp files in order to display Json content.
Further customization of the output can be achieved by extending the JsonView class with your own
custom view class if required.
The following example wraps the result with {results:
...
}:
App::uses(JsonView, View);
class ResultsJsonView extends JsonView {
public function render($view = null, $layout = null) {
$result = parent::render($view, $layout);
if (isset($this->viewVars[_serialize])) {
return json_encode(array(results => json_decode($result)));
}
return $result;
}
}
XmlView
Much like the JsonView, the XmlView requires you to set the _serialize view variable in order to
indicate what information should be serialized into XML for output:
1001
The above example would result in only the users and posts variables being serialized for the XML
output, like so:
<response><users>...</users><posts>...</posts></response>
Note that the XmlView adds a response node to wrap all serialized content.
Conditional View Rendering
Several new methods were added to CakeRequest to ease the task of setting correct HTTP headers to
foster HTTP caching. You can now define our caching strategy using the expiration or validation HTTP
cache model, or combine both. Now there are specific methods in CakeRequest to fine-tune CacheControl directives, set the entity tag (Etag), set the Last-Modified time and much more.
When those methods are combined with having the RequestHandlerComponent enabled in your controller, the component will automatically decide if the response is already cached in the client and will send
a 304 Not Modified status code before rendering the view. Skipping the view rendering process saves CPU
cycles and memory.
class ArticlesController extends AppController {
public $components = array(RequestHandler);
public function view($id) {
$article = $this->Article->read(null, $id);
$this->response->modified($article[Article][modified]);
$this->set(compact(article));
}
}
In the above example the view will not be rendered if the client sent the header If-Modified-Since, and the
response will have a 304 status.
Helpers
To allow easier use outside of the View layer, methods from TimeHelper, TextHelper, and
NumberHelper helpers have been extracted to CakeTime, String, and CakeNumber classes respectively.
To use the new utility classes:
class AppController extends Controller {
public function log($msg) {
$msg .= String::truncate($msg, 100);
parent::log($msg);
}
}
1002
You can override the default class to use by creating a new class in your APP/Utility folder, e.g.:
Utility/MyAwesomeStringClass.php, and specify it in engine key:
// Utility/MyAwesomeStringClass.php
class MyAwesomeStringClass extends String {
// my truncate is better than yours
public static function truncate($text, $length = 100, $options = array()) {
return null;
}
}
// Controller/AppController.php
class AppController extends Controller {
public $helpers = array(
Text => array(
engine => MyAwesomeStringClass,
),
);
}
HtmlHelper
A new function HtmlHelper::media() has been added for HTML5s audio/video element generation.
1003
All files containing classes should be named after the class it contains. No file should contain more than one
class. So, no more lowercasing and underscoring your file names. Here are some examples:
my_things_controller.php becomes MyThingsController.php
form.php (a Helper) becomes FormHelper.php
session.php (a Component) becomes SessionComponent.php
This makes file naming a lot more clear and consistent across applications, and also avoids a few edge cases
where the file loader would get confused in the past and load files it should not.
Folder Names
Most folders should be also CamelCased, especially when containing classes. Think of namespaces, each
folder represents a level in the namespacing hierarchy, folders that do not contain classes, or do not constitute
a namespace on themselves, should be lowercased.
CamelCased Folders:
Config
Console
Controller
Controller/Component
Lib
Locale
Model
1004
Model/Behavior
Plugin
Test
Vendor
View
View/Helper
lowercased Folders:
tmp
webroot
htaccess (URL Rewriting)
In your app/webroot/.htaccess replace line RewriteRule ^(.*)$ index.php?url=$1
[QSA,L] with RewriteRule ^(.*)$ index.php [QSA,L]
AppController / AppModel / AppHelper / AppShell
The app/app_controller.php, app/app_model.php, app/app_helper.php are now located and named as app/Controller/AppController.php, app/Model/AppModel.php and
app/View/Helper/AppHelper.php respectively.
Also all shell/task now extend AppShell.
app/Console/Command/AppShell.php
Internationalization / Localization
__() (Double underscore shortcut function) always returns the translation (not echo anymore).
If you want to echo the result of the translation, use:
echo __(My Message);
1005
Alongside this, if you pass additional parameters, the translation will call sprintf6 with these parameters
before returning. For example:
// Will return something like "Called: MyClass:myMethod"
echo __(Called: %s:%s, $className, $methodName);
http://php.net/manual/en/function.sprintf.php
http://php.net/manual/en/function.sprintf.php
1006
COMPONENTS
CONFIGS
CONSOLE_LIBS
CONTROLLERS
CONTROLLER_TESTS
ELEMENTS
HELPERS
HELPER_TESTS
LAYOUTS
LIB_TESTS
LIBS
MODELS
MODEL_TESTS
SCRIPTS
VIEWS
CakeRequest
This new class encapsulates the parameters and functions related to an incoming request. It replaces
many features inside Dispatcher, RequestHandlerComponent and Controller. It also replaces
$this->params array in all places. CakeRequest implements ArrayAccess so many interactions
with the old params array do not need to change. See the CakeRequest new features for more information.
Request handling, $_GET[url] and .htaccess files
CakePHP no longer uses $_GET[url] for handling application request paths. Instead it uses
$_SERVER[PATH_INFO]. This provides a more uniform way of handling requests between servers
with URL rewriting and those without. Because of these changes, youll need to update your .htaccess files
and app/webroot/index.php, as these files were changed to accommodate the changes. Additionally
$this->params[url][url] no longer exists. Instead you should be using $this->request->url
to access the same value. This attribute now contains the url without the leading slash / prepended.
Note: For the homepage itself (http://domain/) $this->request->url now returns boolean false instead of /. Make sure you check on that accordingly:
if (!$this->request->url) {} // instead of $this->request->url === /
1007
Components
Component is now the required base class for all components. You should update your components and
their constructors, as both have changed:
class PrgComponent extends Component {
public function __construct(ComponentCollection $collection,
$settings = array()) {
parent::__construct($collection, $settings);
}
}
As with helpers it is important to call parent::__construct() in components with overridden constructors. Settings for a component are also passed into the constructor now, and not the initialize()
callback. This makes getting well constructed objects easier, and allows the base class to handle setting the
properties up.
Since settings have been moved to the component constructor, the initialize() callback no longer
receives $settings as its 2nd parameter. You should update your components to use the following method
signature:
public function initialize(Controller $controller) { }
Additionally, the initialize() method is only called on components that are enabled. This usually means
components that are directly attached to the controller object.
Deprecated callbacks removed
All the deprecated callbacks in Component have not been transferred to ComponentCollection. Instead you
should use the trigger() method to interact with callbacks. If you need to trigger a callback you could do so
by calling:
$this->Components->trigger(someCallback, array(&$this));
In the past you were able to disable components via $this->Auth->enabled = false; for example. In CakePHP 2.0 you should use the ComponentCollections disable method, $this->Components>disable(Auth);. Using the enabled property will not work.
AclComponent
Many of RequestHandlerComponents methods are just proxies for CakeRequest methods. The following methods have been deprecated and will be removed in future versions:
isSsl()
isAjax()
isPost()
isPut()
isFlash()
isDelete()
getReferer()
getClientIp()
accepts(), prefers(), requestedWith() All deal in mapped content types now. They no
longer work with mime-types. You can use RequestHandler::setContent() to create new
content types.
RequestHandler::setContent() no longer accepts an array as a single argument, you must
supply both arguments.
SecurityComponent
SecurityComponent no longer handles Basic and Digest Authentication. These are both handled by the new
AuthComponent. The following methods have been removed from SecurityComponent:
requireLogin()
generateDigestResponseHash()
loginCredentials()
loginRequest()
parseDigestAuthData()
In addition the following properties were removed:
$loginUsers
$requireLogin
Moving these features to AuthComponent was done to provide a single place for all types of authentication
and to streamline the roles of each component.
1009
AuthComponent
The AuthComponent was entirely re-factored for 2.0, this was done to help reduce developer confusion and
frustration. In addition, AuthComponent was made more flexible and extensible. You can find out more in
the Authentication guide.
EmailComponent
The EmailComponent has been deprecated and has created a new library class to send e-mails. See CakeEmail Email changes for more details.
SessionComponent
The API for App::build() has changed to App::build($paths, $mode). It now allows you to
either append, prepend or reset/replace existing paths. The $mode param can take any of the following 3
values: App::APPEND, App::PREPEND, App::RESET. The default behavior of the function remains the
same (ie. Prepending new paths to existing list).
1010
App::path()
Now supports plugins, App::path(Controller, Users) will return the folder location of the controllers in the Users plugin.
Wont merge core paths anymore, it will only return paths defined in App::build() or default ones in
app (or corresponding plugin).
App::build()
Will not merge app path with core paths anymore.
App::objects()
Now supports plugins, App::objects(Users.Model) will return the models in plugin Users.
Returns array() instead of false for empty results or invalid types.
Does not return core objects anymore, App::objects(core) will return array().
Returns the complete class name.
App class lost the following properties, use method App::path() to access their value
App::$models
App::$behaviors
App::$controllers
App::$components
App::$datasources
App::$libs
App::$views
App::$helpers
App::$plugins
App::$vendors
App::$locales
App::$shells
App::import()
No longer looks for classes recursively, it strictly uses the values for the paths defined in App::build().
Will not be able to load App::import(Component, Component) use App::uses(Component, Controller);
Using App::import(Lib, CoreClass) to load core classes is no longer possible.
1011
Importing a non-existent file, supplying a wrong type or package name, or null values for $name and
$file parameters will result in a false return value.
App::import(Core, CoreClass) is no longer supported, use App::uses() instead and let the class
autoloading do the rest.
Loading Vendor files does not look recursively in the vendors folder, it will also no longer convert the
file to underscored as it did in the past.
App::core()
First parameter is no longer optional, it will always return one path
It cant be used anymore to get the vendors paths
It will only accept new style package names
Class loading with App::uses() Although there has been a huge refactoring in how the classes are loaded,
in very few occasions you will need to change your application code to respect the way you were used to
doing it. The biggest change is the introduction of a new method:
App::uses(AuthComponent, Controller/Component);
We decided the function name should emulate PHP 5.3s use keyword, just as a way of declaring where
a class name should be located. The first parameter of App::uses() is the complete name of the class
you intend to load, and the second one, the package name (or namespace) where it belongs to. The main
difference with CakePHP 1.3s App::import() is that the former wont actually import the class, it will
just setup the system so when the class is used for the first time it will be located.
Some examples on using App::uses() when migrating from App::import():
App::import(Controller, Pages);
// becomes
App::uses(PagesController, Controller);
App::import(Component, Auth);
// becomes
App::uses(AuthComponent, Controller/Component);
App::import(View, Media);
// becomes
App::uses(MediaView, View);
App::import(Core, Xml);
// becomes
App::uses(Xml, Utility);
App::import(Datasource, MongoDb.MongoDbSource);
// becomes
App::uses(MongoDbSource, MongoDb.Model/Datasource);
1012
All classes that were loaded in the past using App::import(Core, $class); will need to be
loaded using App::uses() referring to the correct package. See the API to locate the classes in their new
folders. Some examples:
App::import(Core, CakeRoute);
// becomes
App::uses(CakeRoute, Routing/Route);
App::import(Core, Sanitize);
// becomes
App::uses(Sanitize, Utility);
App::import(Core, HttpSocket);
// becomes
App::uses(HttpSocket, Network/Http);
In contrast to how App::import() worked in the past, the new class loader will not locate classes recursively. This led to an impressive performance gain even on develop mode, at the cost of some seldom used
features that always caused side effects. To be clear again, the class loader will only fetch the class in the
exact package in which you told it to find it.
App::build() and core paths App::build() will not merge app paths with core paths anymore.
Examples:
App::build(array(controllers => array(/full/path/to/controllers)));
//becomes
App::build(array(Controller => array(/full/path/to/Controller)));
App::build(array(helpers => array(/full/path/to/controllers)));
//becomes
App::build(array(View/Helper => array(/full/path/to/View/Helper)));
CakeLog
Log streams now need to implement CakeLogInterface. Exceptions will be raised if a configured
logger does not.
Cache
1013
Cache no longer retains the name of the last configured cache engine. This means that operations you
want to occur on a specific engine need to have the $config parameter equal to the config name you
want the operation to occur on.
Cache::config(something);
Cache::write(key, $value);
// would become
Cache::write(key, $value, something);
Router
You can no longer modify named parameter settings with Router::setRequestInfo(). You
should use Router::connectNamed() to configure how named parameters are handled.
Router no longer has a getInstance() method. It is a static class, call its methods and properties
statically.
Router::getNamedExpressions() is deprecated.
Use the new router constants.
Router::ACTION, Router::YEAR, Router::MONTH, Router::DAY, Router::ID, and
Router::UUID instead.
Router::defaults() has been removed. Delete the core routes file inclusion from your applications routes.php file to disable default routing. Conversely if you want default routing, you will have
to add an include to Cake/Config/routes.php in your routes file.
When
using
Router::parseExtensions()
the
under
$this->params[url][ext].
$this->request->params[ext].
extension
parameter
is
no
longer
Instead
it
is
available
at
Default plugin routes have changed. Plugin short routes are no longer built in for any actions other
than index. Previously /users and /users/add would map to the UsersController in the Users
plugin. In 2.0, only the index action is given a short route. If you wish to continue using short
routes, you can add a route like:
Router::connect(
/users/:action,
array(controller => users, plugin => users)
);
To your routes file for each plugin you need short routes on.
Your app/Config/routes.php file needs to be updated adding this line at the bottom of the file:
require CAKE . Config . DS . routes.php;
This is needed in order to generate the default routes for your application. If you do not wish to have such
routes, or want to implement your own standard you can include your own file with custom router rules.
1014
Dispatcher
of
cake/libs,
you
will
have
to
update
your
Dispatcher::dispatch() now takes two parameters. The request and response objects. These
should be instances of CakeRequest & CakeResponse or a subclass thereof.
Dispatcher::parseParams() now only accepts a CakeRequest object.
Dispatcher::baseUrl() has been removed.
Dispatcher::getUrl() has been removed.
Dispatcher::uri() has been removed.
Dispatcher::$here has been removed.
Configure
Configure::read() with no parameter no longer returns the value of debug instead it returns
all values in Configure. Use Configure::read(debug); if you want the value of debug.
Configure::load() now requires a ConfigReader to be setup. Read Loading configuration files
for more information.
Configure::store() now writes values to a given Cache configuration. Read Loading configuration files for more information.
Scaffold
Scaffold edit views should be renamed to form. This was done to make scaffold and bake templates
consistent.
views/scaffolds/edit.ctp -> View/Scaffolds/form.ctp
views/posts/scaffold.edit.ctp -> View/Posts/scaffold.form.ctp
Xml
The class Xml was completely re-factored. Now this class does not manipulate data anymore, and it
is a wrapper to SimpleXMLElement. You can use the following methods:
Xml::build(): static method that you can pass an xml string, array, path to file or url. The
result will be a SimpleXMLElement instance or an exception will be thrown in case of error.
Xml::fromArray(): static method that returns a SimpleXMLElement from an array.
Xml::toArray(): static method that returns an array from SimpleXMLElement.
You should see the Xml documentation for more information on the changes made to the Xml class.
1015
Inflector
CakeSession is now a fully static class, both SessionHelper and SessionComponent are wrappers
and sugar for it. It can now easily be used in models or other contexts. All of its methods are called statically.
Session configuration has also changed see the session section for more information
HttpSocket
HttpSocket doesnt change the header keys. Following other places in core, the HttpSocket does not
change the headers. RFC 26168 says that headers are case insensitive, and HttpSocket preserves the
values the remote host sends.
HttpSocket returns responses as objects now. Instead of arrays, HttpSocket returns instances of
HttpResponse. See the HttpSocket documentation for more information.
Cookies are stored internally by host, not per instance. This means that, if you make two requests to
different servers, cookies from domain1 wont be sent to domain2. This was done to avoid possible
security problems.
Helpers
Constructor changed
In order to accommodate View being removed from the ClassRegistry, the signature of Helper::__construct()
was changed. You should update any subclasses to use the following:
public function __construct(View $View, $settings = array())
When overriding the constructor you should always call parent::__construct as well. Helper::__construct
stores the view instance at $this->_View for later reference. The settings are not handled by the parent
constructor.
HelperCollection added
After examining the responsibilities of each class involved in the View layer, it became clear that View
was handling much more than a single task. The responsibility of creating helpers is not central to what
8
http://tools.ietf.org/html/rfc2616.html
1016
View does, and was moved into HelperCollection. HelperCollection is responsible for loading and constructing helpers, as well as triggering callbacks on helpers. By default, View creates a HelperCollection
in its constructor, and uses it for subsequent operations. The HelperCollection for a view can be found at
$this->Helpers
The motivations for refactoring this functionality came from a few issues.
View being registered in ClassRegistry could cause registry poisoning issues when requestAction or
the EmailComponent were used.
View being accessible as a global symbol invited abuse.
Helpers were not self contained. After constructing a helper, you had to manually construct several
other objects in order to get a functioning object.
You can read more about HelperCollection in the Collections documentation.
Deprecated properties
The following properties on helpers are deprecated, you should use the request object properties or Helper
methods instead of directly accessing these properties as they will be removed in a future release.
Helper::$webroot is deprecated, use the request objects webroot property.
Helper::$base is deprecated, use the request objects base property.
Helper::$here is deprecated, use the request objects here property.
Helper::$data is deprecated, use the request objects data property.
Helper::$params is deprecated, use the $this->request instead.
XmlHelper, AjaxHelper and JavascriptHelper removed
The AjaxHelper and JavascriptHelper have been removed as they were deprecated in version 1.3. The
XmlHelper was removed, as it was made obsolete and redundant with the improvements to Xml. The Xml
class should be used to replace previous usage of XmlHelper.
The AjaxHelper, and JavascriptHelper are replaced with the JsHelper and HtmlHelper.
JsHelper
JsBaseEngineHelper is now abstract, you will need to implement all the methods that previously
generated errors.
PaginatorHelper
PaginatorHelper::sort() now takes the title and key arguments in the reverse order. $key
will always be first now. This was done to prevent needing to swap arguments when adding a second
one.
2.0 Migration Guide
1017
PaginatorHelper had a number of changes to the paging params used internally. The default key has
been removed.
PaginatorHelper now supports generating links with paging parameters in the querystring.
There have been a few improvements to pagination in general. For more information on that you should
read the new pagination features page.
FormHelper
$selected parameter removed The $selected parameter was removed from several methods in
FormHelper. All methods now support a $attributes[value] key now which should be used
in place of $selected. This change simplifies the FormHelper methods, reducing the number of arguments, and reduces the duplication that $selected created. The effected methods are:
FormHelper::select()
FormHelper::dateTime()
FormHelper::year()
FormHelper::month()
FormHelper::day()
FormHelper::hour()
FormHelper::minute()
FormHelper::meridian()
Default URLs on forms is the current action The default url for all forms, is now the current
url including passed, named, and querystring parameters. You can override this default by supplying
$options[url] in the second parameter of $this->Form->create().
FormHelper::hidden() Hidden fields no longer remove the class attribute. This means that if there are
validation errors on hidden fields, the error-field class name will be applied.
CacheHelper
CacheHelper has been fully decoupled from View, and uses helper callbacks to generate caches. You
should remember to place CacheHelper after other helpers that modify content in their afterRender
and afterLayout callbacks. If you dont some changes will not be part of the cached content.
CacheHelper also no longer uses <cake:nocache> to indicate un-cached regions. Instead it uses special
HTML/XML comments. <!--nocache--> and <!--/nocache-->. This helps CacheHelper generate valid markup and still perform the same functions as before. You can read more CacheHelper and View
changes.
1018
with
minimized
attributes
(ie:
Other facility is that minimized attributes can be passed as item and not as key. For example:
$this->Form->checkbox(field, array(checked, value => some_value));
1019
Pagination
Pagination has traditionally been a single method in Controller, this created a number of problems though.
Pagination was hard to extend, replace, or modify. For 2.0 pagination has been extracted into a component.
Controller::paginate() still exists, and serves as a convenience method for loading and using the
PaginatorComponent.
For more information on the new features offered by pagination in 2.0, see the Pagination documentation.
View
View no longer registered in ClassRegistry
The view being registered ClassRegistry invited abuse and affectively created a global symbol. In 2.0 each
Helper receives the current View instance in its constructor. This allows helpers access to the view in a similar
fashion as in the past, without creating global symbols. You can access the view instance at $this->_View in
any helper.
Deprecated properties
1020
Removed methods
To use themes in your Controller you no longer set public $view = Theme;. Use public
$viewClass = Theme; instead.
Callback positioning changes
beforeLayout used to fire after scripts_for_layout and content_for_layout were prepared. In 2.0, beforeLayout is fired before any of the special variables are prepared, allowing you to manipulate them before they are
passed to the layout. The same was done for beforeRender. It is now fired well before any view variables
are manipulated. In addition to these changes, helper callbacks always receive the name of the file about to
be rendered. This combined with helpers being able to access the view through $this->_View and the
current view content through $this->_View->output gives you more power than ever before.
Helper callback signature changes
Helper callbacks now always get one argument passed in. For beforeRender and afterRender it is the view
file being rendered. For beforeLayout and afterLayout it is the layout file being rendered. Your helpers
function signatures should look like:
public function beforeRender($viewFile) {
}
public function afterRender($viewFile) {
}
1021
Element caching, and view callbacks have been changed in 2.0 to help provide you with more flexibility and
consistency. Read more about those changes.
CacheHelper decoupled
In previous versions there was a tight coupling between CacheHelper and View. For 2.0 this coupling
has been removed and CacheHelper just uses callbacks like other helpers to generate full page caches.
CacheHelper <cake:nocache> tags changed
In previous versions, CacheHelper used a special <cake:nocache> tag as markers for output that should
not be part of the full page cache. These tags were not part of any XML schema, and were not possible to
validate in HTML or XML documents. For 2.0, these tags have been replaced with HTML/XML comments:
<cake:nocache> becomes <!--nocache-->
</cake:nocache> becomes <!--/nocache-->
The internal code for full page view caches has also changed, so be sure to clear out view cache files when
updating.
MediaView changes
MediaView::render() now forces download of unknown file types instead of just returning false. If
you want you provide an alternate download filename you now specify the full name including extension
using key name in the array parameter passed to the function.
PHPUnit instead of SimpleTest
All of the core test cases and supporting infrastructure have been ported to use PHPUnit 3.5. Of course you
can continue to use SimpleTest in your application by replacing the related files. No further support will
be given for SimpleTest and it is recommended that you migrate to PHPUnit as well. For some additional
information on how to migrate your tests see PHPUnit migration hints.
No more group tests
PHPUnit does not differentiate between group tests and single test cases in the runner. Because of this,
the group test options, and support for old style group tests has been removed. It is recommended that
1022
The testsuite shell has had its invocation simplified and expanded. You no longer need to differentiate
between case and group. It is assumed that all tests are cases. In the past you would have done cake
testsuite app case models/post you can now do cake testsuite app Model/Post.
The testsuite shell has been refactored to use the PHPUnit cli tool. It now supports all the command line
options supported by PHPUnit. cake testsuite help will show you a list of all possible modifiers.
Model
Model relationships are now lazy loaded. You can run into a situation where assigning a value to a nonexistent model property will throw errors:
$Post->inexistentProperty[] = value;
will throw the error Notice: Indirect modification of overloaded property $inexistentProperty has no effect.
Assigning an initial value to the property solves the issue:
$Post->nonexistentProperty = array();
$Post->nonexistentProperty[] = value;
CakePHP 2.0 introduces some changes to Database objects that should not greatly affect backwards compatibility. The biggest one is the adoption of PDO for handling database connections. If you are using a
vanilla installation of PHP 5 you will already have installed the needed extensions, but you may need to
activate individual extensions for each driver you wish to use.
Using PDO across all DBOs let us homogenize the code for each one and provide more reliable and predictable behavior for all drivers. It also allowed us to write more portable and accurate tests for database
related code.
1023
The first thing users will probably miss is the affected rows and total rows statistics, as they are not
reported due to the more performant and lazy design of PDO, there are ways to overcome this issue but very
specific to each database. Those statistics are not gone, though, but could be missing or even inaccurate for
some drivers.
A nice feature added after the PDO adoption is the ability to use prepared statements with query placeholders
using the native driver if available.
List of Changes
DboMysqli was removed, we will support DboMysql only.
API for DboSource::execute has changed, it will now take an array of query values as second parameter:
public function execute($sql, $params = array(), $options = array())
became:
public function execute($sql, $options = array(), $params = array())
third parameter is meant to receive options for logging, currently it only understands the log option.
DboSource::value() looses its third parameter, it was not used anyways
DboSource::fetchAll() now accepts an array as second parameter, to pass values to be bound to the
query, third parameter was dropped. Example:
$db->fetchAll(
SELECT
* from users
WHERE
username = ?
AND
password = ?,
array(jhon, 12345)
);
$db->fetchAll(
SELECT
* from users
WHERE
username = :username
AND
password = :password,
array(username => jhon, password => 12345)
);
The PDO driver will automatically escape those values for you.
Database statistics are collected only if the fullDebug property of the corresponding DBO is set to
true.
New method DboSource::getConnection() will return the PDO object in case you need to talk to the
driver directly.
1024
Treatment of boolean values changed a bit to make it more cross-database friendly, you may need to
change your test cases.
Postgresql support was immensely improved, it now correctly creates schemas, truncate tables, and is
easier to write tests using it.
DboSource::insertMulti() will no longer accept sql string, just pass an array of fields and a nested
array of values to insert them all at once
TranslateBehavior was refactored to use model virtualFields, this makes the implementation more
portable.
All tests cases with Mysql related stuff were moved to the corresponding driver test case. This left the
DboSourceTest file a bit skinny.
Transaction nesting support. Now it is possible to start a transaction several times. It will only be
committed if the commit method is called the same amount of times.
Sqlite support was greatly improved. The major difference with cake 1.3 is that it will only support
Sqlite 3.x . It is a great alternative for development apps, and quick at running test cases.
Boolean column values will be casted to php native boolean type automatically, so make sure you
update your test cases and code if you were expecting the returned value to be a string or an integer:
If you had a published column in the past using mysql all values returned from a find would be
numeric in the past, now they are strict boolean values.
Behaviors
BehaviorCollection
BehaviorCollection no longer strtolower()s mappedMethods. Behavior mappedMethods are now case sensitive.
AclBehavior and TreeBehavior
became:
public $actsAs = array(
Acl => array(type => Controlled),
Tree => array(type => nested)
);
1025
Plugins
Plugins no longer magically append their plugin prefix to components, helpers and models used within them.
You must be explicit with the components, models, and helpers you wish to use. In the past:
public $components = array(Session, Comments);
Would look in the controllers plugin before checking app/core components. It will now only look in the
app/core components. If you wish to use objects from a plugin you must put the plugin name:
public $components = array(Session, Comment.Comments);
This was done to reduce hard to debug issues caused by magic misfiring. It also improves consistency in an
application, as objects have one authoritative way to reference them.
Plugin App Controller and Plugin App Model
The plugin AppController and AppModel are no longer located directly in the plugin folder. They are now
placed into the plugins Controller and Model folders as such:
/app
/Plugin
/Comment
/Controller
CommentAppController.php
/Model
CommentAppModel.php
Console
Much of the console framework was rebuilt for 2.0 to address many of the following issues:
Tightly coupled.
It was difficult to make help text for shells.
Parameters for shells were tedious to validate.
Plugin tasks were not reachable.
Objects with too many responsibilities.
Backwards incompatible Shell API changes
Shell no longer has an AppModel instance. This AppModel instance was not correctly built and
was problematic.
Shell::_loadDbConfig() has been removed. It was not generic enough to stay in Shell. You
can use the DbConfigTask if you need to ask the user to create a db config.
1026
They have
Shells lazy load tasks, and use TaskCollection to provide an interface similar to that used for
Helpers, Components, and Behaviors for on the fly loading of tasks.
Shell::$shell has been removed.
Shell::_checkArgs() has been removed. Configure a ConsoleOptionParser
Shells no longer have direct access to ShellDispatcher. You should use the ConsoleInput,
and ConsoleOutput objects instead. If you need to dispatch other shells, see the section on Invoking other shells from your shell.
Backwards incompatible ShellDispatcher API changes
Bakes ControllerTask no longer takes public and admin as passed arguments. They are now
options, indicated like --admin and --public.
Its recommended that you use the help on shells you use to see what if any parameters have changed.
Its also recommended that you read the console new features for more information on new APIs that are
available.
Debugging
The debug() function now defaults to outputting HTML safe strings. This is disabled if being used in
the console. The $showHtml option for debug() can be set to false to disable HTML-safe output from
debug.
1027
ConnectionManager
ConnectionManager::enumConnectionObjects() will now return the current configuration for
each connection created, instead of an array with filename, class name and plugin, which wasnt really
useful.
When defining database connections you will need to make some changes to the way configs were defined
in the past. Basically in the database configuration class, the key driver is not accepted anymore, only
datasource, in order to make it more consistent. Also, as the datasources have been moved to packages
you will need to pass the package they are located in. Example:
public $default = array(
datasource => Database/Mysql,
persistent => false,
host => localhost,
login => root,
password => root,
database => cake,
);
View will now always have the last rendered content (view or layout) accessible through $this->output.
In helpers you can use $this->_View->output. Modifying this property will change the content that
comes out of the view rendering.
Helpers
HtmlHelper
http://cakephp.lighthouseapp.com/projects/42648/tickets/856
1028
loadConfig() has moved from Helper to HtmlHelper class. This method now uses the new
reader classes (see 2.0 Configure) to load your config file. As an option you can pass the path as
second parameter (app/Config is default). To simplify, you can set the configuration file (and the
reader) in Controller::$helpers (example below) to load on helper constructor. In configuration file you can set the below keys:
tags Should be an array with key value;
minimizedAttributes Should be a list;
docTypes Should be an array with key value;
attributeFormat Should be a string;
minimizedAttributeFormat Should be a string.
Example of how to set configuration file on controller:
public $helpers = array(
Html => array(
// Option one: an array with filename and reader name
configFile => array(config_file, php)
// Option two: a string with filename. The PhpReader will be used
configFile => config_file
)
);
FormHelper
FormHelper now supports all HTML5 input types and custom input types. Just use the input type
you want as the method on the helper. For example range() would create an input with type =
range.
postLink() and postButton() Creates link/button to access some page using HTTP method
POST. With this, in your controller you can avoid some action, like delete, to be accessed by GET
method.
select() with multiple = checkbox, now treats the id attribute as a prefix for all the generated
options.
Libs
CakeRequest
CakeRequest is a new class introduced in 2.0. It encapsulates commonly used request introspection
methods and replaces the params array with a more useful object. Read more about CakeRequest.
1029
CakeResponse
CakeResponse is a new class introduced in 2.0. It encapsulates commonly used methods and properties
in the HTTP response your application generates. It consolidates several features in CakePHP. Read more
about CakeResponse.
CakeSession, SessionComponent
CakeSession and the SessionComponent have had a number of changes, see the session section for
more information.
Router
Routes can return full URLs Route objects can now return full URLs, and Router will not further
modify them beyond adding the query string and fragment elements. For example this could be used to
create routes to handle subdomains, or enabling https/http flags. An example of a route class that supports
subdomains would be:
class SubdomainRoute extends CakeRoute {
public function match($params) {
$subdomain = isset($params[subdomain]) ? $params[subdomain] : null;
unset($params[subdomain]);
$path = parent::match($params);
if ($subdomain) {
$path = http:// . $subdomain . .localhost . $path;
}
return $path;
}
}
When creating links you could do the following to make links pointing at other subdomains.
echo $this->Html->link(
Other domain,
array(subdomain => test, controller => posts, action => add)
);
Xml has had a number of changes. Read more about Xml class.
1030
Configure can now be configured to load configuration files from a variety of sources and formats. The
Configuration section contains more information about the changes made to configure.
Configure::read() without any arguments allows you to read all values from configure, instead of
just the debug value.
Error and exception handling
CakePHP 2.0 has had Exceptions and Error Handling handling rebuilt, to be more flexible and give more
power to developers.
String::wrap()
String::wrap() was added to help make fixed width formatting of text easier. Its used in Shells
whenever you use Shell::wrapText().
debug()
debug() no longer outputs HTML in the console. Instead it makes output like the following:
########## DEBUG ##########
Array
(
[0] => test
)
###########################
RequestHandler was heavily refactored due to the introduction of CakeRequest. These changes
allowed for some new features to be introduced as well.
1031
Automatic parsing of Accept headers If a client sends a single Accept mime type that matches one of
the extensions enabled in :php:classRouter, RequestHandler will treat it the same an extension. This
expands CakePHPs support for REST style endpoints. To use this feature start off by enabling extensions
in app/Config/routes.php:
Router::parseExtensions(json, xml);
Once you have created layouts and views for your extensions, you will be able to visit a url like posts/view/1
and send Accept: application/json in the headers to receive the JSON version of that URL.
CookieComponent
CakePHP has had CSRF protection since 1.2. For 2.0 the existing CSRF has a new more paranoid mode,
and is its own standalone feature. In the past CSRF features were coupled with form tampering safe-guards.
Developers often disabled validatePost in order to do dynamic forms, disabling the CSRF protection at the
same time. For 2.0 CSRF checking has been separated from form tampering giving you greater control.
For more information see CSRF protection
Controller
Controllers now have access to request and response objects. You can read more about these objects on their
specific pages.
Console
The console for CakePHP 2.0 was almost entirely rebuilt. Several new features as well as some backwards
incompatible changes were made. Read more about console changes.
Pagination
Pagination now provides a default maxLimit for pagination at 100.
This limit can be overridden with the paginate variable on Controller:
$this->paginate = array(maxLimit => 1000);
This default is provided to prevent user URL manipulation causing excessive strain on the database for
subsequent requests, where a user would edit the limit parameter to a very large number.
1032
Aliasing
You can now alias helpers, components and behaviors to use your class instead of a different one. This means
that you can very easily make a MyHtml helper and not need to replace every instance of $this->Html
in your views. To do this, pass the className key along with your class, like you would with models:
public $helpers = array(
Html => array(
className => MyHtml
)
);
Calls to the Email component would call the QueueEmailer component instead. Finally, you can alias
behaviors as well:
public $actsAs = array(
Containable => array(
className => SuperContainable
)
);
Because of the way 2.0 utilizes collections and shares them across the application, any classes you alias will
be used throughout your application. Whenever your application tries to access the alias, it will access your
class. For instance, when we aliased the Html helper in the example above, any helpers that use the Html
helper or elements that load the Html helper, will use MyHtml instead.
ConnectionManager
A new method ConnectionManager::drop() was added to allow removing connections at runtime.
http://www.phpunit.de/manual/current/en/
http://www.simpletest.org/
1033
Installing PHPUnit
Installing from PEAR (Recommended)
PHPUnit recommends it be installed via the PEAR installer, to do so run the following commands:
pear channel-discover pear.phpunit.de
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com
This has to be done only once. Now the PEAR Installer12 can be used to install packages from the PHPUnit
channel:
pear install phpunit/PHPUnit
Installing manually
In addition to PEAR, CakePHP also supports placing the PHPUnit directory inside one of your vendors
directories along with all its dependencies. Doing so is generally not recommended as PHPUnit is complicated to install as it is composed of many PEAR packages. Installing with the PEAR installer is easier and
faster.
If you do wish to install PHPUnit manually, youll need to place it and all of its dependencies inside your
applications Vendor directory.
Differences between SimpleTest
There are a number of differences between SimpleTest and PHPUnit. The following is an attempt to list the
most frequently encountered differences.
startCase() and endCase()
These methods are no longer supported. Use the static methods PHPUnit provides: setupBeforeClass
and tearDownAfterClass.
start(), end(), before() and after()
These methods were part of SimpleTests test case initialization. start() and end() have no replacements. You can use setUp() and tearDown() to replace before() and after().
setUp() and tearDown()
In the past the methods setUp, tearDown, startTest and endTest where supported, and caused
confusion as they looked almost like the same thing but in some cases you should use one or the other.
12
http://pear.php.net/
1034
In the new CakePHP test suite, it is recommended to use only setUp and tearDown. The methods
startTest and endTest are still supported but are deprecated.
getTests
The method getTests is no longer supported. You can use filters instead. The web test runner now takes
an additional query string parameter that allows you to specify a basic regular expression. This regular
expression is used to restrict the methods that are run:
e.g. filter=myMethod
Only tests containing the string myMethod will be run on the next refresh. The cake test shell also supports
a filter option to filter methods.
Assertion methods
Many of the assertion methods have slightly different names between PHPUnit and SimpleTest. Where possible CakeTestCase provides a wrapper for the SimpleTest method names. These compatibility wrappers
will be removed in 2.1.0. The following methods will be affected.
assertEqual -> assertEquals
assertNotEqual -> assertNotEquals
assertPattern -> assertRegExp
assertIdentical -> assertSame
assertNotIdentical -> assertNotSame
assertNoPattern -> assertNotRegExp
assertNoErrors -> no replacement
expectError -> setExpectedException
expectException -> setExpectedException
assertReference -> assertSame
assertIsA -> assertType
Some methods take their arguments in different orders, be sure to check the methods you are using when
updating them.
Mock expectations
Mock objects are dramatically different between PHPUnit and SimpleTest. There is no compatibility wrapper between them. Updating mock object usage can be a painful process but we hope the following tips help
you in your migration. Its highly recommended you familiarize yourself with the PHPUnit Mock object13
documentation.
13
http://www.phpunit.de/manual/current/en/test-doubles.html#test-doubles.mock-objects
1035
Replacing method calls The following regular expressions should help update some of your more
straightforward mock object expectations.
Replace expectOnce() no params
expectOnce\(([^\)]+)\);
expects(\$this->once())->method( $ 1);
Replace expectAt()
expectAt\((\d+), (.+), array\((.+)\)\);
expects(\$this->at( $ 1))->method( $ 2)->with( $ 3);
Replace expectNever
expectNever\(([^\)]+)\);
expects(\$this->never())->method( $ 1);
Replace setReturnValue
setReturnValue\(([^,]+), (.+)\);
expects(\$this->once())->method( $ 1)->will($this->returnValue( $ 2));
Replace setReturnValueAt
setReturnValueAt((\d+), ([^,]+), (.+));
expects(\$this->at( $ 1))->method( $ 2)->will($this->returnValue( $ 3));
Group tests
Group tests have been removed as PHPUnit treats individual test cases and test suites as composable entities in the runner.
You can place group tests inside the cases directory and use
PHPUnit_Framework_TestSuite as a base class. An example Testsuite would look like:
class AllJavascriptHelpersTest extends PHPUnit_Framework_TestSuite {
/**
* Suite define the tests for this suite
*
* @return void
*/
public static function suite() {
1036
}
}
TestManger no longer has methods to add tests to group tests either. It is recommended that you use the
methods PHPUnit offers.
1037
1038
/full/path/to/libs/,
/next/full/path/to/libs/
)
));
Also changed is the order in which bootstrapping occurs. In the past app/config/core.php was loaded
after app/config/bootstrap.php. This caused any App::import() in an application bootstrap
to be un-cached and considerably slower than a cached include. In 1.3 core.php is loaded and the core cache
configs are created before bootstrap.php is loaded.
Loading custom inflections
inflections.php has been removed, it was an unnecessary file hit, and the related features have been
refactored into a method to increase their flexibility. You now use Inflector::rules() to load custom
inflections:
Inflector::rules(singular, array(
rules => array(
/^(bil)er$/i => \1,
/^(inflec|contribu)tors$/i => \1ta
),
uninflected => array(singulars),
irregular => array(spins => spinor)
));
Will merge the supplied rules into the infection sets, with the added rules taking precedence over the core
rules.
File renames and internal changes
Library Renames
Core libraries of libs/session.php, libs/socket.php, libs/model/schema.php and libs/model/behavior.php have
been renamed so that there is a better mapping between filenames and main classes contained within (as well
as dealing with some name-spacing issues):
session.php -> cake_session.php
App::import(Core, Session) -> App::import(Core, CakeSession)
socket.php -> cake_socket.php
App::import(Core, Socket) -> App::import(Core, CakeSocket)
schema.php -> cake_schema.php
App::import(Model, Schema) -> App::import(Model, CakeSchema)
behavior.php -> model_behavior.php
App::import(Core, Behavior) -> App::import(Core, ModelBehavior)
In most cases, the above renaming will not affect userland code.
Inheritance from Object
1039
The path ROOT/Users would match the last Users node instead of the first. In 1.3, if you were expecting
to get the last node you would need to use the path ROOT/Users/Users
RequestHandlerComponent
getReferrer is deprecated use getReferer
1040
These change were done to make CakePHP more explicit and declarative in what classes you the application
developer want to use. In the past there was no way to avoid loading the Session classes without modifying
core files. Which is something we want you to be able to avoid. In addition Session classes were the only
magical component and helper. This change helps unify and normalize behavior amongst all classes.
Library Classes
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 and change
instances of $content_for_layout to $message.
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
Migration from 1.2 to 1.3
1041
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-_+]+/:
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:
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:
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.
// old format:
$url = array(controller => posts, action => view, id => $id);
// use cases:
Router::url($url);
$html->link($url);
1042
$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 controllers 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")
Debugger::output("txt").
no
longer
works.
Use
Object
Object::$_log has been removed. CakeLog::write is now called statically. See Logging 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 the encode 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.
Configure::load(plugin.file); to load configuration files from plugins.
configuration files in your application that use . in the name should be updated to use _
Use
Any
1043
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 cache configuration names, not cache engine names. You can still use Cache::set() or
Cache::engine() to modify cache configurations. Also checkout the New features in CakePHP 1.3 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.
Model Databases and Datasources
Model
Model::del() and Model::remove() have been removed in favor of Model::delete(),
which is now the canonical delete method.
Model::findAll, findCount, findNeighbours, removed.
Dynamic calling of setTablePrefix() has been removed.
tableprefix should be with the
$tablePrefix property, and any other custom construction behavior should be done in an overridden Model::__construct().
DboSource::query() now throws warnings for un-handled model methods, instead of trying to run them as queries. This means, people starting transactions improperly via the
$this->Model->begin() syntax will need to update their code so that it accesses the models
DataSource object directly.
Missing validation methods will now trigger errors in development mode.
Missing behaviors will now trigger a cakeError.
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() or update() correctly without some ugly hacks. If you have custom
1044
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 pre1.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:
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.
View and Helpers
View
View::renderElement removed. Use View::element() instead.
Automagic support for .thtml view file extension has been removed either declare $this->ext
= thtml; in your controllers, or rename your views to use .ctp
View::set(title, $var) no longer sets $title_for_layout when rendering the layout. $title_for_layout is still populated by default. But if you want to customize it, use
$this->set(title_for_layout, $var).
View::$pageTitle has been removed.
$var); instead.
Use $this->set(title_for_layout,
The $cakeDebug layout variable associated with debug = 3 has been removed. Remove it from
your layouts as it will cause errors. Also see the notes related to SQL log dumping and Configure for
more information.
All core helpers no longer use Helper::output(). The method was inconsistently used and caused
output issues with many of FormHelpers methods. If you previously overrode AppHelper::output()
to force helpers to auto-echo you will need to update your view files to manually echo helper output.
TextHelper
TextHelper::trim() is deprecated, used truncate() instead.
TextHelper::highlight() no longer has:
an $highlighter parameter. Use $options[format] instead.
1045
an $considerHtmlparameter.
PaginatorHelper
PaginatorHelper has had a number of enhancements applied to make styling easier. prev(), next(),
first() and last()
The disabled state of these methods now defaults to <span> tags instead of <div> tags.
passedArgs are now auto merged with URL options in paginator.
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
$attributes[empty] instead.
longer
FormHelper::year() no longer
$attributes[empty] instead.
FormHelper::month() no longer
$attributes[empty] instead.
FormHelper::day()
no
longer
$attributes[empty] instead.
has
has
longer
FormHelper::select() no longer
$attributes[empty] instead.
has
FormHelper::minute() no longer
$attributes[empty] instead.
FormHelper::meridian() no
$attributes[empty] instead.
has
a
a
has
Use
parameter.
Use
$showEmpty
parameter.
Use
$showEmpty
a
parameter.
$showEmpty
$showEmpty
has
has
$showEmpty
$showEmpty
$showEmpty
parameter.
parameter.
parameter.
parameter.
Use
Use
Use
Use
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 use FormHelper::submit() with a type =>
reset option for example.
1046
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 2.0 updates 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. The escape option now controls the escaping of the title
and attributes at the same time.
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
CacheHelpers 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 controllers 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().
Migration from 1.2 to 1.3
1047
or
ShellDispatcher::stderr() has been modified to not prepend Error: to the message anymore. Its
signature is now similar to Shell::stdout().
ShellDispatcher::shiftArgs()
The method has been modified to return the shifted argument. Before if no arguments were available the
method was returning false, it now returns null. Before if arguments were available the method was returning
true, it now returns the shifted argument instead.
Vendors, Test Suite & schema
vendors/css, vendors/js, and vendors/img
1048
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.
Vendor, plugin and theme assets
Vendor asset serving has been removed in 1.3 in favour of plugin and theme webroot directories.
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.
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.
var $components = array(
Cookie => array(
name => MyCookie
),
Auth => array(
userModel => MyUser,
loginAction => array(controller => users, action => login)
)
);
1049
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.
View & Helpers
Helpers can now be addressed at $this->Helper->func() in addition to $helper->func(). This
allows view variables and helpers to share names and not create collisions.
New JsHelper and new features in HtmlHelper
See JsHelper documentation for more information
Pagination Helper
Pagination helper provides additional CSS classes for styling and you can set the default sort() direction.
PaginatorHelper::next() and PaginatorHelper::prev() now generate span tags by default, instead of divs.
Helper
Helper::assetTimestamp() has been added.
It will add timestamps to any asset under
WWW_ROOT. It works with Configure::read(Asset.timestamp); just as before, but the
functionality used in Html and Javascript helpers has been made available to all helpers. Assuming
Asset.timestamp == force
$path = css/cake.generic.css
$stamped = $this->Html->assetTimestamp($path);
//$stamped contains css/cake.generic.css?5632934892
The appended timestamp contains the last modification time of the file. Since this method is defined in
Helper it is available to all subclasses.
TextHelper
highlight() now accepts an array of words to highlight.
NumberHelper
A new method addFormat() has been added. This method allows you to set currency parameter sets, so
you dont have to retype them:
$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 improvements14 for more information.
14
http://book.cakephp.org/view/1616/x1-3-improvements
1050
Logging
Logging and CakeLog have been enhanced considerably, both in features and flexibility. See New Logging
features15 for more information.
Caching
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:
Cache::config(custom, array(
engine => CachePack.MyCustomCache,
...
));
App and Plugin cache engines should be configured in app/bootstrap.php. If you try to configure
them in core.php they will not work correctly.
New Cache methods
Cache has a few new methods for 1.3 which make introspection and testing teardown easier.
Cache::configured() returns an array of configured Cache engine keys.
Cache::drop($config) drops a configured Cache engine. Once dropped cache engines are no
longer readable or writeable.
Cache::increment() Perform an atomic increment on a numeric value. This is not implemented
in FileEngine.
Cache::decrement() Perform an atomic decrement on a numeric value. This is not implemented
in FileEngine.
Models, Behaviors and Datasource
App::import(), datasources & datasources from plugins
Datasources can now be included loaded with App::import() and be included in plugins!
To include a datasource in your plugin you put it in
my_plugin/models/datasources/your_datasource.php. To import a Datasource from a
plugin use App::import(Datasource, MyPlugin.YourDatasource);
Using plugin datasources in your database.php
You can use plugin datasources by setting the datasource key with the plugin name.
For example if you had a WebservicePack plugin with a LastFm datasource (plugin/webservice_pack/models/datasources/last_fm.php), you could do:
15
http://book.cakephp.org/view/1194/Logging
1051
Model
Missing Validation methods now trigger errors, making debugging why validation isnt working easier.
Models now support virtual fields16
Behaviors
Using behaviors that do not exist, now triggers a cakeError making missing behaviors easier to find and
fix.
CakeSchema
CakeSchema can now locate, read and write schema files to plugins. The SchemaShell also exposes this
functionality, see below for changes to SchemaShell. CakeSchema also supports tableParameters.
Table Parameters are non column specific table information such as collation, charset, comments, and table
engine type. Each Dbo implements the tableParameters they support.
tableParameters in MySQL
MySQL supports the greatest number of tableParameters; You can use tableParameters to set a variety of
MySQL specific settings.
engine Control the storage engine used for your tables.
charset Control the character set used for tables.
encoding Control the encoding used for tables.
In addition to tableParameters MySQL dbos implement fieldParameters. fieldParameters allow you to control MySQL specific settings per column.
charset Set the character set used for a column
encoding Set the encoding used for a column
See below for examples on how to use table and field parameters in your schema files.
tableParameters in Postgres
tableParameters in SQLite
http://book.cakephp.org/view/1608/Virtual-fields
1052
is an example of a table using tableParameters to set some database specific settings. If you use
a schema file that contains options and features your database does not implement, those options will be
ignored. For example if you imported the above schema to a PostgreSQL server, all of the tableParameters
would be ignore as PostgreSQL does not support any of the included options.
Console
Bake
Bake has had a number of significant changes made to it. Those changes are detailed in the bake updates
section17
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 youre 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.
17
http://book.cakephp.org/view/1611/Bake-improvements-in-1-3
1053
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
Likewise, if you are in a prefixed URL and want to go to a non-prefixed URL, do the following:
$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 the parse() and/or match() methods
found in CakeRoute to provide custom handling.
1054
Dispatcher
Accessing filtered asset paths, while having no defined asset filter will create 404 status code responses.
Library classes
Inflector
You can now globally customize the default transliteration map used in Inflector::slug using Inflector::rules.
Example 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 callbacks18 to the results of
Set::extract and do so in either a map or reduce fashion:
Set::apply(/Movie/rating, $data, array_sum);
Use
Any
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 organizations internal
libraries from vendor libraries. App::import() has also been updated to import from libs directories:
App::import(Lib, ImageManipulation); //imports app/libs/image_manipulation.php
http://ca2.php.net/callback
1055
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:
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 page19
Miscellaneous
Error Handling
Subclasses of ErrorHandler can more easily implement additional error methods. In the past you would
need to override __construct() and work around ErrorHandlers 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:
Configure::write(Routing.prefixes, array(admin, member));
class PostsController extends AppController {
var $scaffold = member;
}
http://sunsson.iptime.org/susv3/basedefs/xbd_chap07.html
1056
bloated ugly methods, and still not afford the flexibility needed for all cases, an alternate path was taken. In
1.3, phone() and postal() 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:
class NlValidation {
public function phone($check) {
...
}
public 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:
public $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:
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.
General Information
Glossary
routing array An array of attributes that are passed to Router::url(). They typically look like:
array(controller => posts, action => view, 5)
HTML attributes An array of key => values that are composed into HTML attributes. For example:
General Information
1057
// Given
array(class => my-class, target => _blank)
// Would generate
class="my-class" target="_blank"
If an option can be minimized or accepts its name as the value, then true can be used:
// Given
array(checked => true)
// Would generate
checked="checked"
plugin syntax Plugin syntax refers to the dot separated class name indicating classes are part of a plugin.
E.g. DebugKit.Toolbar The plugin is DebugKit, and the class name is Toolbar.
dot notation Dot notation defines an array path, by separating nested levels with . For example:
Asset.filter.css
CSRF Cross Site Request Forgery. Prevents replay attacks, double submissions and forged requests from
other domains.
routes.php A file in APP/Config that contains routing configuration. This file is included before each
request is processed. It should connect all the routes your application needs so requests can be routed
to the correct controller + action.
DRY Dont repeat yourself. Is a principle of software development aimed at reducing repetition of information of all kinds. In CakePHP DRY is used to allow you to code things once and re-use them across
your application.
1058
CHAPTER 17
genindex
1059
1060
Index
Symbols
B
BadRequestException, 870
base (CakeRequest property), 66
base (RssHelper property), 179, 474, 656
beforeDelete() (ModelBehavior method), 316, 389
beforeFilter() (Controller method), 49
beforeFind() (ModelBehavior method), 316, 389
beforeLayout() (Helper method), 199, 493
beforeRedirect() (Component method), 80, 394
beforeRender() (Component method), 80, 394
beforeRender() (Controller method), 50
beforeRender() (Helper method), 199, 493
beforeRenderFile() (Helper method), 199, 493
beforeSave() (ModelBehavior method), 316, 389
beforeScaffold() (Controller method), 53
beforeValidate() (ModelBehavior method), 316, 389
bindTranslation() (TranslateBehavior method), 297,
370, 509
blackHole() (SecurityComponent method), 549
blackHoleCallback (SecurityComponent property),
549
blank() (Validation method), 269
Blocks (View property), 93
blocks() (View method), 92
body() (CakeResponse method), 73
boolean() (Validation method), 269
buffer() (JsHelper method), 150, 444, 626
build() (App method), 342
build() (Xml method), 786
buildFromArray() (ConsoleOptionParser method),
809
button() (FormHelper method), 123, 418, 600
1062
C
Cache (class), 678
CACHE (global constant), 338
cache() (CakeResponse method), 72
cacheAction (Controller property), 59
CacheEngine (class), 674
CacheHelper (class), 99, 394, 575
CAKE (global constant), 338
CAKE_CORE_INCLUDE_PATH (global constant),
338
CakeBaseException, 871
CakeEmail (class), 680
CakeException, 871
CakeLog (class), 728
CakeNumber (class), 730
CakeRequest (class), 64
CakeResponse (class), 72
CakeRoute (class), 862
CakeSession (class), 868
CakeTime (class), 774
camelize() (Inflector method), 719
cc() (Validation method), 269
cd() (Folder method), 688
channel() (RssHelper method), 180, 474, 657
charset() (CakeResponse method), 72
charset() (HtmlHelper method), 129, 423, 606
check() (Configure method), 838
check() (CookieComponent method), 562
check() (Hash method), 703
check() (SessionComponent method), 531
check() (SessionHelper method), 181, 475, 658
check() (Set method), 741
checkbox() (FormHelper method), 118, 413, 595
checkNotModified() (CakeResponse method), 72
childCount() (TreeBehavior method), 305, 379, 518
children() (TreeBehavior method), 305, 378, 517
chmod() (Folder method), 688
cipher() (Security method), 738
classicExtract() (Set method), 742
classify() (Inflector method), 719
cleanInsert() (String method), 769
cleanup() (ModelBehavior method), 316, 389
clear() (Cache method), 679
clear() (CacheEngine method), 675
clear() (Shell method), 811
clearGroup() (Cache method), 679
clearGroup() (CacheEngine method), 675
Index
D
data (CakeRequest property), 66
data (RssHelper property), 179, 474, 656
data() (CakeRequest method), 65
database.php, 831
database.php.default, 831
date() (Validation method), 271
dateTime() (FormHelper method), 125, 419, 601
datetime() (Validation method), 271
DAY (global constant), 340
day() (FormHelper method), 126, 421, 603
dayAsSql() (CakeTime method), 774
dayAsSql() (TimeHelper method), 189, 483, 666
daysAsSql() (CakeTime method), 775
daysAsSql() (TimeHelper method), 189, 483, 666
debug() (CakeLog method), 730
debug() (global function), 337, 878
Debugger (class), 879
decimal() (Validation method), 272
decrement() (Cache method), 679
decrement() (CacheEngine method), 675
decrypt() (Security method), 739
defaultCurrency() (CakeNumber method), 732
defaultCurrency() (NumberHelper method), 162,
456, 638
defaultLevels() (CakeLog method), 729
defaultModel() (PaginatorHelper method), 175, 470,
652
defaultRouteClass() (Router method), 862
delete() (Cache method), 678
delete() (CacheEngine method), 675
1063
E
each() (JsHelper method), 156, 451, 633
effect() (JsHelper method), 155, 449, 632
elem() (RssHelper method), 180, 474, 657
element() (View method), 92
elementCache (View property), 93
email() (Validation method), 272
emailPattern() (CakeEmail method), 686
emergency() (CakeLog method), 730
enable() (CakeLog method), 730
1064
F
fetch() (View method), 93
field (RssHelper property), 180, 474, 657
File (class), 693
file extensions, 852
file() (CakeResponse method), 73
file() (FormHelper method), 122, 417, 599
fileExistsInPath() (global function), 337
fileSize() (Validation method), 273
filter() (Hash method), 703
filter() (Set method), 753
find() (Folder method), 690
findRecursive() (Folder method), 690
first() (PaginatorHelper method), 170, 465, 647
flash (AuthComponent property), 546
flash() (AuthComponent method), 547
flash() (Controller method), 53
flash() (SessionHelper method), 181, 476, 658
Index
Index
1065
J
JS (global constant), 339
1066
L
label() (FormHelper method), 116, 411, 593
last() (PaginatorHelper method), 170, 465, 647
lastAccess() (File method), 695
lastChange() (File method), 695
layout (View property), 93
lengthBetween() (Validation method), 268
levels() (CakeLog method), 729
link() (HtmlHelper method), 134, 428, 610
link() (JsHelper method), 157, 452, 634
link() (PaginatorHelper method), 175, 469, 651
listTimezones() (CakeTime method), 780
listTimezones() (TimeHelper method), 194, 488, 671
load() (App method), 346
load() (Configure method), 840
loadConfig() (HtmlHelper method), 145, 440, 622
loadModel() (Controller method), 57
loadTasks() (Shell method), 813
location() (App method), 342
location() (CakeResponse method), 72
lock (File property), 693
log() (Debugger method), 880
LOG_ERROR (global constant), 837
LogError() (global function), 338
loggedIn() (AuthComponent method), 547
login() (AuthComponent method), 547
loginAction (AuthComponent property), 546
loginRedirect (AuthComponent property), 546
logout() (AuthComponent method), 548
logoutRedirect (AuthComponent property), 546
LOGS (global constant), 339
luhn() (Validation method), 274
M
map() (Hash method), 707
map() (Set method), 757
mapActions() (AuthComponent method), 548
mapResources() (Router method), 862
match() (CakeRoute method), 863
matches() (Set method), 758
maxDimensions() (Hash method), 707
maxLength() (Validation method), 274
md5() (File method), 695
media() (HtmlHelper method), 136, 431, 613
Index
MediaView (class), 95
merge() (Hash method), 705
merge() (Set method), 759
mergeDiff() (Hash method), 709
meridian() (FormHelper method), 126, 421, 603
messages() (Folder method), 692
meta() (HtmlHelper method), 130, 425, 607
meta() (PaginatorHelper method), 176, 470, 653
method() (CakeRequest method), 64
MethodNotAllowedException, 870
mime() (File method), 696
mimeType() (Validation method), 274
minLength() (Validation method), 275
MINUTE (global constant), 339
minute() (FormHelper method), 126, 421, 603
MissingActionException, 871
MissingBehaviorException, 871
MissingComponentException, 871
MissingConnectionException, 871
MissingControllerException, 871
MissingDatabaseException, 871
MissingHelperException, 871
MissingLayoutException, 871
MissingShellException, 871
MissingShellMethodException, 871
MissingTableException, 871
MissingTaskException, 871
MissingViewException, 871
mode (Folder property), 688
model (RssHelper property), 180, 474, 657
ModelBehavior (class), 316, 389
modified() (CakeResponse method), 72
money() (Validation method), 275
MONTH (global constant), 340
month() (FormHelper method), 125, 420, 602
move() (Folder method), 692
moveDown() (TreeBehavior method), 307, 380, 520
moveUp() (TreeBehavior method), 308, 381, 520
multiple() (Validation method), 275
N
name (Controller property), 57
name (File property), 693
name() (File method), 695
named parameters, 854
naturalNumber() (Validation method), 276
nest() (Hash method), 710
nest() (Set method), 760
Index
O
object() (JsHelper method), 150, 445, 627
objects() (App method), 343
offset() (File method), 695
onlyAllow() (CakeRequest method), 64
open() (File method), 695
options() (PaginatorHelper method), 172, 466, 649
out() (Shell method), 813
output (View property), 93
overwrite() (Shell method), 814
owner() (File method), 695
P
paginate (Controller property), 59
paginate() (Controller method), 55
PaginatorComponent (class), 524
PaginatorHelper (class), 166, 461, 643
para() (HtmlHelper method), 138, 433, 615
param() (CakeRequest method), 66
param() (PaginatorHelper method), 176, 470, 653
params (CakeRequest property), 66
params (RssHelper property), 180, 474, 657
params() (PaginatorHelper method), 175, 470, 652
parse() (CakeRoute method), 862
parseExtensions() (Router method), 862
passed arguments, 853
password() (AuthComponent method), 548
password() (FormHelper method), 116, 411, 593
1067
Q
query (CakeRequest property), 66
query() (CakeRequest method), 65
R
radio() (FormHelper method), 118, 413, 595
range() (Validation method), 277
read() (Cache method), 678
read() (CacheEngine method), 675
read() (CakeSession method), 868
read() (ConfigReaderInterface method), 842
read() (Configure method), 838
read() (CookieComponent method), 562
read() (File method), 696
read() (Folder method), 692
read() (SessionComponent method), 531
read() (SessionHelper method), 181, 475, 658
readable() (File method), 696
realpath() (Folder method), 693
recover() (TreeBehavior method), 310, 383, 522
redirect() (AuthComponent method), 548
redirect() (Controller method), 52
redirect() (JsHelper method), 158, 453, 635
redirect() (Router method), 861
redirectUrl() (AuthComponent method), 548
reduce() (Hash method), 707
ref (role), 942
referer() (CakeRequest method), 64
referer() (Controller method), 54
remember() (Cache method), 679
remove() (Hash method), 698
remove() (Set method), 765
removeFromTree() (TreeBehavior method), 308,
382, 521
render() (Controller method), 51
renderAs() (RequestHandlerComponent method),
558
reorder() (TreeBehavior method), 309, 310, 382,
384, 521, 523
replaceText() (File method), 696
request (AuthComponent property), 547
request (View property), 93
request() (HttpSocket method), 713
request() (JsHelper method), 151, 446, 628
requestAction() (Controller method), 55
RequestHandlerComponent (class), 554
requireAuth() (SecurityComponent method), 550
requireDelete() (SecurityComponent method), 550
Index
S
safe() (File method), 696
scaffoldError() (Controller method), 53
script() (HtmlHelper method), 138, 433, 615
scriptBlock() (HtmlHelper method), 140, 434, 616
scriptEnd() (HtmlHelper method), 140, 435, 617
scriptStart() (HtmlHelper method), 140, 434, 617
SECOND (global constant), 339
secure() (FormHelper method), 128, 422, 604
Security (class), 737
SecurityComponent (class), 548
select() (FormHelper method), 119, 414, 596
send() (CakeResponse method), 73
serializeForm() (JsHelper method), 158, 452, 635
serverOffset() (CakeTime method), 777
serverOffset() (TimeHelper method), 192, 486, 669
SessionComponent (class), 530
SessionHelper (class), 181, 475, 658
sessionKey (AuthComponent property), 547
Set (class), 740
set() (Cache method), 678
Index
1069
T
tableCells() (HtmlHelper method), 142, 437, 619
tableHeaders() (HtmlHelper method), 141, 436, 618
tableize() (Inflector method), 719
tag() (HtmlHelper method), 137, 432, 614
tagIsInvalid() (FormHelper method), 127, 422, 603
tail() (String method), 772
tail() (TextHelper method), 186, 480, 663
tasks (Shell property), 811
TESTS (global constant), 339
text() (FormHelper method), 116, 411, 593
textarea() (FormHelper method), 117, 412, 594
TextHelper (class), 182, 477, 659
themePath() (App method), 344
time() (RssHelper method), 180, 475, 657
time() (Validation method), 277
TIME_START (global constant), 339
timeAgoInWords() (CakeTime method), 777
timeAgoInWords() (TimeHelper method), 192, 486,
669
TimeHelper (class), 188, 482, 665
timezone() (CakeTime method), 780
timezone() (TimeHelper method), 194, 488, 671
TMP (global constant), 339
toArray() (Xml method), 786
toAtom() (CakeTime method), 778
toAtom() (TimeHelper method), 193, 487, 670
tokenize() (String method), 769
toList() (String method), 773
toList() (TextHelper method), 187, 481, 664
toPercentage() (CakeNumber method), 734
toPercentage() (NumberHelper method), 163, 458,
640
toQuarter() (CakeTime method), 779
toQuarter() (TimeHelper method), 193, 487, 670
toReadableSize() (CakeNumber method), 734
toReadableSize() (NumberHelper method), 164,
458, 640
toRSS() (CakeTime method), 779
toRSS() (TimeHelper method), 194, 488, 671
toServer() (CakeTime method), 779
toServer() (TimeHelper method), 194, 488, 671
toUnix() (CakeTime method), 779
toUnix() (TimeHelper method), 194, 488, 671
trace() (Debugger method), 880
TranslateBehavior (class), 294, 367, 506
tree() (Folder method), 693
1070
U
unauthorizedRedirect (AuthComponent property),
546
underscore() (Inflector method), 719
unlockedFields (SecurityComponent property), 551
unlockField() (FormHelper method), 127, 422, 604
uploadError() (Validation method), 277
url() (Helper method), 199, 492
url() (HtmlHelper method), 144, 438, 621
url() (PaginatorHelper method), 175, 469, 652
url() (Router method), 861
url() (Validation method), 277
user() (AuthComponent method), 548
userDefined() (Validation method), 278
uses (Controller property), 58
uses (Shell property), 811
uses() (App method), 340
useTag() (HtmlHelper method), 145, 440, 622
uuid() (String method), 769
uuid() (Validation method), 278
uuid() (View method), 92
V
valid() (SessionHelper method), 181, 475, 658
validateAuthKey() (Security method), 740
validatePost (SecurityComponent property), 551
Validation (class), 268
ValidationisUnique() (ModelValidation method),
274
value() (Helper method), 199, 493
value() (JsHelper method), 158, 453, 635
variable() (Inflector method), 719
VENDORS (global constant), 339
verify() (TreeBehavior method), 311, 384, 523
version (RssHelper property), 180, 474, 657
version() (Configure method), 839
View (class), 91
viewClassMap()
(RequestHandlerComponent
method), 559
W
wasWithinLast() (CakeTime method), 780
Index
X
Xml (class), 781
XmlView (class), 98
Y
YEAR (global constant), 340
year() (FormHelper method), 125, 420, 602
Index
1071