5.0 Migration Guide
CakePHP 5.0 contains breaking changes, and is not backwards compatible with 4.x releases. Before attempting to upgrade to 5.0, first upgrade to 4.5 and resolve all deprecation warnings.
Refer to the 5.0 Upgrade Guide for step by step instructions on how to upgrade to 5.0.
Deprecated Features Removed
All methods, properties and functionality that were emitting deprecation warnings as of 4.5 have been removed.
Breaking Changes
In addition to the removal of deprecated features there have been breaking changes made:
Global
Type declarations were added to all function parameter and returns where possible. These are intended to match the docblock annotations, but include fixes for incorrect annotations.
Type declarations were added to all class properties where possible. These also include some fixes for incorrect annotations.
The
SECOND,MINUTE,HOUR,DAY,WEEK,MONTH,YEARconstants were removed.Use of
#[\AllowDynamicProperties]removed everywhere. It was used for the following classes:Command/CommandConsole/ShellController/ComponentController/ControllerMailer/MailerView/CellView/HelperView/View
The supported database engine versions were updated:
- MySQL (5.7 or higher)
- MariaDB (10.1 or higher)
- PostgreSQL (9.6 or higher)
- Microsoft SQL Server (2012 or higher)
- SQLite 3 (3.16 or higher)
Auth
- Auth has been removed. Use the cakephp/authentication and cakephp/authorization plugins instead.
Cache
- The
Wincacheengine was removed. The wincache extension is not supported on PHP 8.
Collection
combine()now throws an exception if the key path or group path doesn't exist or contains a null value. This matches the behavior ofindexBy()andgroupBy().
Console
BaseCommand::__construct()was removed.ConsoleIntegrationTestTrait::useCommandRunner()was removed since it's no longer needed.Shellhas been removed and should be replaced with CommandConsoleOptionParser::addSubcommand()was removed alongside the removal ofShell. Subcommands should be replaced withCommandclasses that implementCommand::defaultName()to define the necessary command name.BaseCommandnow emitsCommand.beforeExecuteandCommand.afterExecuteevents around the command'sexecute()method being invoked by the framework.
Connection
Connection::prepare()has been removed. You can useConnection::execute()instead to execute a SQL query by specifing the SQL string, params and types in a single call.Connection::enableQueryLogging()has been removed. If you haven't enabled logging through the connection config then you can later set the logger instance for the driver to enable query logging$connection->getDriver()->setLogger().
Controller
- The method signature for
Controller::__construct()has changed. So you need to adjust your code accordingly if you are overriding the constructor. - After loading components are no longer set as dynamic properties. Instead
Controlleruses__get()to provide property access to components. This change can impact applications that useproperty_exists()on components. - The components'
Controller.shutdownevent callback has been renamed fromshutdowntoafterFilterto match the controller one. This makes the callbacks more consistent. PaginatorComponenthas been removed and should be replaced by calling$this->paginate()in your controller or usingCake\Datasource\Paging\NumericPaginatordirectlyRequestHandlerComponenthas been removed. See the 4.4 migration guide for how to upgradeSecurityComponenthas been removed. UseFormProtectionComponentfor form tampering protection orHttpsEnforcerMiddlewareto enforce use of HTTPS for requests instead.Controller::paginate()no longer accepts query options likecontainfor its$settingsargument. You should instead use thefinderoption$this->paginate($this->Articles, ['finder' => 'published']). Or you can create required select query before hand and then pass it topaginate()$query = $this->Articles->find()->where(['is_published' => true]); $this->paginate($query);.
Core
- The function
getTypeName()has been dropped. Use PHP'sget_debug_type()instead. - The dependency on
league/containerwas updated to4.x. This will require the addition of typehints to yourServiceProviderimplementations. deprecationWarning()now has a$versionparameter.- The
App.uploadedFilesAsObjectsconfiguration option has been removed alongside of support for PHP file upload shaped arrays throughout the framework. ClassLoaderhas been removed. Use composer to generate autoload files instead.
Database
- The
DateTimeTypeandDateTypenow always return immutable objects. Additionally the interface forDateobjects reflects theChronosDateinterface which lacks all of the time related methods that were present in CakePHP 4.x. DateType::setLocaleFormat()no longer accepts an array.Querynow accepts only\Closureparameters instead ofcallable. Callables can be converted to closures using the new first-class array syntax in PHP 8.1.Query::execute()no longer runs results decorator callbacks. You must useQuery::all()instead.TableSchemaAwareInterfacewas removed.Driver::quote()was removed. Use prepared statements instead.Query::orderBy()was added to replaceQuery::order().Query::groupBy()was added to replaceQuery::group().SqlDialectTraithas been removed and all its functionality has been moved into theDriverclass itself.CaseExpressionhas been removed and should be replaced withQueryExpression::case()orCaseStatementExpressionConnection::connect()has been removed. Use$connection->getDriver()->connect()instead.Connection::disconnect()has been removed. Use$connection->getDriver()->disconnect()instead.cake.database.querieshas been added as an alternative to thequeriesLogscope- The ability to enable/disable ResultSet buffering has been removed. Results are always buffered.
Datasource
- The
getAccessible()method was added toEntityInterface. Non-ORM implementations need to implement this method now. - The
aliasField()method was added toRepositoryInterface. Non-ORM implementations need to implement this method now.
Event
- Event payloads must be an array. Other object such as
ArrayAccessare no longer cast to array and will raise aTypeErrornow. - It is recommended to adjust event handlers to be void methods and use
$event->setResult()instead of returning the result
Error
ErrorHandlerandConsoleErrorHandlerhave been removed. See the 4.4 migration guide for how to upgradeExceptionRendererhas been removed and should be replaced withWebExceptionRendererErrorLoggerInterface::log()has been removed and should be replaced withErrorLoggerInterface::logException()ErrorLoggerInterface::logMessage()has been removed and should be replaced withErrorLoggerInterface::logError()
Filesystem
- The Filesystem package was removed, and
Filesystemclass was moved to the Utility package.
Http
ServerRequestis no longer compatible withfilesas arrays. This behavior has been disabled by default since 4.1.0. Thefilesdata will now always containUploadedFileInterfacesobjects.
I18n
FrozenDatewas renamed to Date andFrozenTimewas renamed to DateTime.Timenow extendsCake\Chronos\ChronosTimeand is therefore immutable.Dateobjects do not extendDateTimeInterfaceanymore - therefore you can't compare them withDateTimeobjects. See the cakephp/chronos release documentation for more information.Date::parseDateTime()was removed.Date::parseTime()was removed.Date::setToStringFormat()andDate::setJsonEncodeFormat()no longer accept an array.Date::i18nFormat()andDate::nice()no longer accept a timezone parameter.- Translation files for plugins with vendor prefixed names (
FooBar/Awesome) will now have that prefix in the file name, e.g.foo_bar_awesome.poto avoid collision with aawesome.pofile from a corresponding plugin (Awesome).
Log
- Log engine config now uses
nullinstead offalseto disable scopes. So instead of'scopes' => falseyou need to use'scopes' => nullin your log config.
Mailer
Emailhas been removed. Use Mailer instead.cake.mailerhas been added as an alternative to theemailscope
ORM
EntityTrait::has()now returnstruewhen an attribute exists and is set tonull. In previous versions of CakePHP this would returnfalse. See the release notes for 4.5.0 for how to adopt this behavior in 4.x.EntityTrait::extractOriginal()now returns only existing fields, similar toextractOriginalChanged().- Finder arguments are now required to be associative arrays as they were always expected to be.
TranslateBehaviornow defaults to theShadowTablestrategy. If you are using theEavstrategy you will need to update your behavior configuration to retain the previous behavior.allowMultipleNullsoption forisUniquerule now default to true matching the original 3.x behavior.Table::query()has been removed in favor of query-type specific functions.Table::updateQuery(),Table::selectQuery(),Table::insertQuery(), andTable::deleteQuery()) were added and return the new type-specific query objects below.SelectQuery,InsertQuery,UpdateQueryandDeleteQuerywere added which represent only a single type of query and do not allow switching between query types nor calling functions unrelated to the specific query type.Table::_initializeSchema()has been removed and should be replaced by calling$this->getSchema()inside theinitialize()method.SaveOptionsBuilderhas been removed. Use a normal array for options instead.
Known Issues
Memory usage with large result sets
Compared to CakePHP 4.x, when working with large data sets (especially when calling collection methods like extract() on the result set), you may encounter high memory usage due to the entire result set being buffered in memory.
You can work around this issue by disabling results buffering for the query:
$results = $articles->find()
->disableBufferedResults()
->all();Depending on your use case, you may also consider using disabling hydration:
$results = $articles->find()
->disableHydration()
->all();The above will disable creation of entity objects and return rows as arrays instead.
Routing
- Static methods
connect(),prefix(),scope()andplugin()of theRouterhave been removed and should be replaced by calling their non-static method variants via theRouteBuilderinstance. RedirectExceptionhas been removed. Use\Cake\Http\Exception\RedirectExceptioninstead.
TestSuite
TestSuitewas removed. Users should use environment variables to customize unit test settings instead.TestListenerTraitwas removed. PHPUnit dropped support for these listeners. See PHPUnit 10 UpgradeIntegrationTestTrait::configRequest()now merges config when called multiple times instead of replacing the currently present config.
Validation
Validation::isEmpty()is no longer compatible with file upload shaped arrays. Support for PHP file upload arrays has been removed fromServerRequestas well so you should not see this as a problem outside of tests.- Previously, most data validation error messages were simply
The provided value is invalid. Now, the data validation error messages are worded more precisely. For example,The provided value must be greater than or equal to \`5\`.
View
ViewBuilderoptions are now truly associative (string keys).NumberHelperandTextHelperno longer accept anengineconfig.ViewBuilder::setHelpers()parameter$mergewas removed. UseViewBuilder::addHelpers()instead.- Inside
View::initialize(), prefer usingaddHelper()instead ofloadHelper(). All configured helpers will be loaded afterwards, anyway. View\Widget\FileWidgetis no longer compatible with PHP file upload shaped arrays. This is aligned withServerRequestandValidationchanges.FormHelperno longer setsautocomplete=offon CSRF token fields. This was a workaround for a Safari bug that is no longer relevant.
Deprecations
The following is a list of deprecated methods, properties and behaviors. These features will continue to function in 5.x and will be removed in 6.0.
Database
Query::order()was deprecated. UseQuery::orderBy()instead now thatConnectionmethods are no longer proxied. This aligns the function name with the SQL statement.Query::group()was deprecated. UseQuery::groupBy()instead now thatConnectionmethods are no longer proxied. This aligns the function name with the SQL statement.
ORM
- Calling
Table::find()with options array is deprecated. Use named arguments instead. For e.g. instead offind('all', ['conditions' => $array])usefind('all', conditions: $array). Similarly for custom finder options, instead offind('list', ['valueField' => 'name'])usefind('list', valueField: 'name')or multiple named arguments likefind(type: 'list', valueField: 'name', conditions: $array).
New Features
Improved type checking
CakePHP 5 leverages the expanded type system feature available in PHP 8.1+. CakePHP also uses assert() to provide improved error messages and additional type soundness. In production mode, you can configure PHP to not generate code for assert() yielding improved application performance. See the Symlink Assets for how to do this.
Collection
- Added
unique()which filters out duplicate value specified by provided callback. reject()now supports a default callback which filters out truthy values which is the inverse of the default behavior offilter()
Core
- The
services()method was added toPluginInterface. PluginCollection::addFromConfig()has been added to simplify plugin loading.
Database
ConnectionManagernow supports read and write connection roles. Roles can be configured withreadandwritekeys in the connection config that override the shared config.Query::all()was added which runs result decorator callbacks and returns a result set for select queries.Query::comment()was added to add a SQL comment to the executed query. This makes it easier to debug queries.EnumTypewas added to allow mapping between PHP backed enums and a string or integer column.getMaxAliasLength()andgetConnectionRetries()were added toDriverInterface.- Supported drivers now automatically add auto-increment only to integer primary keys named "id" instead of all integer primary keys. Setting 'autoIncrement' to false always disables on all supported drivers.
Http
- Added support for PSR-17 factories interface. This allows
cakephp/httpto provide a client implementation to libraries that allow automatic interface resolution like php-http. - Added
CookieCollection::__get()andCookieCollection::__isset()to add ergonomic ways to access cookies without exceptions.
ORM
Required Entity Fields
Entities have a new opt-in functionality that allows making entities handle properties more strictly. The new behavior is called 'required fields'. When enabled, accessing properties that are not defined in the entity will raise exceptions. This impacts the following usage:
$entity->get();
$entity->has();
$entity->getOriginal();
isset($entity->attribute);
$entity->attribute;Fields are considered defined if they pass array_key_exists. This includes null values. Because this can be a tedious to enable feature, it was deferred to 5.0. We'd like any feedback you have on this feature as we're considering making this the default behavior in the future.
Typed Finder Parameters
Table finders can now have typed arguments as required instead of an options array. For e.g. a finder for fetching posts by category or user:
public function findByCategoryOrUser(SelectQuery $query, array $options)
{
if (isset($options['categoryId'])) {
$query->where(['category_id' => $options['categoryId']]);
}
if (isset($options['userId'])) {
$query->where(['user_id' => $options['userId']]);
}
return $query;
}can now be written as:
public function findByCategoryOrUser(SelectQuery $query, ?int $categoryId = null, ?int $userId = null)
{
if ($categoryId) {
$query->where(['category_id' => $categoryId]);
}
if ($userId) {
$query->where(['user_id' => $userId]);
}
return $query;
}The finder can then be called as find('byCategoryOrUser', userId: $somevar). You can even include the special named arguments for setting query clauses. find('byCategoryOrUser', userId: $somevar, conditions: ['enabled' => true]).
A similar change has been applied to the RepositoryInterface::get() method:
public function view(int $id)
{
$author = $this->Authors->get($id, [
'contain' => ['Books'],
'finder' => 'latest',
]);
}can now be written as:
public function view(int $id)
{
$author = $this->Authors->get($id, contain: ['Books'], finder: 'latest');
}TestSuite
IntegrationTestTrait::requestAsJson()has been added to set JSON headers for the next request.
Plugin Installer
- The plugin installer has been updated to automatically handle class autoloading for your app plugins. So you can remove the namespace to path mappings for your plugins from your
composer.jsonand just runcomposer dumpautoload.