This page summarizes the changes from CakePHP 1.3 that will assist in a project migration to 2.0, as well as for a developer reference to get up to date with the changes made to the core since the CakePHP 1.3 branch. Be sure to read the other pages in this guide for all the new features and API changes.
Tip
Be sure to checkout the Upgrade shell included in the 2.0 core to help you migrate your 1.3 code to 2.0.
CakePHP 2.x supports PHP Version 5.2.8 and above. PHP4 support has been dropped. For developers that are still working with production PHP4 environments, the CakePHP 1.x versions continue to support PHP4 for the lifetime of their development and support lifetime.
The move to PHP 5 means all methods and properties have been updated with visibility keywords. If your code is attempting access to private or protected methods from a public scope, you will encounter errors.
While this does not really constitute a large framework change, it means that access to tighter visibility methods and variables is now not possible.
In CakePHP 2.0 we rethought the way we are structuring our files and folders. Given that PHP 5.3 is supporting namespaces we decided to prepare our code base for adopting in a near future this PHP version, so we adopted the https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md. At first we glanced at the internal structure of CakePHP 1.3 and realized that after all these years there was no clear organization in the files, nor did the directory structure really hint where each file should be located. With this change we would be allowed to experiment a little with (almost) automatic class loading for increasing the overall framework performance.
Biggest roadblock for achieving this was maintaining some sort of backwards compatibility in the way the classes are loaded right now, and we definitely did not want to become a framework of huge class prefixes, having classnames like My_Huge_Class_Name_In_Package. We decided adopting a strategy of keeping simple class names while offering a very intuitive way of declaring class locations and clear migration path for future PHP 5.3 version of CakePHP. First let’s highlight the main changes in file naming standard we adopted:
All files containing classes should be named after the class it contains. No file should contain more than one class. So, no more lowercasing and underscoring your file names. Here are some examples:
This makes file naming a lot more clear and consistent across applications, and also avoids a few edge cases where the file loader would get confused in the past and load files it should not.
Most folders should be also CamelCased, especially when containing classes. Think of namespaces, each folder represents a level in the namespacing hierarchy, folders that do not contain classes, or do not constitute a namespace on themselves, should be lowercased.
CamelCased Folders:
lowercased Folders:
In your app/webroot/.htaccess replace line RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] with RewriteRule ^(.*)$ index.php?/$1 [QSA,L]
The app/app_controller.php, app/app_model.php, app/app_helper.php are now located and named as app/Controller/AppController.php, app/Model/AppModel.php and app/Helper/AppHelper.php respectively.
Also all shell/task now extend AppShell. You can have your custom AppShell.php at app/Console/Command/AppShell.php
__() (Double underscore shortcut function) always returns the translation (not echo anymore).
If you want to echo the result of the translation, use:
<?php
echo __('My Message');
This change includes all shortcut translation methods:
__()
__n()
__d()
__dn()
__dc()
__dcn()
__c()
Alongside this, if you pass additional parameters, the translation will call sprintf with these parameters before returning. For example:
<?php
// Will return something like "Called: MyClass:myMethod"
echo __('Called: %s:%s', $className, $methodName);
It is valid for all shortcut translation methods.
More information about the specifiers, you can see in sprintf function.
The constants APP and CORE_PATH have consistent values between the web and console environments. In previous versions of CakePHP these values changed depending on your environment.
A number of constants were removed, as they were no longer accurate, or duplicated.
This new class encapsulates the parameters and functions related to an incoming request. It replaces many features inside Dispatcher, RequestHandlerComponent and Controller. It also replaces $this->params array in all places. CakeRequest implements ArrayAccess so many interactions with the old params array do not need to change. See the CakeRequest new features for more information.
CakePHP no longer uses $_GET['url'] for handling application request paths. Instead it uses $_SERVER['PATH_INFO']. This provides a more uniform way of handling requests between servers with URL rewriting and those without. Because of these changes, you’ll need to update your .htaccess files and app/webroot/index.php, as these files were changed to accommodate the changes. Additionally $this->params['url']['url'] no longer exists. Instead you should be using $this->request->url to access the same value.
Component is now the required base class for all components. You should update your components and their constructors, as both have changed:
<?php
class PrgComponent extends Component {
function __construct(ComponentCollection $collection, $settings = array()) {
parent::__construct($collection, $settings);
}
}
As with helpers it is important to call parent::__construct() in components with overridden constructors. Settings for a component are also passed into the constructor now, and not the initialize() callback. This makes getting well constructed objects easier, and allows the base class to handle setting the properties up.
Since settings have been moved to the component constructor, the initialize() callback no longer receives $settings as its 2nd parameter. You should update your components to use the following method signature:
function initialize(Controller $controller) { }
Additionally, the initialize() method is only called on components that are enabled. This usually means components that are directly attached to the controller object.
All the deprecated callbacks in Component have not been transferred to ComponentCollection. Instead you should use the trigger() method to interact with callbacks. If you need to trigger a callback you could do so by calling:
<?php
$this->Components->trigger('someCallback', array(&$this));
In the past you were able to disable components via $this->Auth->enabled = false; for example. In CakePHP 2.0 you should use the ComponentCollection’s disable method, $this->Components->disable(‘Auth’);. Using the enabled property will not work.
Many of RequestHandlerComponent’s methods are just proxies for CakeRequest methods. The following methods have been deprecated and will be removed in future versions:
SecurityComponent no longer handles Basic and Digest Authentication. These are both handled by the new AuthComponent. The following methods have been removed from SecurityComponent:
In addition the following properties were removed:
Moving these features to AuthComponent was done to provide a single place for all types of authentication and to streamline the roles of each component.
The AuthComponent was entirely re-factored for 2.0, this was done to help reduce developer confusion and frustration. In addition, AuthComponent was made more flexible and extensible. You can find out more in the Authentication guide.
The cakeError() method has been removed. It’s recommended that you switch all uses of cakeError to use exceptions. cakeError was removed because it was simulating exceptions. Instead of simulation, real exceptions are used in CakePHP 2.0.
The error handling implementation has dramatically changed in 2.0. Exceptions have been introduced throughout the framework, and error handling has been updated to offer more control and flexibility. You can read more in the Exceptions and Error Handling section.
The API for App::build() has changed to App::build($paths, $mode). It now allows you to either append, prepend or reset/replace existing paths. The $mode param can take any of the following 3 values: App::APPEND, App::PREPEND, App::RESET. The default behavior of the function remains the same (ie. Prepending new paths to existing list).
App class lost the following properties, use method App::path() to access their value
Although there has been a huge refactoring in how the classes are loaded, in very few occasions you will need to change your application code to respect the way you were used to doing it. The biggest change is the introduction of a new method:
<?php
App::uses('AuthComponent', 'Controller/Component');
We decided the function name should emulate PHP 5.3’s use keyword, just as a way of declaring where a classname should be located. The first parameter of App::uses() is the complete name of the class you intend to load, and the second one, the package name (or namespace) where it belongs to. The main difference with CakePHP 1.3’s App::import() is that the former won’t actually import the class, it will just setup the system so when the class is used for the first time it will be located.
Some examples on using App::uses() when migrating from App::import():
<?php
App::import('Controller', 'Pages');
// becomes
App::uses('PagesController', 'Controller');
App::import('Component', 'Email');
// becomes
App::uses('EmailComponent', 'Controller/Component');
App::import('View', 'Media');
// becomes
App::uses('MediaView', 'View');
App::import('Core', 'Xml');
// becomes
App::uses('Xml', 'Utility');
App::import('Datasource', 'MongoDb.MongoDbSource')
// becomes
App::uses('MongoDbSource', 'MongoDb.Model/Datasource')
All classes that were loaded in the past using App::import('Core', $class); will need to be loaded using App::uses() referring to the correct package. See the api to locate the classes in their new folders. Some examples:
<?php
App::import('Core', 'CakeRoute');
// becomes
App::uses('CakeRoute', 'Routing/Route');
App::import('Core', 'Sanitize');
// becomes
App::uses('Sanitize', 'Utility');
App::import('Core', 'HttpSocket');
// becomes
App::uses('HttpSocket', 'Network/Http');
In contrast to how App::import() worked in the past, the new class loader will not locate classes recursively. This led to an impressive performance gain even on develop mode, at the cost of some seldom used features that always caused side effects. To be clear again, the class loader will only fetch the class in the exact package in which you told it to find it.
App::build() will not merge app paths with core paths anymore.
Examples:
<?php
App::build(array('controllers' => array('/full/path/to/controllers')))
//becomes
App::build(array('Controller' => array('/full/path/to/Controller')))
App::build(array('helpers' => array('/full/path/to/controllers')))
//becomes
App::build(array('View/Helper' => array('/full/path/to/View/Helper')))
<?php
Cache::config('something');
Cache::write('key', $value);
// would become
Cache::write('key', $value, 'something');
You can no longer modify named parameter settings with Router::setRequestInfo(). You should use Router::connectNamed() to configure how named parameters are handled.
Router no longer has a getInstance() method. It is a static class, call its methods and properties statically.
Router::getNamedExpressions() is deprecated. Use the new router constants. Router::ACTION, Router::YEAR, Router::MONTH, Router::DAY, Router::ID, and Router::UUID instead.
Router::defaults() has been removed. Delete the core routes file inclusion from your applications routes.php file to disable default routing. Conversely if you want default routing, you will have to add an include to Cake/Config/routes.php in your routes file.
When using Router::parseExtensions() the extension parameter is no longer under $this->params['url']['ext']. Instead it is available at $this->request->params['ext'].
Default plugin routes have changed. Plugin short routes are no longer built in for any actions other than index. Previously /users and /users/add would map to the UsersController in the Users plugin. In 2.0, only the index action is given a short route. If you wish to continue using short routes, you can add a route like:
<?php
Router::connect('/users/:action', array('controller' => 'users', 'plugin' => 'users'));
To your routes file for each plugin you need short routes on.
Your app/Config/routes.php file needs to be updated adding this line at the bottom of the file:
<?php
require CAKE . 'Config' . DS . 'routes.php';
This is needed in order to generate the default routes for your application. If you do not wish to have such routes, or want to implement your own standard you can include your own file with custom router rules.
You should see the Xml documentation for more information on the changes made to the Xml class.
CakeSession is now a fully static class, both SessionHelper and SessionComponent are wrappers and sugar for it. It can now easily be used in models or other contexts. All of its methods are called statically.
Session configuration has also changed see the session section for more information
In order to accommodate View being removed from the ClassRegistry, the signature of Helper::__construct() was changed. You should update any subclasses to use the following:
<?php
function __construct(View $View, $settings = array())
When overriding the constructor you should always call parent::__construct as well. Helper::__construct stores the view instance at $this->_View for later reference. The settings are not handled by the parent constructor.
After examining the responsibilities of each class involved in the View layer, it became clear that View was handling much more than a single task. The responsibility of creating helpers is not central to what View does, and was moved into HelperCollection. HelperCollection is responsible for loading and constructing helpers, as well as triggering callbacks on helpers. By default, View creates a HelperCollection in its constructor, and uses it for subsequent operations. The HelperCollection for a view can be found at $this->Helpers
The motivations for refactoring this functionality came from a few issues.
You can read more about HelperCollection in the Collections documentation.
The following properties on helpers are deprecated, you should use the request object properties or Helper methods instead of directly accessing these properties as they will be removed in a future release.
The AjaxHelper and JavascriptHelper have been removed as they were deprecated in version 1.3. The XmlHelper was removed, as it was made obsolete and redundant with the improvements to Xml. The Xml class should be used to replace previous usage of XmlHelper.
The AjaxHelper, and JavascriptHelper are replaced with the JsHelper and HtmlHelper.
There have been a few improvements to pagination in general. For more information on that you should read the new pagination features page.
The $selected parameter was removed from several methods in FormHelper. All methods now support a $attributes['value'] key now which should be used in place of $selected. This change simplifies the FormHelper methods, reducing the number of arguments, and reduces the duplication that $selected created. The effected methods are:
The default url for all forms, is now the current url including passed, named, and querystring parameters. You can override this default by supplying $options['url'] in the second parameter of $this->Form->create().
CacheHelper has been fully decoupled from View, and uses helper callbacks to generate caches. You should remember to place CacheHelper after other helpers that modify content in their afterRender and afterLayout callbacks. If you don’t some changes will not be part of the cached content.
CacheHelper also no longer uses <cake:nocache> to indicate un-cached regions. Instead it uses special HTML/XML comments. <!--nocache--> and <!--/nocache-->. This helps CacheHelper generate valid markup and still perform the same functions as before. You can read more CacheHelper and View changes.
The Helper class has more 3 protected attributes:
By default the values used in CakePHP 1.3 were not changed. But now you can use boolean attributes from HTML, like <input type="checkbox" checked />. To this, just change $_minimizedAttributeFormat in your AppHelper to %s.
To use with Html/Form helpers and others, you can write:
$this->Form->checkbox('field', array('checked' => true, 'value' => 'some_value'));
Other facility is that minimized attributes can be passed as item and not as key. For example:
$this->Form->checkbox('field', array('checked', 'value' => 'some_value'));
Note that checked have a numeric key.
The deprecated properties on Controller will be accessible through a __get() method. This method will be removed in future versions, so it’s recommended that you update your application.
Controller now defines a maxLimit for pagination. This maximum limit is set to 100, but can be overridden in the $paginate options.
Pagination has traditionally been a single method in Controller, this created a number of problems though. Pagination was hard to extend, replace, or modify. For 2.0 pagination has been extracted into a component. Controller::paginate() still exists, and serves as a convenience method for loading and using the PaginatorComponent.
For more information on the new features offered by pagination in 2.0, see the Pagination documentation.
The view being registered ClassRegistry invited abuse and affectively created a global symbol. In 2.0 each Helper receives the current View instance in its constructor. This allows helpers access to the view in a similar fashion as in the past, without creating global symbols. You can access the view instance at $this->_View in any helper.
The deprecated properties on View will be accessible through a __get() method. This method will be removed in future versions, so it’s recommended that you update your application.
By default View objects contain a HelperCollection at $this->Helpers.
To use themes in your Controller you no longer set public $view = 'Theme';. Use public $viewClass = 'Theme'; instead.
beforeLayout used to fire after scripts_for_layout and content_for_layout were prepared. In 2.0, beforeLayout is fired before any of the special variables are prepared, allowing you to manipulate them before they are passed to the layout. The same was done for beforeRender. It is now fired well before any view variables are manipulated. In addition to these changes, helper callbacks always receive the name of the file about to be rendered. This combined with helpers being able to access the view through $this->_View and the current view content through $this->_View->output gives you more power than ever before.
Helper callbacks now always get one argument passed in. For beforeRender and afterRender it is the view file being rendered. For beforeLayout and afterLayout it is the layout file being rendered. Your helpers function signatures should look like:
<?php
function beforeRender($viewFile) {
}
function afterRender($viewFile) {
}
function beforeLayout($layoutFile) {
}
function afterLayout($layoutFile) {
}
Element caching, and view callbacks have been changed in 2.0 to help provide you with more flexibility and consistency. Read more about those changes.
In previous versions there was a tight coupling between CacheHelper and View. For 2.0 this coupling has been removed and CacheHelper just uses callbacks like other helpers to generate full page caches.
In previous versions, CacheHelper used a special <cake:nocache> tag as markers for output that should not be part of the full page cache. These tags were not part of any XML schema, and were not possible to validate in HTML or XML documents. For 2.0, these tags have been replaced with HTML/XML comments:
<cake:nocache> becomes <!--nocache-->
</cake:nocache> becomes <!--/nocache-->
The internal code for full page view caches has also changed, so be sure to clear out view cache files when updating.
MediaView::render() now forces download of unknown file types instead of just returning false. If you want you provide an alternate download filename you now specify the full name including extension using key ‘name’ in the array parameter passed to the function.
All of the core test cases and supporting infrastructure have been ported to use PHPUnit 3.5. Of course you can continue to use SimpleTest in your application by replacing the related files. No further support will be given for SimpleTest and it is recommended that you migrate to PHPUnit as well. For some additional information on how to migrate your tests see PHPUnit migration hints.
PHPUnit does not differentiate between group tests and single test cases in the runner. Because of this, the group test options, and support for old style group tests has been removed. It is recommended that GroupTests be ported to PHPUnit_Framework_Testsuite subclasses. You can find several examples of this in CakePHP’s test suite. Group test related methods on TestManager have also been removed.
The testsuite shell has had its invocation simplified and expanded. You no longer need to differentiate between case and group. It is assumed that all tests are cases. In the past you would have done cake testsuite app case models/post you can now do cake testsuite app Model/Post.
The testsuite shell has been refactored to use the PHPUnit cli tool. It now supports all the command line options supported by PHPUnit. cake testsuite help will show you a list of all possible modifiers.
Model relationships are now lazy loaded. You can run into a situation where assigning a value to a nonexistent model property will throw errors:
<?php
$Post->inexistentProperty[] = 'value';
will throw the error “Notice: Indirect modification of overloaded property $inexistentProperty has no effect”. Assigning an initial value to the property solves the issue:
<?php
$Post->nonexistentProperty = array();
$Post->nonexistentProperty[] = 'value';
Or just declare the property in the model class:
<?php
class Post {
public $nonexistentProperty = array();
}
Either of these approaches will solve the notice errors.
The notation of find() in Cake 1.2 is no longer supported. Finds should use notation $model->find('type', array(PARAMS)) as in Cake 1.3.
Cake 2.0 introduces some changes to Database objects that should not greatly affect backwards compatibility. The biggest one is the adoption of PDO for handling database connections. If you are using a vanilla installation of PHP 5 you will already have installed the needed extensions, but you may need to activate individual extensions for each driver you wish to use.
Using PDO across all DBOs let us homogenize the code for each one and provide more reliable and predictable behavior for all drivers. It also allowed us to write more portable and accurate tests for database related code.
The first thing users will probably miss is the “affected rows” and “total rows” statistics, as they are not reported due to the more performant and lazy design of PDO, there are ways to overcome this issue but very specific to each database. Those statistics are not gone, though, but could be missing or even inaccurate for some drivers.
A nice feature added after the PDO adoption is the ability to use prepared statements with query placeholders using the native driver if available.
DboMysqli was removed, we will support DboMysql only.
API for DboSource::execute has changed, it will now take an array of query values as second parameter:
<?php
public function execute($sql, $params = array(), $options = array())
became:
<?php
public function execute($sql, $options = array(), $params = array())
third parameter is meant to receive options for logging, currently it only understands the “log” option.
DboSource::value() looses its third parameter, it was not used anyways
DboSource::fetchAll() now accepts an array as second parameter, to pass values to be bound to the query, third parameter was dropped. Example:
<?php
$db->fetchAll('SELECT * from users where username = ? AND password = ?', array('jhon', '12345'));
$db->fetchAll('SELECT * from users where username = :username AND password = :password', array('username' => 'jhon', 'password' => '12345'));
The PDO driver will automatically escape those values for you.
No longer supports strings as configuration. Example:
<?php
public $actsAs = array(
'Acl' => 'Controlled',
'Tree' => 'nested'
);
became:
<?php
public $actsAs = array(
'Acl' => array('type' => 'Controlled'),
'Tree' => array('type' => 'nested')
);
Plugins no longer magically append their plugin prefix to components, helpers and models used within them. You must be explicit with the components, models, and helpers you wish to use. In the past:
<?php
public $components = array('Session', 'Comments');
Would look in the controller’s plugin before checking app/core components. It will now only look in the app/core components. If you wish to use objects from a plugin you must put the plugin name:
<?php
public $components = array('Session', 'Comment.Comments');
This was done to reduce hard to debug issues caused by magic misfiring. It also improves consistency in an application, as objects have one authoritative way to reference them.
The plugin AppController and AppModel are no longer located directly in the plugin folder. They are now placed into the plugin’s Controller and Model folders as such:
/app
/Plugin
/Comment
/Controller
CommentAppController.php
/Model
CommentAppModel.php
Much of the console framework was rebuilt for 2.0 to address many of the following issues:
It’s recommended that you use the help on shells you use to see what if any parameters have changed. It’s also recommended that you read the console new features for more information on new APIs that are available.
The debug() function now defaults to outputting html safe strings. This is disabled if being used in the console. The $showHtml option for debug() can be set to false to disable html-safe output from debug.
ConnectionManager::enumConnectionObjects() will now return the current configuration for each connection created, instead of an array with filename, classname and plugin, which wasn’t really useful.
When defining database connections you will need to make some changes to the way configs were defined in the past. Basically in the database configuration class, the key “driver” is not accepted anymore, only “datasource”, in order to make it more consistent. Also, as the datasources have been moved to packages you will need to pass the package they are located in. Example:
<?php
public $default = array(
'datasource' => 'Database/Mysql',
'persistent' => false,
'host' => 'localhost',
'login' => 'root',
'password' => 'root',
'database' => 'cake',
);