Development. CakePHP CakePHP
Annonce
Annonce
CHAPTER 10
Development
In this section we’ll 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 =
array
(
'datasource' => 'Database/Mysql' ,
'persistent' =>
false
,
'host'
'login'
'password'
'database'
'prefix'
=>
=>
=>
=>
=>
'localhost'
'c4k3roxx!'
''
,
'cakephpuser'
,
,
'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
855
CakePHP Cookbook Documentation, Release 2.x
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
to indicate plugin datasource to use.
persistent Whether or not to use a persistent connection to the database. When using SQLServer you should not enable persistent connections as it causes difficult to diagnose crashes.
host The database server’s hostname (or IP address).
login The username for the account.
password The password for the account.
database The name of the database for this connection to use.
prefix (optional) The string that prefixes every table name in the database. If your tables don’t have prefixes, set this to an empty string.
port (optional) The TCP port or Unix socket used to connect to the server.
encoding Indicates the character set to use when sending SQL statements to the server. This defaults to the database’s default encoding for all databases other than DB2. If you wish to use UTF-8 encoding with mysql/mysqli connections you must use ‘utf8’ without the hyphen.
schema Used in PostgreSQL database setups to specify which schema to use.
unix_socket Used by drivers that support it to connect via unix socket files. If you are using PostgreSQL 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 the Mysql, Postgres, and Sqlserver datasources 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.
856 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Additional Class Paths
It’s occasionally useful to be able to share MVC classes between applications on the same system. If you want the same controller in both applications, you can use CakePHP’s bootstrap.php to bring these additional classes into view.
By using
in bootstrap.php we can define additional paths where CakePHP will look for classes:
App :: build (
array
(
'Model' =>
array
(
'/path/to/models' ,
'/next/path/to/models'
),
'Model/Behavior' =>
array
(
'/path/to/behaviors' ,
'/next/path/to/behaviors'
),
'Model/Datasource' =>
array
(
'/path/to/datasources' ,
'/next/path/to/datasources'
),
'Model/Datasource/Database' =>
array
(
'/path/to/databases' ,
'/next/path/to/database'
),
'Model/Datasource/Session' =>
array
(
'/path/to/sessions' ,
'/next/path/to/sessions'
),
'Controller' =>
array
(
'/path/to/controllers' ,
'/next/path/to/controllers'
),
'Controller/Component' =>
array
(
'/path/to/components' ,
'/next/path/to/components'
),
'Controller/Component/Auth' =>
array
(
'/path/to/auths' ,
'/next/path/to/auths'
),
'Controller/Component/Acl' =>
array
(
'/path/to/acls' ,
'/next/path/to/acls'
),
'View' =>
array
(
'/path/to/views' ,
'/next/path/to/views'
),
'View/Helper' =>
array
(
'/path/to/helpers' ,
'/next/path/to/helpers'
Configuration 857
CakePHP Cookbook Documentation, Release 2.x
));
),
'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 application’s 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
CakePHP’s 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, you’ll need to be familiar with
, CakePHP’s configuration registry class.
CakePHP Core Configuration
The
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.
858 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
• 0 = Production mode. No output.
• 1 = Show errors and warnings.
• 2 = Show errors, warnings, and SQL. [SQL log is only shown when you add $this-
>element(‘sql_dump’) to your view or layout.]
Error Configure the Error handler used to handle errors for your application.
By default
ErrorHandler::handleError() is used. It will display errors using
> 0 and log errors with
when debug = 0.
Sub-keys:
• handler - callback - The callback to handle errors. You can set this to any callback type, including anonymous functions.
• level - int - The level of errors you are interested in capturing.
• trace - boolean - Include stack traces for errors in log files.
Exception Configure the Exception handler used for uncaught exceptions.
By default, ErrorHandler::handleException() is used. It will display a HTML page for the exception, and while debug
> 0, framework errors like Missing Controller will be displayed. When debug = 0, framework errors will be coerced into generic HTTP errors. For more information on Exception handling, see the
section.
App.baseUrl If you don’t want or can’t get mod_rewrite (or some other compatible module) up and running on your server, you’ll need to use CakePHP’s built-in pretty URLs. In /app/Config/core.php, uncomment the line that looks like:
Configure :: write ( 'App.baseUrl' , env( 'SCRIPT_NAME' ));
Also remove these .htaccess files:
/.
htaccess
/ app /.
htaccess
/ app / webroot /.
htaccess
This will make your URLs look like www.example.com/index.php/controllername/actionname/param rather than www.example.com/controllername/actionname/param.
If you are installing CakePHP on a webserver besides Apache, you can find instructions for getting
URL rewriting working for other servers under the
section.
App.encoding Define what encoding your application uses. This encoding is used to generate the charset in the layout, and encode entities. It should match the encoding values specified for your database.
Routing.prefixes Un-comment this definition if you’d like to take advantage of CakePHP prefixed routes like admin. Set this variable with an array of prefix names of the routes you’d like to use. More on this later.
Cache.disable When set to true, persistent caching is disabled site-wide. This will make all read/writes to
fail.
Cache.check If set to true, enables view caching. Enabling is still needed in the controllers, but this variable enables the detection of those settings.
Configuration 859
CakePHP Cookbook Documentation, Release 2.x
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 CakePHP’s /tmp directory.
• ‘database’ - Uses CakePHP’s 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 -
Doesn’t 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 CakePHP’s Access Control List functionality. See the
Access Control Lists chapter for more information.
860 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Note: Cache configuration is also found in core.php — We’ll be covering that later on, so stay tuned.
The
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
you can clear data using
Configure Class
class Configure
Despite few things needing to be configured in CakePHP, it’s sometimes useful to have your own configuration rules for your application. In the past you may have defined custom configuration values by defining variable or constants in some files. Doing so forces you to include that configuration file every time you needed to use those values.
CakePHP’s Configure class can be used to store and retrieve application or runtime specific values. Be careful, this class allows you to store anything in it, then use it in any other part of your code: a sure temptation to break the MVC pattern CakePHP was designed for. The main goal of Configure class is to keep centralized variables that can be shared between many objects. Remember to try to live by “convention over configuration” and you won’t end up breaking the MVC structure we’ve set in place.
This class can be called from anywhere within your application, in a static context:
Configure :: read ( 'debug' ); static Configure::write($key, $value)
Parameters
• $key (string) – The key to write, can be a
value.
Configuration 861
CakePHP Cookbook Documentation, Release 2.x
• $value (mixed) – The value to store.
Use write() to store data in the application’s configuration:
Configure :: write ( 'Company.name' , 'Pizza, Inc.' );
Configure :: write ( 'Company.slogan' , 'Pizza for your body and soul' );
Note: The
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
value
Used to read configuration data from the application. Defaults to CakePHP’s important debug value.
If a key is supplied, the data is returned. Using our examples from write() above, we can read that data back:
Configure :: read ( 'Company.name' ); //yields: 'Pizza, Inc.'
Configure :: read ( 'Company.slogan' ); //yields: 'Pizza for your body
//and soul'
Configure :: read ( 'Company' );
//yields:
array
( 'name' => 'Pizza, Inc.' , 'slogan' => 'Pizza for your body and soul' );
If $key is left null, all values in Configure will be returned. If the value corresponding to the specified
$key does not exist then null will be returned.
static Configure::consume($key)
Parameters
• $key (string) – The key to read, can use be a
value
Read and delete a key from Configure. This is useful when you want to combine reading and deleting values in a single operation.
static Configure::check($key)
862 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Parameters
• $key (string) – The key to check.
Used to check if a key/path exists and has not-null value.
New in version 2.3: Configure::check() was added in 2.3
static Configure::delete($key)
Parameters
• $key (string) – The key to delete, can be a
value
Used to delete information from the application’s 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
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.
Reading and writing configuration files
CakePHP comes with two built-in configuration file readers.
is able to read PHP config files, in the same format that Configure has historically read.
is able to read ini config files. See the
PHP documentation
1 for more information on the specifics of ini files. To use a core config reader, you’ll need to attach it to Configure using
App :: uses ( 'PhpReader' , 'Configure' );
// Read config files from app/Config
Configure :: config ( 'default' ,
new
PhpReader());
1 http://php.net/parse_ini_file
Configuration 863
CakePHP Cookbook Documentation, Release 2.x
// Read config files from another path.
Configure :: config ( 'default' ,
new
PhpReader( '/path/to/your/config/files/' ));
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
// Get the array of aliases for attached readers.
Configure :: configured ();
// Check if a specific reader is attached
Configure :: configured ( 'default' );
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 you’ve 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
, the generated file will be a PHP configuration file loadable by the
864 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Given that the ‘default’ reader is an instance of PhpReader.
Save all data in Configure to the file my_config.php
:
Configure :: dump ( 'my_config.php' , 'default' );
Save only the error handling configuration:
Configure :: dump ( 'error.php' , 'default' ,
array
( 'Error' , 'Exception' ));
Configure::dump() can be used to either modify or overwrite configuration files that are readable with
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
class. This allows you to store Configuration information in any storage engine that
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 you’ve stored runtime configuration, you’ll 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' );
Configuration 865
CakePHP Cookbook Documentation, Release 2.x
When restoring configuration information it’s 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.
Creating your own Configuration readers
Since configuration readers are an extensible part of CakePHP, you can create configuration readers in your application and plugins. Configuration readers need to implement the
interface defines a read method, as the only required method. If you really like XML files, you could create a simple Xml config reader for you application:
// in app/Lib/Configure/MyXmlReader.php
App :: uses ( 'Xml' , 'Utility' );
class MyXmlReader implements
ConfigReaderInterface {
public function
__construct ( $path =
null
) {
if
( !
$path ) {
$path = APP .
'Config' .
DS;
}
$this -> _path = $path ;
}
public function
read ( $key ) {
$xml = Xml :: build ( $this -> _path .
$key .
'.xml' );
return
Xml :: toArray ( $xml );
}
// As of 2.3 a dump() method is also required
public function
dump ( $key , $data ) {
// code to dump data to file
}
}
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: XMLReader a 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
ConfigReaderInterface::read(
$key)
Parameters
866 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
• $key (string) – The key name or identifier to load.
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.
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
)
);
Files without $config will cause an
Load your custom configuration file by inserting the following in app/Config/bootstrap.php:
Configure :: load ( 'customConfig' ); class IniReader
Allows you to read configuration files that are stored as plain .ini files. The ini files must be compatible with PHP’s 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.
Configuration 867
CakePHP Cookbook Documentation, Release 2.x
An example ini file would look like: 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
CakePHP’s naming conventions can be really nice - you can name your database table big_boxes, your model BigBox, your controller BigBoxesController, and everything just works together automatically. The way CakePHP knows how to tie things together is by inflecting the words between their singular and plural forms.
There are occasions (especially for our non-English speaking friends) where you may run into situations where CakePHP’s
(the class that pluralizes, singularizes, camelCases, and under_scores) might not work as you’d like. If CakePHP won’t recognize your Foci or Fish, you can tell CakePHP about your special cases.
Loading custom inflections
You can use
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.
868 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Bootstrapping CakePHP
If you have any additional configuration needs, use CakePHP’s bootstrap file, found in app/Config/bootstrap.php. This file is executed just after CakePHP’s core bootstrapping.
This file is ideal for a number of common bootstrapping tasks:
• Defining convenience functions.
• Registering global constants.
• Defining additional model, view, and controller paths.
• Creating cache configurations.
• Configuring inflections.
• Loading configuration files.
Be careful to maintain the MVC software design pattern when you add things to the bootstrap file: it might be tempting to place formatting functions there in order to use them in your controllers.
Resist the urge. You’ll be glad you did later on down the line.
You might also consider placing things in the AppController class. This class is a parent class to all of the controllers in your application. AppController is a handy place to use controller callbacks and define methods to be used by all of your controllers.
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 Apache’s 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 application’s 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. It’s 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 won’t 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
Routing 869
CakePHP Cookbook Documentation, Release 2.x
CakePHP also comes with a few default routes to get you started. These can be disabled later on once you are sure you don’t need them. See
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. CakePHP’s default routing will get you pretty far in any application. You can access an action directly via the URL by putting its name in the request. You can also pass parameters to your controller actions using the URL.
// URL pattern default routes: http :// example .
com / controller / action / param1 / param2 / param3
The URL /posts/view maps to the view() action of the PostsController, and /products/view_clearance maps to the 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 application’s
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
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 you’re 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 you’ve specified a URL, you use the last two parameters of connect() to tell CakePHP what to do with a request once it has been matched. The second parameter is an associative array. The keys of the array should be named after the route elements in the URL, or the default elements: :controller,
:action
, and :plugin. The values in the array are the default values for those keys. Let’s look at some basic examples before we start using the third parameter of connect():
870 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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
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. In this setting, you can access a single piece of content from 2 different URLs. See
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.
Let’s say that instead of accessing our regular URL at /users/some_action/5, we’d 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
that this route should accept any additional positional arguments given. These arguments will be made available in the
array.
Routing 871
CakePHP Cookbook Documentation, Release 2.x
When generating URLs, routes are used too.
Using array(’controller’ => ’users’,
’action’ => ’some_action’, 5) as a url will output /cooks/some_action/5 if the above route is the first match found.
By default all named and passed arguments are extracted from URLs matching greedy templates. However, you can configure how and which named arguments are parsed using
if you need to.
Route Elements
You can specify your own route elements and doing so gives you the power to define places in the
URL where parameters for controller actions should lie. When a request is made, the values for these route elements are found in $this->request->params on the controller. This is different than how named parameters are handled, so note the difference: named parameters (/controller/action/name:value) are found in $this->request->params[’named’], whereas custom route element data is found in
$this->request->params . When you define a custom route element, you can optionally specify a regular expression - this tells CakePHP how to know if the URL is correctly formed or not. If you choose to not provide a regular expression, any non / will be treated as part of the parameter:
Router :: connect (
'/:controller/:id' ,
array
( 'action' => 'view' ),
array
( 'id' => '[0-9]+' )
);
This simple example illustrates how to create a quick way to view models from any controller by crafting a URL that looks like /controllername/:id. The URL provided to connect() specifies two route elements: :controller and :id. The :controller element is a CakePHP default route element, so the router knows how to match and identify controller names in URLs. The :id element is a custom route element, and must be further clarified by specifying a matching regular expression in the third parameter of connect().
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 ),
872 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
);
array
( 'userShortcut' => '(?i:principal)' )
One more example, and you’ll be a routing pro:
Router :: connect (
'/:controller/:year/:month/:day' ,
array
( 'action' => 'index' ),
array
(
'year' => '[12][0-9]{3}' ,
'month' => '0[1-9]|1[012]' ,
'day' => '0[1-9]|[12][0-9]|3[01]'
)
);
This is rather involved, but shows how powerful routes can really become. The URL supplied has four route elements. The first is familiar to us: it’s a default route element that tells CakePHP to expect a controller name.
Next, we specify some default values. Regardless of the controller, we want the index() action to be called.
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
• ext Used for
routing.
Passing Parameters to Action
When connecting routes using
you may want to have routed elements be passed arguments instead. By using the 3rd argument of
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 873
CakePHP Cookbook Documentation, Release 2.x
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'
));
Per-Route Named Parameters
While you can control named parameters on a global scale using
you can also control named parameter behavior at the route level using the 3rd argument of
Router::connect()
:
Router :: connect (
'/:controller/:action/*' ,
array
(),
array
(
'named' =>
array
(
'wibble' ,
'fish' =>
array
( 'action' => 'index' ),
'fizz' =>
array
( 'controller' =>
array
( 'comments' , 'other' )),
'buzz' => 'val-[\d]+'
)
)
);
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
874 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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
)
);
You can configure the Router to use multiple prefixes too.
By adding additional values to
Routing.prefixes
. If you set:
Configure :: write ( 'Routing.prefixes' ,
array
( 'admin' , 'manager' ));
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 875
CakePHP Cookbook Documentation, Release 2.x
Much like admin routing all prefix actions should be prefixed with the prefix name.
So
/manager/posts/add would map to PostsController::manager_add().
Additionally, the current prefix will be available from the controller methods through
$this->request->prefix
When using prefix routes it’s important to remember, using the HTML helper to build your links will help maintain the prefix calls. Here’s 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:
876 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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'
)
);
File extensions are used by
to do automatic view switching based on content types. See the RequestHandlerComponent for more information.
Using Additional Conditions When Matching Routes
When creating routes you might want to restrict certain URL’s based on specific request/environment settings. A good example of this is
routing. You can specify additional conditions in the $defaults argument for
. By default CakePHP exposes 3 environment conditions, but you
can add more using
Custom Route Classes . The built-in options are:
• [type] Only match requests for specific content types.
• [method] Only match requests with specific HTTP verbs.
• [server] Only match when $_SERVER[’SERVER_NAME’] matches the given value.
We’ll provide a simple example here of how you can use the [method] option to create a custom RESTful route:
Router :: connect (
"/:controller/:id" ,
array
( "action" => "edit" , "[method]" => "PUT" ),
array
( "id" => "[0-9]+" )
);
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 877
CakePHP Cookbook Documentation, Release 2.x
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 ());
}
}
You would get the following output:
Array
(
[ 0 ] => recent
[ 1 ] => mark
)
This same data is also available at
$this->request->params[’pass’] and
$this->passedArgs in your controllers, views, and helpers.
The values in the pass array are numerically indexed based on the order they appear in the called URL: debug( $this -> request -> params [ 'pass' ]); debug( $this -> passedArgs );
Either of the above would output:
Array
(
[ 0 ] => recent
[ 1 ] => mark
)
Note: $this->passedArgs may also contain named parameters as a named array mixed with Passed arguments.
When generating URLs, using a
you add passed arguments as values without string keys in the array:
array
( 'controller' => 'posts' , 'action' => 'view' , 5 )
Since 5 has a numeric key, it is treated as a passed argument.
878 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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, you’d 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
named parameters are not reverse routing, or parsing correctly, you will need to inform
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 (
array
( 'chapter' , 'section' ));
Will ensure that your chapter and section parameters reverse route correctly.
When generating URLs, using a
you add named parameters as values with string keys matching the name:
array
( 'controller' => 'posts' , 'action' => 'view' , 'chapter' => 'association' )
Since ‘chapter’ doesn’t match any defined route elements, it’s treated as a named parameter.
Routing 879
CakePHP Cookbook Documentation, Release 2.x
Note: Both named parameters and route elements share the same key-space. It’s 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
)
));
The above would generate the URL /posts/index/filter[published]:1/filter[frontpage]:1.
The parameters are then parsed and stored in your controller’s passedArgs variable as an array, just as you sent them to
$this -> passedArgs [ '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
880 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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
)
),
);
Controlling Named Parameters
You can control named parameter configuration at the per-route-level or control them globally. Global control is done through Router::connectNamed() The following gives some examples of how you can control named parameter parsing with connectNamed().
Do not parse any named parameters:
Router :: connectNamed (
false
);
Parse only default parameters used for CakePHP’s pagination:
Router :: connectNamed (
false
,
array
( 'default' =>
true
));
Parse only the page parameter if its value is a number:
Router :: connectNamed (
array
( 'page' => '[\d]+' ),
array
( 'default' =>
false
, 'greedy' =>
false
)
);
Parse only the page parameter no matter what:
Router :: connectNamed (
array
( 'page' ),
array
( 'default' =>
false
, 'greedy' =>
false
)
);
Parse only the page parameter if the current action is ‘index’:
Router :: connectNamed (
array
( 'page' =>
array
( 'action' => 'index' )),
array
( 'default' =>
false
, 'greedy' =>
false
)
);
Routing 881
CakePHP Cookbook Documentation, Release 2.x
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
)
); connectNamed() supports a number of options:
• greedy Setting this to true will make Router parse all named params. Setting it to false will parse only the connected named params.
• default Set this to true to merge in the default set of named parameters.
• reset Set to true to clear existing rules and start fresh.
• separator Change the string used to separate the key & value in a named parameter. Defaults to :
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
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
882 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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 don’t 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 ));
This would redirect /posts/* to http://google.com with a HTTP status of 302.
Disabling the Default Routes
If you have fully customized all your routes, and want to avoid any possible duplicate content penalties from search engines, you can remove the default routes that CakePHP offers by deleting them from your application’s routes.php file.
This will cause CakePHP to serve errors, when users try to visit URLs that would normally be provided by
CakePHP but have not been connected explicitly.
Custom Route Classes
Custom route classes allow you to extend and change how individual routes parse requests and handle reverse routing. A custom route class should be created in app/Routing/Route and should extend
and implement one or both of match() and/or parse(). parse() is used to parse requests and match() is used to handle reverse routing.
You can use a custom route class when making a route by using the routeClass option, and loading the file containing your route before trying to use it:
App :: uses ( 'SlugRoute' , 'Routing/Route' );
Router :: connect (
'/:slug' ,
array
( 'controller' => 'posts' , 'action' => 'view' ),
array
( 'routeClass' => 'SlugRoute' )
);
Routing 883
CakePHP Cookbook Documentation, Release 2.x
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 uri’s 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.
884 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
$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
static Router::redirect($route, $url, $options = array())
Parameters
• $route (string) – A route template that dictates which URLs should be redirected.
• $url (mixed) – Either a
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
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
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 885
CakePHP Cookbook Documentation, Release 2.x
• $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
will be prepended to generated URLs.
static Router::mapResources($controller, $options = array())
Creates REST resource routes for the given controller(s). See the
section for more information.
static Router::parseExtensions($types)
Used in routes.php to declare which
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
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 URL’s. 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
class CakeRoute
The base class for custom routes to be based on.
886 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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 doesn’t 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 PHP’s 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 CakePHP’s 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 session’s 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 887
CakePHP Cookbook Documentation, Release 2.x
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
• Session.cacheLimiter - Allows you define the cache control headers used for the session cookie. The default is must-revalidate. This option was added in 2.8.0.
CakePHP’s 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'
)
));
If you are using php’s default session settings, take note that session.gc_maxlifetime can override your setting for timeout. The default is 24 minutes. Change this in your ini settings to get it to match longer sessions:
Configure :: write ( 'Session' ,
array
(
'defaults' => 'php' ,
'timeout' => 2160 , // 36 hours
'ini' =>
array
(
'session.gc_maxlifetime' => 129600 // 36 hours
)
));
888 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Built-in Session handlers & configuration
CakePHP comes with several built-in session configurations. You can either use these as the basis for your session configuration, or you can create a fully custom solution. To use defaults, simply set the ‘defaults’ key to the name of the default you want to use. You can then override any sub setting by declaring it in your
Session config:
Configure :: write ( 'Session' ,
array
(
'defaults' => 'php'
));
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' ,
'timeout' => 4320 //3 days
));
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 don’t 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 ,
Sessions 889
CakePHP Cookbook Documentation, Release 2.x
'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.
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 you’ve 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
'' ,
890 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
`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 space, sessions will start to expire as records are evicted.
To use Cache based sessions you can configure you Session config like:
Configure :: write ( 'Session' ,
array
(
'defaults' => 'cache' ,
'handler' =>
array
(
'config' => 'session'
)
));
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’.
Setting ini directives
The built-in defaults attempt to provide a common base for session configuration. You may need to tweak specific ini flags as well. CakePHP exposes the ability to customize the ini settings for both default configurations, as well as custom ones. The ini key in the session settings, allows you to specify individual configuration values. For example you can use it to control settings like session.gc_divisor:
Configure :: write ( 'Session' ,
array
(
'defaults' => 'php' ,
'ini' =>
array
(
'session.gc_divisor' => 1000 ,
'session.cookie_httponly' =>
true
)
));
Creating a custom session handler
Creating a custom session handler is straightforward in CakePHP. In this example we’ll create a session handler that stores sessions both in the Cache (apc) and the database. This gives us the best of fast IO of apc, without having to worry about sessions evaporating when the cache fills up.
Sessions 891
CakePHP Cookbook Documentation, Release 2.x
First we’ll need to create our custom class and put it in app/Model/Datasource/Session/ComboSession.php.
The class should look something like:
App :: uses ( 'DatabaseSession' , 'Model/Datasource/Session' );
class ComboSession extends
DatabaseSession
implements
CakeSessionHandlerInterface {
public
$cacheKey ;
public function
__construct () {
$this -> cacheKey = Configure :: read ( 'Session.handler.cache' );
parent
:: __construct ();
}
// read data from the session.
public function
read ( $id ) {
$result = Cache :: read ( $id , $this -> cacheKey );
if
( $result ) {
return
$result ;
}
return parent
:: read ( $id );
}
// write data into the session.
public function
write ( $id , $data ) {
Cache :: write ( $id , $data , $this -> cacheKey );
return parent
:: write ( $id , $data );
}
// destroy a session.
public function
destroy ( $id ) {
Cache :: delete ( $id , $this -> cacheKey );
return parent
:: destroy ( $id );
}
// removes expired sessions.
public function
gc ( $expires =
null
) {
Cache :: gc ( $this -> cacheKey );
return parent
:: gc ( $expires );
}
}
Our class extends the built-in DatabaseSession so we don’t have to duplicate all of its logic and behavior. We wrap each operation with a
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'
)
));
892 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
// 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
Reading & writing session data
Depending on the context you are in, your application has different classes that provide access to the session.
In controllers you can use
of your application you can use CakeSession to access the session as well. Like the other interfaces to the session, CakeSession provides a simple CRUD interface.
static CakeSession::read($key)
You can read values from the session using
compatible syntax:
CakeSession :: read ( 'Config.language' ); static CakeSession::write($key, $value)
$key should be the dot separated path you wish to write $value to:
CakeSession :: write ( 'Config.language' , 'eng' ); 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
and
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
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:
Exceptions 893
CakePHP Cookbook Documentation, Release 2.x
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, CakePHP’s default handler will be used.
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.
Built-in Exceptions for CakePHP
There are several built-in exceptions inside CakePHP, outside of the internal framework exceptions, there are several exceptions for HTTP methods exception BadRequestException
Used for doing 400 Bad Request error.
exception UnauthorizedException
Used for doing a 401 Unauthorized error.
exception ForbiddenException
Used for doing a 403 Forbidden error.
exception NotFoundException
Used for doing a 404 Not found error.
exception MethodNotAllowedException
Used for doing a 405 Method Not Allowed error.
894 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
exception InternalErrorException
Used for doing a 500 Internal Server Error.
exception NotImplementedException
Used for doing a 501 Not Implemented Errors.
You can throw these exceptions from your controllers to indicate failure states, or HTTP errors. An example use of the HTTP exceptions could be rendering 404 pages for items that have not been found:
public function
view ( $id ) {
$post = $this -> Post -> findById ( $id );
if
( !
$post ) {
throw new
NotFoundException( 'Could not find that post' );
}
$this -> set ( 'post' , $post );
}
By using exceptions for HTTP errors, you can keep your code both clean, and give RESTful responses to client applications and users.
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 model’s connection is missing.
exception MissingTableException
A model’s table is missing from CakePHP’s 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.
Exceptions 895
CakePHP Cookbook Documentation, Release 2.x
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 exception CakeBaseException
Base exception class in CakePHP. All CakeExceptions and HttpExceptions above extend this class.
CakeBaseException::responseHeader(
$header = null , $value = null)
See
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.”
Using HTTP exceptions in your controllers
You can throw any of the HTTP related exceptions from your controller actions to indicate failure states.
For example:
public function
view ( $id ) {
$post = $this -> Post -> findById ( $id );
if
( !
$post ) {
throw new
NotFoundException();
}
$this -> set ( compact ( 'post' ));
}
The above would cause the configured
Exception.handler
to catch and process the
. 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.
896 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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 don’t need to bother customizing them as they are used only during development. With debug turned off all framework layer exceptions are converted to InternalErrorException.
Creating your own application exceptions
You can create your own application exceptions using any of the built in SPL exceptions
2
itself, or
CakeException . Application exceptions that extend Exception or the SPL exceptions will be
treated as 500 error in production mode.
is special in that all
objects are coerced into either 500 or 404 errors depending on the code they use. When in development mode
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 {};
You could provide nice development errors, by creating app/View/Errors/missing_widget.ctp.
When in production mode, the above error would be treated as a 500 error.
The constructor for
has been extended, allowing you to pass in hashes of data. These hashes are interpolated into the the messageTemplate, as well as into the view that is used to represent the error in development mode. This allows you to create data rich exceptions, by providing more context for your errors. You can also provide a message template which allows the native __toString() methods to work as normal:
class MissingWidgetException extends
CakeException {
protected
$_messageTemplate = 'Seems that %s is missing.' ;
}
throw new
MissingWidgetException(
array
( 'widget' => 'Pointy' ));
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 doesn’t 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 you’ll get the error400 template. If you have defined
2 http://php.net/manual/en/spl.exceptions.php
Exceptions 897
CakePHP Cookbook Documentation, Release 2.x
an error template for your custom exception, that template will be used in development mode. If you’d like your own exception handling logic even in production, see the next section.
Extending and implementing your own Exception handlers
You can implement application specific exception handling in one of a few ways. Each approach gives you different amounts of control over the exception handling process.
• Set Configure::write(’Exception.handler’, ’YourClass::yourMethod’);
• Create AppController::appError();
• Set Configure::write(’Exception.renderer’, ’YourClass’);
In the next few sections, we will detail the various approaches and the benefits each has.
Create your own Exception handler with Exception.handler
Creating your own exception handler gives you full control over the exception handling process. The class you choose should be loaded in your app/Config/bootstrap.php, so it’s available to handle any exceptions. You can define the handler as any callback type. By settings Exception.handler CakePHP will ignore all other Exception settings. A sample custom exception handling setup could look like:
// in app/Config/core.php
Configure :: write ( 'Exception.handler' , 'AppExceptionHandler::handle' );
// in app/Config/bootstrap.php
App :: uses ( 'AppExceptionHandler' , 'Lib' );
// in app/Lib/AppExceptionHandler.php
class AppExceptionHandler
{
public static function
handle ( $error ) {
echo
'Oh noes! ' .
$error -> getMessage ();
// ...
}
// ...
}
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 ) {
898 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
}
}
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. It’s 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:
class AppController extends
Controller {
public function
appError ( $error ) {
// custom logic goes here.
}
}
Using a custom renderer with Exception.renderer to handle application exceptions
If you don’t want to take control of the exception handling, but want to change how exceptions are rendered you can use Configure::write(’Exception.renderer’, ’AppExceptionRenderer’); to choose a class that will render exception pages. By default :php:class‘ExceptionRenderer‘ is used. Your custom exception renderer class should be placed in app/Lib/Error. Or an Error directory in any bootstrapped Lib path. In a custom exception rendering class you can provide specialized handling for application specific errors:
// in app/Lib/Error/AppExceptionRenderer.php
App :: uses ( 'ExceptionRenderer' , 'Error' );
class AppExceptionRenderer extends
ExceptionRenderer {
public function
missingWidget ( $error ) {
echo
'Oops that widget is missing!' ;
}
}
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 899
CakePHP Cookbook Documentation, Release 2.x
Creating a custom controller to handle exceptions
In your ExceptionRenderer sub-class, you can use the _getController method to allow you to return a custom controller to handle your errors. By default CakePHP uses CakeErrorController which omits a few of the normal callbacks to help ensure errors always display. However, you may need a more custom error handling controller in your application. By implementing _getController in your
AppExceptionRenderer class, you can use any controller you want:
class AppExceptionRenderer extends
ExceptionRenderer {
protected function
_getController ( $exception ) {
App :: uses ( 'SuperCustomError' , 'Controller' );
return new
SuperCustomErrorController();
}
}
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
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 application’s app/Config/core.php file. You can define a callback to be fired each time your application triggers any PHP error - exceptions are handled
separately. The callback can be any PHP callable, including an anonymous function. The default error handling configuration looks like:
900 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Configure :: write ( 'Error' ,
array
(
'handler' => 'ErrorHandler::handleError' ,
'level' =>
E_ALL
& ~ E_DEPRECATED,
'trace' =>
true
));
You have 5 built-in options when configuring error handlers:
• handler - callback - The callback to handle errors. You can set this to any callable type, including anonymous functions.
• level - int - The level of errors you are interested in capturing. Use the built-in PHP error constants, and bitmasks to select the level of error you are interested in.
• 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, CakePHP’s 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).
Creating your own error handler
You can create an error handler out of any callback type. For example you could use a class called
AppError to handle your errors. The following would need to be done:
//in app/Config/core.php
Configure :: write ( 'Error.handler' , 'AppError::handleError' );
//in app/Config/bootstrap.php
App :: uses ( 'AppError' , 'Lib' );
//in app/Lib/AppError.php
class AppError
{
public static function
handleError ( $code , $description , $file =
null
,
$line =
null
, $context =
null
) {
echo
'There has been an error!' ;
}
}
Error Handling 901
CakePHP Cookbook Documentation, Release 2.x
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
handling as well.
Changing fatal error behavior
Since CakePHP 2.2 the Error.handler will receive the fatal error codes as well. If you do not want to show the cake error page, you can override it like:
//in app/Config/core.php
Configure :: write ( 'Error.handler' , 'AppError::handleError' );
//in app/Config/bootstrap.php
App :: uses ( 'AppError' , 'Lib' );
//in app/Lib/AppError.php
class AppError
{
public static function
handleError ( $code , $description , $file =
null
,
$line =
null
, $context =
null
) {
list
(, $level ) = ErrorHandler :: mapErrorCode ( $code );
if
( $level === LOG_ERR) {
// Ignore fatal error. It will keep the PHP error message only
return false
;
}
return
ErrorHandler :: handleError (
$code ,
$description ,
$file ,
$line ,
$context
);
}
}
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 doesn’t offer any tools that directly connect with any IDE or editor, CakePHP does provide several tools to assist in debugging and exposing what is running under the hood of your application.
902 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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.
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 you’d 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
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 PHP’s default error handling, replacing it with far more useful error reports. The Debugger’s error handling is used by default in CakePHP. As with all debugging functions,
Configure::debug must be set to a value higher than 0.
When an error is raised, Debugger both outputs information to the page and makes an entry in the error.log
file. The error report that is generated has both a stack trace and a code excerpt from where the error was raised. Click on the “Error” link to reveal the stack trace, and on the “Code” link to reveal the error-causing lines.
Using the Debugger Class
class Debugger
To use the debugger, first ensure that Configure::read(‘debug’) is set to a value greater than 0.
static Debugger::dump($var, $depth = 3)
Dump prints out the contents of a variable. It will print out all properties and methods (if any) of the supplied variable:
$foo =
array
( 1 , 2 , 3 );
Debugger :: dump ( $foo );
Debugging 903
CakePHP Cookbook Documentation, Release 2.x
// outputs
array
(
1 ,
2 ,
3
)
// simple object
$car =
new
Car();
Debugger :: dump ( $car );
// outputs
Car
Car :: colour = 'red'
Car :: make = 'Toyota'
Car :: model = 'Camry'
Car :: mileage = '15000'
Car :: accelerate ()
Car :: decelerate ()
Car :: stop ()
Changed in version 2.1: In 2.1 forward the output was updated for readability.
See
Changed in version 2.5.0: The depth parameter was added.
static Debugger::log($var, $level = 7, $depth = 3)
Creates a detailed stack trace log at the time of invocation. The log() method prints out data similar to that done by Debugger::dump(), but to the debug.log instead of the output buffer. Note your app/tmp directory (and its contents) must be writable by the web server for log() to work correctly.
Changed in version 2.5.0: The depth parameter was added.
static Debugger::trace($options)
Returns the current stack trace. Each line of the trace includes the calling method, including which file and line the call originated from.
//In PostsController::index() pr(Debugger :: trace ());
//outputs
PostsController :: index () APP / Controller / DownloadsController .
php, line 48
Dispatcher :: _invoke () CORE / lib / Cake / Routing / Dispatcher .
php, line 265
Dispatcher :: dispatch () CORE / lib / Cake / Routing / Dispatcher .
php, line 237
[main] APP / webroot / index .
php, line 84
Above is the stack trace generated by calling Debugger::trace() in a controller action. Reading the stack trace bottom to top shows the order of currently running functions (stack frames). In the above example, index.php called Dispatcher::dispatch(), which in-turn called Dispatcher::_invoke(). The
_invoke() method then called PostsController::index(). This information is useful when working with recursive operations or deep stacks, as it identifies which functions are currently running at the time of the trace().
904 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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 >
[ 2 ] => < code >< span style = "color: #000000" >
function
excerpt ( $file , $line , $context = 2 ) { </ span ></ code >
[ 3 ] => < span class = "code-highlight" >< code >< span style = "color: #000000" >
[ 4 ] => < code >< span style = "color: #000000" > $data = @ explode ( "\n" ,
$data = $lines file_get_contents (
=
$file
array
)); </
(); </ span span
></
></ code > code ></ span >
)
Although this method is used internally, it can be handy if you’re 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.
Using Logging to debug
Logging messages is another good way to debug applications, and you can use
to do logging in your application. All objects that extend Object have an instance method log() which can be used to log messages:
$this -> log ( 'Got here' , 'debug' );
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
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' );
Debugging 905
CakePHP Cookbook Documentation, Release 2.x
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 DebugKit
3 from GitHub.
Xdebug
If your environment supplies the Xdebug PHP extension, fatal errors will show additional Xdebug stack trace details. Details about Xdebug can be found at Xdebug
4
.
Testing
CakePHP comes with comprehensive testing support built-in. CakePHP comes with integration for PH-
PUnit
5
. 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.38"
Install via .phar Package
You can also download the file directly.
Just make sure you get the correct version from https://phar.phpunit.de/ . Make sure /usr/local/bin is in your php.ini file’s include_path: wget https :// phar .
phpunit .
de / phpunit 3.7
.
38.
phar O phpunit .
phar chmod + x phpunit .
phar mv phpunit .
phar / usr / local / bin / phpunit
Note: PHPUnit 4 is not compatible with CakePHP’s Unit Testing.
Depending on your system’s configuration, you may need to run the previous commands with sudo
3 https://github.com/cakephp/debug_kit/tree/2.2
4 http://xdebug.org
5 http://phpunit.de
906 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Note: In CakePHP 2.5.7 and later you can place the phar directly in your vendors or App/Vendor directory.
Tip: All output is swallowed when using PHPUnit 3.6+. Add the --debug modifier if using the CLI or add &debug=1 to the URL if using the web runner to display output.
Test Database Setup
Remember to have a debug level of at least 1 in your app/Config/core.php file before running any tests. Tests are not accessible via the web runner when debug is equal to 0. Before running any tests you should be sure to add a $test database configuration. This configuration is used by CakePHP for fixture tables and data:
public
$test =
array
(
'datasource' => 'Database/Mysql' ,
'persistent' =>
false
,
'host'
'login'
=>
=>
'dbhost' ,
'dblogin' ,
'password' => 'dbpassword' ,
'database' => 'test_database'
);
Note: It’s a good idea to make the test database and your actual database different databases. This will prevent any embarrassing mistakes later.
Checking the Test Setup
After installing PHPUnit and setting up your $test database configuration you can make sure you’re ready to write and run your own tests by running one of the core tests. There are two built-in runners for testing, we’ll start off by using the web runner. The tests can then be accessed by browsing to http://localhost/your_app/test.php
. You should see a list of the core test cases. Click on the ‘AllConfigure’ test. You should see a green bar with some additional information about the tests run, and number passed.
Congratulations, you are now ready to start writing tests!
Test Case Conventions
Like most things in CakePHP, test cases have some conventions. Concerning tests:
1. PHP files containing tests should be in your app/Test/Case/[Type] directories.
2. The filenames of these files should end in Test.php instead of just .php.
3. The classes containing tests should extend CakeTestCase, ControllerTestCase or
PHPUnit_Framework_TestCase
.
Testing 907
CakePHP Cookbook Documentation, Release 2.x
4. Like other class names, the test case class names should match the filename. RouterTest.php
should contain class RouterTest extends CakeTestCase.
5. The name of any method containing a test (i.e. containing an assertion) should begin with test, as in testPublished(). You can also use the @test annotation to mark methods as test methods.
When you have created a test case, you can execute it by browsing to http://localhost/your_app/test.php
(depending on how your specific setup looks).
Click App test cases, and then click the link to your specific file. You can run tests from the command line using the test shell:
./ Console / cake test app Model / Post
For example, would run the tests for your Post model.
Creating Your First Test Case
In the following example, we’ll create a test case for a very simple helper method. The helper we’re going to test will be formatting progress bar HTML. Our helper looks like:
class ProgressHelper extends
AppHelper {
public function
bar ( $value ) {
$width = round ( $value / 100 , 2 )
return
sprintf (
*
100 ;
'<div class="progress-container">
<div class="progress-bar" style="width: %s%%"></div>
</div>' , $width );
}
}
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, we’ll create the test case file in app/Test/Case/View/Helper/ProgressHelperTest.php
. In that file we’ll 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 () {
}
}
We’ll flesh out this skeleton in a minute. We’ve 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 we’ll add the following:
908 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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 of things like backing up the values in
and, storing the paths in
Next, we’ll fill out the test method. We’ll 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 you’re 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, you’ll want to run the test cases very frequently. It’s a good idea to run tests before committing any changes to help ensure you haven’t 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 you’re 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 you’ve 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.
Testing 909
CakePHP Cookbook Documentation, Release 2.x
Viewing code coverage
If you have Xdebug
6 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.
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
6 http://xdebug.org
910 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Note: If you are running tests that interact with the session it’s 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 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
Filtering test cases
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 PHPUnit’s 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 application’s 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 you’ll 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.
Test Case Lifecycle Callbacks
Test cases have a number of lifecycle callbacks you can use when doing testing:
Testing 911
CakePHP Cookbook Documentation, Release 2.x
• setUp is called before every test method. Should be used to create the objects that are going to be tested, and initialize any data for the test. Always remember to call parent::setUp()
• tearDown is called after every test method. Should be used to cleanup after the test is complete.
Always remember to call parent::tearDown().
• setupBeforeClass is called once before test methods in a case are started. This method must be static .
• 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. Let’s 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
912 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
'body' => 'text' ,
'published' =>
array
(
'type' => 'integer' ,
'default' => '0' ,
),
'null' =>
false
'created' => 'datetime' ,
'updated' => 'datetime'
);
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 model’s 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 doesn’t 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 application’s 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
Testing 913
CakePHP Cookbook Documentation, Release 2.x
• biginteger: maps to BIGINT
• integer: maps to INT
• float: maps to FLOAT
• decimal: maps to DECIMAL
• datetime: maps to DATETIME
• timestamp: maps to TIMESTAMP
• time: maps to TIME
• date: maps to DATE
• binary: maps to BLOB
• boolean: maps to TINYINT
key
Set to primary to make the field AUTO_INCREMENT, and a PRIMARY KEY for the table.
length
Set to the specific length the field should take.
null
Set to either true (to allow NULLs) or false (to disallow NULLs).
default
Default value the field takes.
We can define a set of records that will be populated after the fixture table is created. The format is fairly straight forward, $records is an array of records. Each item in $records should be a single row. Inside each row, should be an associative array of the columns and values for the row. Just keep in mind that each record in the $records array must have a key for every field specified in the $fields array. If a field for a particular record needs to have a null value, just specify the value of that key as null.
Dynamic data and fixtures
Since records for a fixture are declared as a class property, you cannot easily use functions or other dynamic data to define fixtures. To solve this problem, you can define $records in the init() function of your fixture. For example if you wanted all the created and updated timestamps to reflect today’s date you could do the following:
class ArticleFixture extends
CakeTestFixture {
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'
);
public function
init () {
$this -> records =
array
(
array
(
'id' => 1 ,
914 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
'title' => 'First Article' ,
'body' => 'First Article Body' ,
'published' => '1' ,
'created' => date ( 'Y-m-d H:i:s' ),
'updated' => date ( 'Y-m-d H:i:s' ),
),
);
parent
:: init ();
}
}
When overriding init() just remember to always call parent::init().
Importing table information and records
Your application may have already working models with real data associated to them, and you might decide to test your application with that data. It would be then a duplicate effort to have to define the table definition and/or records on your fixtures. Fortunately, there’s a way for you to define that table definition and/or records for a particular fixture come from an existing model or an existing table.
Let’s start with an example.
Assuming you have a model named Article available in your application (that maps to a table named articles), change the example fixture given in the previous section
(app/Test/Fixture/ArticleFixture.php) to:
class ArticleFixture extends
CakeTestFixture {
public
$import = 'Article' ;
}
This statement tells the test suite to import your table definition from the table linked to the model called
Article. You can use any model available in your application. The statement 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 there’s any table prefix declared it will be automatically used when fetching table information. The two snippets above do not import records from the table. To
Testing 915
CakePHP Cookbook Documentation, Release 2.x
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 ,
'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'
)
);
}
Loading fixtures in your test cases
After you’ve created your fixtures, you’ll want to use them in your test cases. In each test case you should load the fixtures you will need. You should load a fixture for every model that will have a query run against it. To load fixtures you define the $fixtures property in your model:
class ArticleTest extends
CakeTestCase {
public
$fixtures =
array
( 'app.article' , 'app.comment' );
}
The above will load the Article and Comment fixtures from the application’s Fixture directory. You can also load fixtures from CakePHP core, or plugins:
916 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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
;
public function
testMyFunction () {
$this -> loadFixtures ( 'Article' , 'Comment' );
}
}
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' );
}
In the above example, both fixtures would be loaded from App/Test/Fixture/blog/.
Changed in version 2.5: As of 2.5.0 you can load fixtures in subdirectories.
Testing Models
Let’s 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.
Testing 917
CakePHP Cookbook Documentation, Release 2.x
Let’s 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 we’ll 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 the relevant fixture uses the same value so that the table is created in the correct database.
Creating a test method
Let’s now add a method to test the function published() in the Article model.
Edit the file app/Test/Case/Model/ArticleTest.php
so it now looks like this:
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' , 'title' ));
$expected =
array
(
array
( 'Article' =>
array
( 'id' => 1 , 'title' => 'First Article' )),
array
( 'Article' =>
array
( 'id' => 2 , 'title' => 'Second Article' )),
array
( 'Article' =>
array
( 'id' => 3 , 'title' => '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
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.
918 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
Mocking model methods
There will be times you’ll want to mock methods on models when testing them.
You should use getMockForModel to create testing mocks of models. It avoids issues with reflected properties that normal mocks have:
public function
testSendingEmails () {
$model = $this -> getMockForModel ( 'EmailVerification' ,
array
( 'send' ));
$model -> expects ( $this -> once ())
-> method ( 'send' )
-> will ( $this -> returnValue (
true
));
$model -> verifyEmail ( '[email protected]' );
}
New in version 2.3: CakeTestCase::getMockForModel() was added in 2.3.
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
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 );
}
}
Testing 919
CakePHP Cookbook Documentation, Release 2.x
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 );
}
public function
testIndexShort () {
$result = $this -> testAction ( '/articles/index/short' ); debug( $result );
}
public function
testIndexShortGetRenderedHtml () {
$result = $this -> testAction (
'/articles/index/short' ,
array
( 'return' => 'contents' )
); debug( $result );
}
public function
testIndexShortGetViewVars () {
$result = $this -> testAction (
'/articles/index/short' ,
array
( 'return' => 'vars' )
); debug( $result );
}
public function
testIndexPostData () {
$data =
array
(
'Article' =>
array
(
'user_id' => 1 ,
'published' => 1 ,
'slug' => 'new-article' ,
'title' => 'New Article' ,
'body' => 'New Body'
)
);
$result = $this -> testAction (
'/articles/index' ,
array
( 'data' => $data , 'method' => 'post' )
); debug( $result );
}
}
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.
920 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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' )) {
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' ));
Testing 921
CakePHP Cookbook Documentation, Release 2.x
// 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:
• vars Get the set view variables.
• view Get the rendered view, without a layout.
• contents Get the rendered view including the layout.
• result Get the return value of the controller action. Useful for testing requestAction methods.
The default value is result. As long as your return type is not result you can also access the various other return types as properties in the test case:
public function
testIndex () {
$this -> testAction ( '/posts/index' );
$this -> assertInternalType ( 'array' , $this -> vars [ 'posts' ]);
}
Using mocks with testAction
There will be times when you want to replace components or models with either partially mocked objects or completely mocked objects. You can do this by using ControllerTestCase::generate().
generate() takes the hard work out of generating mocks on your controller. If you decide to generate a controller to be used in testing, you can generate mocked versions of its models and components along with it:
$Posts = $this -> generate ( 'Posts' ,
array
(
'methods' =>
array
(
'isAuthorized'
),
'models' =>
array
(
'Post' =>
array
( 'save' )
),
'components' =>
array
(
'RequestHandler' =>
array
( 'isPut' ),
'Email' =>
array
( 'send' ),
'Session'
)
));
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
922 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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
.
A more complex example
In its simplest form, testAction() will run PostsController::index() on your testing controller (or an automatically generated one), including all of the mocked models and components. The results of the test are stored in the vars, contents, view, and return properties. Also available is a headers property which gives you access to the headers that would have been sent, allowing you to check for redirects:
public function
testAdd () {
$Posts = $this -> generate ( 'Posts' ,
array
(
'components' =>
array
(
'Session' ,
'Email' =>
array
( 'send' )
)
));
$Posts -> Session
-> expects ( $this -> once ())
-> method ( 'setFlash' );
$Posts -> Email
-> expects ( $this -> once ())
-> method ( 'send' )
-> will ( $this -> returnValue (
true
));
$this -> testAction ( '/posts/add' ,
array
(
'data' =>
array
(
'Post' =>
array
( 'title' => 'New Post' )
)
));
$this -> assertContains ( '/posts' , $this -> headers [ 'Location' ]);
}
public function
testAddGet () {
$this -> testAction ( '/posts/add' ,
array
(
'method' => 'GET' ,
'return' => 'contents'
));
$this -> assertRegExp ( '/<html/' , $this -> contents );
$this -> assertRegExp ( '/<form/' , $this -> view );
}
This example shows a slightly more complex use of the testAction() and generate() methods.
Testing 923
CakePHP Cookbook Documentation, Release 2.x
First, we generate a testing controller and mock the
nent 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 you’ll have to use a different method to register your mock expectations. For example if you wanted to mock out
you’d have to do the following:
public function
testAdd () {
$Posts = $this -> generate ( 'Posts' ,
array
(
'components' =>
array
(
'Session' ,
'Auth' =>
array
( 'user' )
)
));
$Posts -> Auth -> staticExpects ( $this -> any ())
-> method ( 'user' )
-> with ( 'id' )
-> will ( $this -> returnValue ( 2 ));
}
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 ));
}
}
Now we create the file app/Test/Case/Controller/MarkersControllerTest.php and make sure our web service is returning the proper response:
class MarkersControllerTest extends
ControllerTestCase {
public function
testIndex () {
$result = $this -> testAction ( '/markers/index.json' );
$result = json_decode ( $result ,
true
);
$expected =
array
(
'Marker' =>
array
( 'id' => 1 , 'lng' => 66 , 'lat' => 45 ),
);
$this -> assertEquals ( $expected , $result );
924 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
}
}
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
‘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 webdriver
7
Testing Components
Let’s 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
:
7 http://seleniumhq.org
Testing 925
CakePHP Cookbook Documentation, Release 2.x
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
;
}
class PagematronComponentTest extends
CakeTestCase {
public
$PagematronComponent =
null
;
public
$Controller =
null
;
public function
setUp () {
parent
:: setUp ();
// Setup our component and fake test controller
$Collection =
new
ComponentCollection();
$this -> PagematronComponent =
new
PagematronComponent( $Collection );
$CakeRequest =
new
CakeRequest();
$CakeResponse =
new
CakeResponse();
$this -> Controller =
new
PagematronControllerTest( $CakeRequest , $CakeResponse );
$this -> PagematronComponent -> startup ( $this -> Controller );
}
public function
testAdjust () {
// Test our adjust method with different parameter settings
$this -> PagematronComponent -> adjust ();
$this -> assertEquals ( 20 , $this -> Controller -> paginate [ 'limit' ]);
$this -> PagematronComponent -> adjust ( 'medium' );
$this -> assertEquals ( 50 , $this -> Controller -> paginate [ 'limit' ]);
$this -> PagematronComponent -> adjust ( 'long' );
$this -> assertEquals ( 100 , $this -> Controller -> paginate [ 'limit' ]);
}
public function
tearDown () {
parent
:: tearDown ();
// Clean up after we're done unset ( $this -> PagematronComponent ); unset ( $this -> Controller );
}
}
Testing Helpers
Since a decent amount of logic resides in Helper classes, it’s 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 curren-
926 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
cies 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.
Now we create our tests:
// app/Test/Case/View/Helper/CurrencyRendererHelperTest.php
App :: uses ( 'Controller' , 'Controller' );
App :: uses ( 'View' , 'View' );
App :: uses ( 'CurrencyRendererHelper' , 'View/Helper' );
class CurrencyRendererHelperTest extends
CakeTestCase {
public
$CurrencyRenderer =
null
;
// Here we instantiate our helper
public function
setUp () {
parent
:: setUp ();
$Controller =
new
Controller();
$View =
new
View( $Controller );
$this -> CurrencyRenderer =
new
CurrencyRendererHelper( $View );
}
// Testing the usd() function
public function
testUsd () {
$this -> assertEquals ( 'USD 5.30' , $this -> CurrencyRenderer -> usd ( 5.30
));
// We should always have 2 decimal digits
$this -> assertEquals ( 'USD 1.00' , $this -> CurrencyRenderer -> usd ( 1 ));
$this -> assertEquals ( 'USD 2.05' , $this -> CurrencyRenderer -> usd ( 2.05
));
// Testing the thousands separator
$this -> assertEquals (
);
'USD 12,000.70' ,
$this -> CurrencyRenderer -> usd ( 12000.70
)
}
}
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 927
CakePHP Cookbook Documentation, Release 2.x
Creating Test Suites
If you want several of your tests to run at the same time, you can create a test suite. A test suite is composed of several test cases. CakeTestSuite offers a few methods for easily creating test suites based on the file system. If we wanted to create a test suite for all our model tests we would create app/Test/Case/AllModelTest.php
. Put the following in it:
class AllModelTest extends
CakeTestSuite {
public static function
suite () {
$suite =
new
CakeTestSuite( 'All model tests' );
$suite -> addTestDirectory (TESTS .
'Case/Model' );
return
$suite ;
}
}
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 application’s 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
Creating Tests for Plugins
Tests for plugins are created in their own directory inside the plugins folder.
/ app
/ Plugin
/ Blog
/ Test
/
Case
/ Fixture
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:
928 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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' );
// do some useful test here
$this -> assertTrue ( is_object ( $this -> BlogPost ));
}
}
If you want to use plugin fixtures in the app tests you can reference them using plugin.pluginName.fixtureName
syntax in the $fixtures array.
Integration with Jenkins
Jenkins
8 is a continuous integration server, that can help you automate the running of your test cases. This helps ensure that all your tests stay passing and your application is always ready.
Integrating a CakePHP application with Jenkins is fairly straightforward. The following assumes you’ve already installed Jenkins on *nix system, and are able to administer it. You also know how to create jobs, and run builds. If you are unsure of any of these, refer to the Jenkins documentation
9
.
Create a job
Start off by creating a job for your application, and connect your repository so that jenkins can access your code.
Add test database config
Using a separate database just for Jenkins is generally a good idea, as it stops bleed through and avoids a number of basic problems. Once you’ve created a new database in a database server that jenkins can access
(usually localhost). Add a shell script step to the build that contains the following: cat > app / Config / database .
php << 'DATABASE_PHP'
<?
php
class DATABASE_CONFIG
{
public
$test =
array
(
'datasource' => 'Database/Mysql' ,
'host' => 'localhost' ,
8 http://jenkins-ci.org
9 http://jenkins-ci.org/
Testing 929
CakePHP Cookbook Documentation, Release 2.x
'database' => 'jenkins_test' ,
'login' => 'jenkins' ,
'password' => 'cakephp_jenkins' ,
'encoding' => 'utf8'
);
}
DATABASE_PHP
This ensures that you’ll always have the correct database configuration that Jenkins requires. Do the same for any other configuration files you need to. It’s 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: mysql u jenkins pcakephp_jenkins e 'DROP DATABASE IF EXISTS jenkins_test; CREATE DATABASE jenkins_test' ;
Add your tests
Add another shell script step to your build. In this step run the tests for your application. Creating a junit log file, or clover coverage is often a nice bonus, as it gives you a nice graphical view of your testing results: app / Console / cake test app AllTests \
-stderr \
-log junit junit .
xml \
-coverage clover clover .
xml
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 won’t 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 you’ve created in your application. It’s simple, usually XML-based (we’re talking simple XML, nothing like a SOAP envelope), and depends on HTTP headers for direction. Exposing an API via REST in CakePHP is simple.
The Simple Setup
The fastest way to get up and running with REST is to add a few lines to your routes.php file, found in app/Config. The Router object features a method called mapResources(), that is used to set up a
930 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
number of default routes for REST access to your controllers. Make sure mapResources() comes before require CAKE . ’Config’ .
DS . ’routes.php’; and other routes which would override the routes. If we wanted to allow REST access to a recipe database, we’d do something like this:
//In app/Config/routes.php...
Router :: mapResources ( 'recipes' );
Router :: parseExtensions ();
The first line sets up a number of default routes for easy REST access while parseExtensions() method specifies the desired result format (e.g. xml, json, rss). These routes are HTTP Request Method sensitive.
HTTP format URL format
GET
GET
POST
POST
PUT
DELETE
/recipes.format
/recipes/123.format
/recipes.format
Controller action invoked
RecipesController::index()
RecipesController::view(123)
RecipesController::add()
/recipes/123.format
RecipesController::edit(123)
/recipes/123.format
RecipesController::edit(123)
/recipes/123.format
RecipesController::delete(123)
CakePHP’s 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' )
));
REST 931
CakePHP Cookbook Documentation, Release 2.x
}
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' )
));
}
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 we’ve added a call to
, the CakePHP router is already primed to
serve up different views based on different kinds of requests. Since we’re dealing with REST requests, we’ll be making XML views. You can also easily make JSON views using CakePHP’s built-in
JSON and XML views . By using the built in
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
for quick-and-easy XML output in those views.
Here’s what our index view might look like:
932 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
// 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 we’re 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" >
< author id = "23423" first_name = "Billy" last_name = "Bob" ></ author >
< comment id = "245" body = "Yummy yummmy" ></ comment >
</ recipe >
< recipe id = "3247" created = "2008-06-15" modified = "2008-06-15" >
< author id = "625" first_name = "Nate" last_name = "Johnson" ></ author >
< comment id = "654" body = "This is a comment for this tasty dish." ></ comment >
</ recipe >
</ recipes >
Creating the logic for the edit action is a bit trickier, but not by much. Since you’re providing an API that outputs XML, it’s a natural choice to receive XML as input. Not to worry, the RequestHandler and
classes make things much easier. If a POST or PUT request has an XML content-type, then the input is run through CakePHP’s
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
.
Accepting Input in Other Formats
Typically REST applications not only output content in alternate data formats, but also accept data in different formats. In CakePHP, the
helps facilitate this. By default, it will decode any incoming JSON/XML input data for POST/PUT requests and supply the array version of that data in $this->request->data. You can also wire in additional deserializers for alternate formats if you need them, using RequestHandler::addInputType().
Modifying the default REST routes
New in version 2.1.
If the default REST routes don’t work for your application, you can modify them using
Router::resourceMap()
.
This method allows you to set the default routes that get set with
. When using this method you need to set all the defaults you want to
use:
REST 933
CakePHP Cookbook Documentation, Release 2.x
Router :: resourceMap (
array
(
array
( 'action' => 'index' , 'method' => 'GET' , 'id' =>
false
),
array
( 'action' => 'view' , 'method' => 'GET' , 'id' =>
true
),
array
( 'action' => 'add' , 'method' => 'POST' , 'id' =>
false
),
array
( 'action' => 'edit' , 'method' => 'PUT' , 'id' =>
true
),
array
( 'action' => 'delete' , 'method' => 'DELETE' , 'id' =>
true
),
array
( 'action' => 'update' , 'method' => 'POST' , 'id' =>
true
)
));
By overwriting the default resource map, future calls to mapResources() will use the new values.
Custom REST Routing
If the default routes created by
don’t work for you, use the
method to define a custom set of REST routes. The connect() method allows you to define a number of different options for a given URL. See the section on
Conditions When Matching Routes
for more information.
New in version 2.5.
You can provide connectOptions key in the $options array for
to provide custom setting used by
Router :: mapResources ( 'books' ,
array
(
'connectOptions' =>
array
(
'routeClass' => 'ApiRoute' ,
)
));
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, let’s take a look at how they are added:
934 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
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 plugin’s 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 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 callback
10 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
10 http://php.net/callback
Dispatcher Filters 935
CakePHP Cookbook Documentation, Release 2.x
),
'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 get into that soon. Finally, CakePHP’s 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 CakePHP’s directory. Let’s 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 ;
936 Chapter 10. Development
CakePHP Cookbook Documentation, Release 2.x
}
}
}
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, let’s 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 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
and
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.
Let’s 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
class and serve the response from the beforeDispatch callback.
Dispatcher Filters 937
CakePHP Cookbook Documentation, Release 2.x
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
class, but let’s 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' )));
$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.
938 Chapter 10. Development
Download
Annonce