Migrating from CakePHP 2.10
This guide describes how to move an application from the original CakePHP 2.10.24 to pieceofcake2/cakephp, and lists the breaking changes. It is based on UPGRADE.md.
Prerequisites
Before migrating to this fork, ensure:
- Your application is running on PHP 7.4
- You're using CakePHP 2.10.24 (earlier versions are not supported)
- Your application uses Composer for dependency management
From Original CakePHP 2.x
- Update to CakePHP 2.10.24 first: If you're using an earlier version, update to
cakephp/cakephp:2.10.24on PHP 7.4 first - Ensure PHP 7.4 Compatibility: Your application must be fully working on PHP 7.4 before migrating to PHP 8.x
- Update Composer: Replace
cakephp/cakephpwithpieceofcake2/cakephpin yourcomposer.json, and add thecomposer/installerssettings so that plugins are installed intoapp/Plugin/(plugins/in the modern layout). These settings are required; see Configuring composer.json - Upgrade PHP: Update your PHP version to 8.0 or newer
- Test Thoroughly: Run your application's test suite to ensure compatibility
v2.13.0
Changes in v2.13.0 that may affect applications already running on the fork.
CakePHP 2.13 adds features; follow these steps to update from 2.12:
- Update the constraint in
composer.jsonto"pieceofcake2/cakephp": "^2.13"and runcomposer update pieceofcake2/cakephp. - If you use pieceofcake2/phpstan-cakephp2, update it to
^1.1, which knows the classes of theCakenamespace:composer require --dev pieceofcake2/phpstan-cakephp2:^1.1. With version 1.0, PHPStan reports errors in code that worked before. - Read the items below, especially the class names in logs and output and the comparisons of class names as strings, and run your tests.
Variadic arguments (PR #89)
The methods that read a variable number of arguments with
func_get_args()now declare them as a variadic parameter. The calls do not change:$this->Auth->allow('index', 'view')and$this->Post->query($sql, $params)work as before.A class that overrides one of these methods must declare the same variadic parameter. PHP refuses an override with fewer parameters and stops when the class is loaded:
Declaration of AppModel::query($sql) must be compatible with Model::query($sql, ...$params).Method New signature Shell::dispatchShell()dispatchShell(...$args)AuthComponent::allow(),deny()allow(...$args),deny(...$args)SecurityComponent::requirePost(),requireGet(),requirePut(),requireDelete(),requireSecure(),requireAuth()requirePost(...$args), ...Controller::setAction()setAction($action, ...$args)Controller::validate(),validateErrors()validate(...$args),validateErrors(...$objects)ContainableBehavior::contain()contain(Model $Model, ...$args)DboSource::query()query(...$args)Model::isUnique()isUnique($fields, $or = true, ...$args)Model::query()query($sql, ...$params)CakeRequest::data(),param()data($name, ...$args),param($name, ...$args)CakeRequest::input()input($callback = null, ...$args)CakeRequest::allowMethod(),onlyAllow()allowMethod($methods, ...$args),onlyAllow($methods, ...$args)Router::parseExtensions()parseExtensions(...$extensions)CakeTestCase::loadFixtures()loadFixtures(...$args)Hash::merge()merge(array $data, $merge, ...$args)Sanitize::stripTags()stripTags($str, ...$tags)Set::merge()merge($data, $merge = null, ...$args)HtmlHelper::css()css($path, $options = [], ...$args)HtmlHelper::useTag()useTag($tag, ...$args)php// Before public function query($sql) { return parent::query(...func_get_args()); } // After public function query($sql, ...$params) { return parent::query($sql, ...$params); }1
2
3
4
5
6
7
8
9
10
11The
rectorcommand of pieceofcake2/upgrade adds the parameters for you. The changed code also runs on the versions before, so run it before you update:bashbin/cake upgrade rector --rules variadic-arguments src tests1See Rector rules.
CakeRequest::input(null)now returns the raw input, asinput()does. It callednullbefore.
Plugins in the CakePHP 5 layout (PR #88)
- A plugin can put namespaced classes in
src/(loaded by Composer with the plugin'spsr-4entry), its templates intemplates/, its translations inresources/locales/, its vendor files invendor/and its bootstrap, routes, configuration files and schemas inconfig/, as the application does. - CakePHP searches these directories first, and
Controller/,View/,Locale/,Vendor/,Config/and the other CakePHP 2 directories after them. Nothing changes for a plugin that does not have them. See How To Create Plugins.
WARNING
In a plugin that already has one of these directories for another purpose, its files are now found first. For example, App::import('Vendor', ...) looks first in the Composer vendor/ directory of a plugin checked out for development.
Templates in the CakePHP 5 style (PR #94)
- In
templates/, templates, layouts and elements may use the.phpextension and the CakePHP 5 directorieslayout/,element/,email/andError/, e.g.templates/email/html/welcome.php. - Nothing changes for
.ctptemplates and for theView/directories of the legacy layout. See Templates in the CakePHP 5 style.
WARNING
A .php file next to a .ctp file of the same name in templates/ is now used instead of it. Check that templates/ holds no other .php files.
PHP 8.1 to 8.5 deprecations (PR #96)
The core no longer triggers the deprecations of PHP 8.1 to 8.5 in its own code. Most applications only need to update. These changes may affect yours:
Xml::build()no longer has theloadEntitiesoption. It only decided whetherlibxml_disable_entity_loader()was called, which PHP 8 deprecates and which no longer changes anything with libxml 2.9 and later: external entities are not loaded either way. Passing the option is ignored, so you can remove it from your calls. See Xml.CakeRequest::input()with a decoding callback returnsnullwithout calling the callback when the request has no body.RequestHandlerComponentsets the same empty data as before. If your own callback handled an empty body, handlenullinstead. See Request and Response objects.- The core requires
symfony/polyfill-php84for thePdo\Mysqlconstants on PHP 8.0 to 8.3. Composer installs it.
Classes in a namespace: App::className() and App.namespace (PR #91)
- Settings with a suffix take fully qualified class names with the suffix:
MemoryEngine::classfor a cache engine,SesTransport::classfor a transport, and the same for Auth objects, password hashers, view classes, components, helpers, behaviors, shell tasks and shell helpers. The names without the suffix ('Acme\Cache\Engine\Memory') keep working. App.namespaceis new and not set by default. When it is set, controllers, shells, models and the classes above are first looked up in that namespace by their short name, e.g.App\Controller\PostsControllerfor/posts. See Namespaced Classes.- Fully qualified class names as the keys of
$components,$helpers,$actsAs, shell$tasks,$usesand the model associations, or given toload(),loadModel()andClassRegistry::init()without an alias, get the class name without the namespace and the suffix as their alias:GreetingComponent::classgives$this->Greeting, andPost::classgives$this->Post. Before, the alias was the name with the backslashes; code that used such an alias must use the short alias. A parent class and a subclass that name the same object, one by its class name and one by its short name, now give one entry with the merged settings.'with' => PostsTag::classof a hasAndBelongsToMany association finds the join modelPostsTag, andClassRegistry::getObject(),isKeySet()andremoveObject()also find objects by their fully qualified class names. See Namespaced Classes. ControllerandViewhave the new public property$controllerClass, which is also passed from the controller to the view. Rename a property of that name in a subclass.
WARNING
Controller::$name, Model::$name and Shell::$name of a class in a namespace no longer contain the namespace, e.g. Post instead of Acme\Model\Post. Set $name if you relied on the old value.
WARNING
A namespaced controller now merges $components and $helpers with its parent class AppController in any namespace, also without App.namespace.
The framework classes in the Cake namespace (PR #92)
- The classes of the framework moved into the
Cakenamespace, e.g.Cake\Controller\Controller. The global names of CakePHP 2 are aliases of the same classes: code that extends, type-hints, catches or loadsController,CakeRequestorNotFoundExceptionkeeps working. - The files of the framework moved from
src/Cake/tosrc/, loaded by Composer with PSR-4 (Cake\insrc/), andCAKEpoints tosrc/.src/Cake/bootstrap.php, whichwebroot/index.phprequires, andsrc/Cake/TestSuite/Fixture/CakeFixtureInjector.php, the listener ofphpunit.xml.dist, stay at their paths and keep working. If you include other files ofvendor/pieceofcake2/cakephp/src/Cake/by path, change them tosrc/. - Objects serialized before the update still unserialize. Objects serialized after it name the namespaced classes and cannot be read by an older version.
- A global class registered with
App::uses()no longer overrides a core class, because the framework uses the namespaced names. Copy the core file tosrc/Cake/instead. See Overriding classes in CakePHP. - PHPStan needs pieceofcake2/phpstan-cakephp2
^1.1: runcomposer require --dev pieceofcake2/phpstan-cakephp2:^1.1. With version 1.0, PHPStan no longer finds the type of components, models, tasks and behaviors. See Static Analysis.
WARNING
get_class(), ::class and ReflectionClass::getName() return the namespaced names, so a comparison like get_class($x) === 'CakeRequest' is now false. The error log, Debugger output and the debug error pages show the namespaced names too, e.g. [Cake\Error\NotFoundException]: update the log filters and alerts that match the global names.
TIP
bin/cake upgrade rector --rules namespaced-classes src tests of pieceofcake2/upgrade rewrites the comparisons with get_class() and imports the namespaced classes.
App::uses() is deprecated (PR #98)
- Composer loads the classes of the core, of the application (pieceofcake2/app loads
src/with a classmap) and of the plugins installed with Composer. The classes named in the configuration ($components,$helpers,$uses, the associations, the engines and the other settings) are found withoutApp::uses(), as before. - The first call of
App::uses()in a process logs a warning with the file and line of the call. Withdebugabove 0, it also triggersE_USER_DEPRECATED. The call still works. - The core no longer calls
App::uses().AppController,AppModel,AppHelperandAppShellof the application are found in their packages without it. - To remove the calls, let Composer load your classes, then delete the
App::uses()lines: in an application, add"classmap": ["src/"]to theautoloadof itscomposer.jsonand runcomposer dump-autoload; in a plugin, add a classmap or PSR-4 to thecomposer.jsonof the plugin. See Removing App::uses() calls.
v2.12.11
Security fix: request URLs with extra leading slashes (GHSA-wh43-gc93-wh4c, PR #90)
CakeRequestremoves all the leading slashes and backslashes of the request URL, not only the first one. A request to//posts/view/1is routed as before, but$this->request->urlis nowposts/view/1instead of/posts/view/1, andhereis/posts/view/1instead of the protocol-relative//posts/view/1.here()also collapses the leading slashes of its result. URLs with a single leading slash are not affected. See Security Fixes.
HTML5 script, link and style tags (PR #86)
HtmlHelper outputs the tags of HTML5:
<script src="...">,<link rel="stylesheet" href="...">,<style>, and script blocks withouttype="text/javascript"and without CDATA. Browsers treat both forms the same, so the pages do not change.Tests that compare the HTML of a page or of a helper may need the new tags.
To keep the former output, e.g. for pages served as XHTML, set
html5tofalsein the settings of the helper:phppublic $helpers = ['Html' => ['html5' => false]];1See HTML5 Tags.
v2.12.10
Changes in v2.12.10 that may affect applications already running on the fork.
New function raw() (PR #84)
raw(), the opposite ofh(), returns a value as it is, to print HTML without escaping on purpose. Use it where a view prints HTML that the application built or sanitized, instead of a bareecho:php<?= raw($post['Post']['body_html']) ?>1An application or a package that already defines a global
raw()keeps its own function, as for the other global functions. Check that yours does the same, or rename it. See raw().
v2.12.7
Changes in v2.12.7 that may affect applications already running on the fork.
DSN configuration: DATABASE_URL and MAILER_DSN (PR #69)
A datasource in
DATABASE_CONFIGand a profile inEmailConfigcan set the whole connection with a DSN in a new'dsn'key, read from the environment variables Symfony uses:php// config/database.php public $default = [ 'datasource' => 'Database/Mysql', 'encoding' => 'utf8mb4', ]; public function __construct() { $this->default['dsn'] = env('DATABASE_URL'); }1
2
3
4
5
6
7
8
9
10php// config/email.php public $default = [ 'from' => 'app@example.com', ]; public function __construct() { $this->default['dsn'] = env('MAILER_DSN'); }1
2
3
4
5
6
7
8
9DATABASE_URLis a DSN such asmysql://user:secret@localhost:3306/app, andMAILER_DSNuses the Symfony Mailer format, such assmtp://user:secret@smtp.example.com:587ornull://null.The keys parsed from the DSN replace the same keys of the array, and the other keys stay. An empty
'dsn'is ignored, so configurations without it work exactly as before.Plugins can add DSN schemes, or replace built-in ones, with
ConnectionManager::setDsnClassMap()andCakeEmail::setDsnClassMap(). pieceofcake2/aws and pieceofcake2/async-aws register the Amazon SES schemes (ses+api://,ses+smtp://, ...).The application skeleton (pieceofcake2/app) reads
DATABASE_URLandMAILER_DSN, and loadsconfig/.env.testin the tests. Since app v2.5.0 it loads the.envfiles with symfony/dotenv as Symfony does (see Loading .env Files).
See Database DSNs, Mailer DSN and Registering DSN Schemes.
v2.12.6
Changes in v2.12.6 that may affect applications already running on the fork.
Security fix: 'cipher' encryption is deprecated (PR #62)
Security::cipher() and the 'cipher' type of CookieComponent (its default type) are not secure with any Security.cipherSeed (GHSA-65v5-h65v-8pgg). They are now deprecated. Values are encrypted exactly as before, and nothing throws, but:
- The first
'cipher'use in a process (one request or one console run) writes awarningto the log, also with debug off. With debug on, the same message is also triggered asE_USER_DEPRECATED. The'aes'and'rijndael'types and unencrypted cookies log nothing. - A
Security.cipherSeedthat is not a digits-only number from 1 to 4294967295 is reported: the firstSecurity::cipher()call logs awarning(the seed is never logged), and with debug onDebugger::checkSecurityKeys()(called byAuthComponent) triggers anE_USER_NOTICE. The notice asking you to change the default seed now fires for both the former default'76859309657453542496749683645'and the new default'2659758734'. - The fallback
config/core.phpof the core (used only when the application has noconfig/core.php) setsSecurity.cipherSeedto'2659758734'andSecurity.useOpenSsltotrue.
Migration:
- Do not change
Security.cipherSeedjust to silence the warning. A new seed makes existing'cipher'cookies unreadable, and'cipher'stays insecure. Switch to'aes'as described in Migrating to 'aes'. - If your test suite turns deprecations or notices into exceptions, the first
'cipher'use in a test run, orAuthComponentwith debug on and an out-of-range seed, now fails. Pass'aes'as the mode ofcookieEncrypted()/assertCookieEncrypted()once the application uses'aes', or set an in-range seed in the test configuration. - Replace your own calls to
Security::cipher()withSecurity::encrypt()/Security::decrypt()and a key of at least 32 bytes.
See Security Fixes and cipherSeed and the 'cipher' Cookie Type.
CookieComponent reads Cookie.type and Cookie.key (PR #62)
- Without a
'key'setting,CookieComponentuses ConfigureCookie.keywhen it is a non-empty string, otherwiseSecurity.saltas before. - Without a
'type'setting, it uses ConfigureCookie.typewhen it is set, otherwise'cipher'as before. An invalid value triggers the usualE_USER_WARNINGand falls back to'cipher'. - An explicit
'key'/'type'setting,$this->Cookie->key = ...or$this->Cookie->type(...)always wins. cookieEncrypted()/assertCookieEncrypted()ofIntegrationTestTraituse the same default key.
Nothing changes for applications that set neither value. To switch the whole application to 'aes', set these in config/core.php:
Configure::write('Security.useOpenSsl', true);
Configure::write('Cookie.type', 'aes');
Configure::write('Cookie.key', env('COOKIE_KEY') ?: null);2
3
'aes' needs a key of at least 32 bytes. Setting Cookie.key while still on 'cipher' changes the key, so existing 'cipher' cookies become unreadable. See Cookie.
Debug error pages in the CakePHP 5 style (PR #60)
- In debug mode, the core error pages use a new
dev_errorlayout ported from CakePHP 5, with code excerpts for every stack frame and(edit)links that open the file in your editor (PhpStorm by default; setDebugger.editorand, in a container,Debugger.editorBasePath). See Debug Error Pages. - Production mode (
debug= 0), AJAX requests and the console are unchanged, and no existing PHP API changed. Error templates provided by your application, plugin or theme keep rendering in your layout, and theexception_stack_traceelement still works.
Migration:
To keep the old look for the core error pages, provide your own
Layouts/dev_error.ctp(intemplates/Layouts/,app/View/Layouts/or a theme), which receives the blockstitle,subheading,fileandtemplateNameand the$errorview variable, or copy the error templates you want to keep from the previous release into yourErrors/directory:php<?php $this->extend('/Layouts/default'); ?> <h2><?php echo $this->fetch('title'); ?></h2> <p class="error"><?php echo $this->fetch('subheading'); ?></p> <?php echo $this->fetch('file'); ?> <?php echo $this->element('exception_stack_trace'); ?>1
2
3
4
5
Exceptions instead of E_USER_ERROR (PR #58)
Triggering E_USER_ERROR is deprecated as of PHP 8.4, so these errors are now exceptions with the same message:
Configure::bootstrap()throwsConfigureExceptionwhen the applicationcore.phporbootstrap.phpcannot be included. The console prints the message asError: ...and exits with code 1.TranslateBehaviorthrowsCakeExceptionwhen the model's datasource is not connected, or whenbindTranslation()is given an association name that is already bound to the model. See Translate.bin/cakeprints a missing Composer autoloader to STDERR and exits with code 1.
Migration: execution already stopped at these points, so most applications need no change. If a custom error handler caught these E_USER_ERROR errors and continued, catch the exceptions instead. See Exceptions.
ConsoleLog color detection (PR #58)
ConsoleLogdecides between colored and plain output by checking whether itsstreamis a terminal, not STDIN. Logging to a file or pipe from a terminal no longer writes ANSI escape codes, and logging tophp://stderron a terminal is colored even when STDIN is redirected.ConsoleOutputusesstream_isatty(), so streams that cannot be checked (php://memory,php://temp) get plain output without a warning.
Migration: set 'outputAs' => ConsoleOutput::COLOR (or PLAIN / RAW) in the log configuration to choose the format yourself. See Logging.
New validation rule Validation::utf8() (PR #64)
A backport of the CakePHP 5 rule. It checks that a value is a valid UTF-8 string without characters above the Basic Multilingual Plane, which MySQL's
utf8(utf8mb3) columns cannot store. With['extended' => true], such characters are allowed (forutf8mb4). Non-string values and invalid byte sequences are rejected in both modes:phppublic $validate = [ 'title' => ['rule' => 'utf8'], 'body' => ['rule' => ['utf8', ['extended' => true]]], ];1
2
3
4No existing rule changes. A model method named
utf8()keeps precedence over the core rule. See Data Validation.
Folder recognizes more stream wrapper schemes (PR #63)
Folder::isAbsolute()andFolder::isRegisteredStreamWrapper()recognize registered stream wrapper schemes that contain digits,+,-or., such ass3://.FolderandFileno longer treat such paths as relative. See Folder & File.
Console without an application core.php (PR #59)
- When the application has no
config/core.php, the console no longer stops with the fatal error "Can't find application core file". It loads the fallbackcore.phpof the core, so commands that do not need the application settings can run. Applications with aconfig/core.phpare not affected. See Shells, Tasks & Console Tools.
PHPStan level 5 docblocks (PR #56)
- The core (including
TestSuite) passes PHPStan level 5 withpieceofcake2/phpstan-cakephp2^0.2.4, without a baseline. Only docblocks were corrected; no native parameter, return or property types were added, and no public or protected signature changed, so subclasses in applications and plugins keep working. - Some properties that were created dynamically (deprecated as of PHP 8.2) are now declared, for example
Scaffold::$redirectandControllerTestDispatcher::$response.
Migration: none needed. If you run PHPStan on your application, the corrected docblocks may report new errors in your code, or make baseline entries obsolete. See Static Analysis.
v2.12.5
Changes in v2.12.5 that may affect applications already running on the fork.
Security fixes
This release fixes three vulnerabilities. Most applications only need to update. See Security Fixes for details.
- Email headers (GHSA-4f9q-3c4x-52cv):
SmtpTransportandMailTransportremove line breaks from header names and values. If you relied on putting raw line breaks in a header value to add headers, useCakeEmail::setHeaders()/addHeaders()with one entry per header instead. - View, layout and element names (GHSA-w2mh-x92p-5p95): a name containing
..must resolve to a file inside the view paths (App::path('View'), plugin and theme view paths). Files outside them are treated as missing:element()behaves as for a missing element,elementExists()returnsfalse, and views and layouts throwMissingViewException/MissingLayoutException. Move templates you load from elsewhere into a view path. - Login redirect (GHSA-x4p9-p4cx-gxwx):
AuthComponent::redirectUrl()turns a protocol-relative URL stored in the session into a local path. No change is needed.
Invalid ORDER BY directions are deprecated (PR #54)
- The sort direction given to
order(find options,Model::$order, association'order'settings,DboSource::order()) should beASCorDESC, optionally followed byNULLS FIRSTorNULLS LAST. It is matched case-insensitively and surrounding / repeated whitespace is allowed, so'asc','Desc',' DESC ','ASC NULLS FIRST'and'desc nulls last'are all valid.NULLS FIRST/NULLS LASTare passed to the database as written and are not supported by MySQL, MariaDB and SQL Server; see Sort Order. - Any other direction now triggers an
E_USER_DEPRECATEDerror (Invalid ORDER BY direction "..."). This covers array values (['Post.title' => $direction]), the$directionargument ofDboSource::order(), and text after anASC/DESCin a string key ('Post.title DESC LIMIT 1';ASC/DESCinside parentheses or quoted literals is ignored). - Nothing else changes in this release: the generated SQL is exactly the same as before. Invalid directions will throw an
InvalidArgumentExceptionin a future version. PaginatorComponentalready restricts thedirectionrequest parameter toasc/desc, so pagination does not trigger the deprecation.- Only the direction is checked. The field / expression part of an order key is trusted SQL (quoted where possible, otherwise used as is) and must never contain user input.
DboSource::expression()objects are used verbatim and are not checked.
See also Sort Order.
Migration:
- An expression in the direction value: move the expression into the key. Replace
['Page.name' => "='test' DESC"]with["Page.name = 'test'" => 'DESC'], or useDboSource::expression()for raw SQL. - SQL after the direction in a string key (for example
'Post.title DESC LIMIT 1'): put only the field and the direction inorder, and pass the rest with the matching find option (limit, ...) or as aDboSource::expression()object. NULLS FIRST/NULLS LASTwithout a direction: addASCorDESC('ASC NULLS FIRST').- A direction that is not a string (
true,1, ...): pass'ASC'or'DESC'. - A direction taken from request data: restrict it to
asc/descfirst, asPaginatorComponentdoes, e.g.$direction = strtolower((string)$this->request->query('direction')) === 'desc' ? 'DESC' : 'ASC';. - To find the call sites, log or break on
E_USER_DEPRECATEDerrors whose message starts withInvalid ORDER BY direction.
v2.12.3
Changes in v2.12.3 that may affect applications already running on the fork.
Xml::build() readFile option also blocks https URLs (PR #33)
- Security fix (SSRF):
Xml::build($input, ['readFile' => false])fetchedhttps://URLs because of a missing pair of parentheses (inherited from the original CakePHP 2.x). With'readFile' => false, neitherhttp://norhttps://URLs are fetched now; anXmlExceptionis thrown instead. See Security Fixes.
Migration:
- No action required if you pass
'readFile' => falseto disable remote loading; this now works as documented. - If you relied on
Xml::build()fetching anhttps://URL with'readFile' => false, either remove the option (default istrue) or fetch the document yourself (e.g. withHttpSocket) and pass the XML string.
CookieComponent honors the 'type' setting (PR #34)
CookieComponentnow applies the'type'setting given in$components(e.g.'Cookie' => ['type' => 'aes']). Previously the setting was silently ignored and cookies were encrypted with the insecure default'cipher'type.
Migration:
- If your controllers set
'type'in$components, cookies will now be written with that type, and cookies already written with'cipher'can no longer be decrypted. Plan the switch like any other encryption type change. - When using
'aes', setConfigure::write('Security.useOpenSsl', true);unless the mcrypt extension is installed (see Enable OpenSSL). - An invalid
'type'setting now triggers anE_USER_WARNINGand falls back to'cipher', asCookieComponent::type()does.
Test case listing supports the tests/TestCase layout (PR #35)
CakeTestLoader::generateTestList()now finds test cases intests/TestCasefor the app and plugins, falling back to the legacyTest/Caselayout. Previously the list was empty for the modern layout, sobin/cake test app/bin/cake test <Plugin>without a test case name and the web test runner showed no test cases.
Migration:
- No action required.
SQL Server driver sets PDO::ATTR_ERRMODE by default (PR #36)
Sqlserver::connect()now mergesPDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTIONinto the'flags'setting as the other drivers do. Because of an operator precedence bug, the default was never applied.
Migration:
- No action required. PDO already uses
PDO::ERRMODE_EXCEPTIONby default on PHP 8, and aPDO::ATTR_ERRMODEvalue given in'flags'still takes precedence.
ServerShell default port changed to 8765 (PR #37)
bin/cake servernow listens on port8765by default (as in CakePHP 3 and later) instead of80, which requires root privileges on most systems.ServerShell::DEFAULT_PORTis now8765.
Migration:
- To keep using port 80, pass it explicitly:
bin/cake server -p 80.
Breaking Changes
Post-Installation Steps
For Legacy Directory Structure (app/ directory)
After installation, install the Bake plugin and copy dispatcher files:
# Install Bake plugin
composer require --dev pieceofcake2/bake
# Copy web dispatcher files
cp app/Plugin/Bake/Console/Templates/skel/webroot/index.php app/webroot/index.php
cp app/Plugin/Bake/Console/Templates/skel/webroot/test.php app/webroot/test.php
# Copy console dispatcher
cp app/Plugin/Bake/Console/Templates/skel/Console/cake app/Console/cake
chmod +x app/Console/cake2
3
4
5
6
7
8
9
10
For Modern Directory Structure (src/ directory)
The following files need to be updated:
config/define.phpbin/cakewebroot/index.phpwebroot/test.php
See pieceofcake2/app for the modern application skeleton compatible with both CakePHP 2.x and 5.x. See also Installation and Directory Structure.
Directory Structure Modernization (PR #21)
- Directory layout has been restructured to modern standards
lib/Cake/→src/Cake/(framework source code)lib/Cake/Test/Case/→tests/TestCase/(framework tests)lib/Cake/Test/Fixture/→tests/Fixture/(test fixtures)lib/Cake/Console/cake→bin/cake(console executable)lib/Cake/Config/→config/(framework configuration files at root level)lib/Cake/View/andlib/Cake/Templates/→templates/(framework default templates at root level)- All configuration files and paths updated accordingly
- Added
CORE_ROOTconstant to reference framework root directory
Modern Test Directory Support:
- Applications and plugins can now use modern
tests/TestCase/andtests/Fixture/directory structure - Test suite automatically detects both
Test/Case/(traditional) andtests/TestCase/(modern) directories - Fixture loader supports both
Test/Fixture/andtests/Fixture/locations - Enables gradual migration to modern directory structure for application tests
- No breaking changes - both directory structures work simultaneously
Migration:
- No action required for applications using this framework as a dependency
bin/cake(orvendor/bin/cakewhen installed via Composer) now intelligently detects your project structure- Automatically works with both modern
src/and traditionalapp/directory layouts - Optionally migrate your application tests from
app/Test/Case/toapp/tests/TestCase/at your own pace
Enhanced Console Executable:
bin/cakenow automatically detects project root from Composer's autoloader- No manual configuration needed - works out of the box with any project structure
- Supports modern (
src/) and traditional (app/) application directories
Composer Autoloading Migration (PR #22)
The framework has migrated from include-path to Composer classmap autoloading. This change simplifies class loading and improves compatibility with modern PHP tooling.
What changed:
- Removed
App::uses()declarations from CakePHP framework classes (798 occurrences removed) - CakePHP framework classes are now automatically loaded via Composer's classmap autoloader
- Application classes can be automatically loaded by configuring Composer classmap autoloading (see Migration section below)
- CakePHP framework classes that extend application classes use
App::uses():App::uses('AppController', 'Controller')- for CakeErrorControllerApp::uses('AppModel', 'Model')- for I18nModel, AcoAction, PermissionApp::uses('AppShell', 'Console/Command')- for all core shell commandsApp::uses('AppHelper', 'View/Helper')- for all core helpers
Migration:
For new projects or modern directory structure (src/):
Recommended: Update your
composer.jsonto include classmap autoloading (similar to pieceofcake2/app):json{ "autoload": { "classmap": ["src/"] } }1
2
3
4
5
For legacy projects (app/ directory):
- No action required - existing applications continue to work without changes
- The framework's
App::uses()calls for application classes ensure backward compatibility
Plugin classes:
Plugins that don't support Composer autoloading require
App::uses()to load their classes (same as before):phpApp::uses('MyPluginHelper', 'MyPlugin.View/Helper');1If a plugin's documentation indicates autoloading support,
App::uses()may not be needed
Custom Autoloader Override (optional):
If you need to override autoloading behavior for specific classes, register your custom autoloader with the prepend flag to give it priority over Composer's autoloader:
// Example: Custom classmap-based autoloader
$customClassMap = [
'MyCustomClass' => '/path/to/MyCustomClass.php',
'AnotherClass' => '/path/to/AnotherClass.php',
];
spl_autoload_register(function ($class) use ($customClassMap) {
if (isset($customClassMap[$class])) {
require $customClassMap[$class];
return true;
}
return false;
}, true, true); // throw=true, prepend=true (priority over Composer)2
3
4
5
6
7
8
9
10
11
12
13
14
15
Parameters explained:
- First
true: Throw exception if autoloader registration fails - Second
true: Prepend to autoloader queue (gives priority over Composer's autoloader)
Duplicate Class Name Warnings:
When Composer encounters duplicate class names in the classmap, it will issue a warning during composer install or composer dump-autoload:
Warning: Ambiguous class resolution, "AppController" was found in both "/path/to/vendor/pieceofcake2/app/src/Controller/AppController.php" and "/path/to/your-app/src/Controller/AppController.php", the first will be used.To prevent these warnings and ensure the correct class is loaded, use exclude-from-classmap in your composer.json:
{
"autoload": {
"classmap": ["src/"],
"exclude-from-classmap": [
"vendor/pieceofcake2/app/src/Controller/AppController.php",
"vendor/pieceofcake2/app/src/Model/AppModel.php"
]
},
"autoload-dev": {
...
}
}2
3
4
5
6
7
8
9
10
11
12
This tells Composer to ignore specific files when building the classmap, preventing conflicts with your application's base classes.
Bake Plugin Extraction (PR #17)
- Bake functionality has been extracted to a separate plugin (pieceofcake2/bake)
BakeShelland all Bake tasks removed from core (BakeTask,ModelTask,ControllerTask,ViewTask,FixtureTask,TestTask,TemplateTask,ProjectTask,PluginTask,DbConfigTask,CommandTask)- Application skeleton templates moved from
lib/Cake/Console/Templates/skel/to Bake plugin - Dispatcher files (
index.php,test.php,cake) now located in Bake plugin - Console bake commands no longer available without installing the Bake plugin
Migration:
Install the Bake plugin separately:
composer require --dev pieceofcake2/bakeLoad the plugin in your
app/Config/bootstrap.php:phpCakePlugin::load('Bake');1Copy dispatcher files from Bake plugin if needed (for new projects)
Why this change:
- Allows independent development and versioning of Bake functionality
- Reduces core framework size
- Most production applications don't need Bake in production
Legacy Upgrade Plugin Extraction (PR #19)
- Legacy Upgrade functionality has been extracted to a separate plugin (pieceofcake2/upgrade)
UpgradeShellremoved from core- Legacy upgrade commands (1.3 → 2.0) no longer available without installing the Upgrade plugin
Migration:
INFO
The Upgrade plugin is only relevant for upgrading legacy CakePHP 1.3 applications to 2.0, which is extremely rare in 2025. Most users will never need this plugin.
If you need to upgrade a CakePHP 1.3 application to 2.0, install the Upgrade plugin separately:
composer require --dev pieceofcake2/upgradeThen load the plugin in your app/Config/bootstrap.php:
CakePlugin::load('Upgrade');Why this change:
- Reduces core framework size
- The Upgrade plugin is only useful for upgrading from CakePHP 1.3 to 2.0
- Almost no one needs this functionality in 2025
Composer-Only Installation Required (PR #14)
- Non-Composer installation is no longer supported
- Dispatcher files (
app/webroot/index.php,app/webroot/test.php,app/Console/cake) require Composer autoload - Removed manual
CAKE_CORE_INCLUDE_PATHdetection from dispatcher files - Removed
include_pathmanipulation logic - Removed legacy
app/Vendor/cakephp/cakephppath detection - Removed
app/Console/cake.batandapp/Console/cake.php(Windows batch file and PHP wrapper no longer needed)
Migration:
Ensure you're using Composer for dependency management
Install the Bake plugin:
composer require --dev pieceofcake2/bakeCopy updated dispatcher files from
app/Plugin/Bake/Console/Templates/skel/to your application:bashcp app/Plugin/Bake/Console/Templates/skel/webroot/index.php app/webroot/index.php cp app/Plugin/Bake/Console/Templates/skel/webroot/test.php app/webroot/test.php cp app/Plugin/Bake/Console/Templates/skel/Console/cake app/Console/cake1
2
3Remove old dispatcher files if present:
bashrm -f app/Console/cake.bat app/Console/cake.php1Run
composer installto ensure all dependencies are properly loaded
Cache Engines Removed (PR #4)
- Xcache support has been removed (not compatible with PHP 7.0+)
- Wincache support has been removed (not actively maintained for PHP 8.x)
Migration:
- If using these cache engines, migrate to Redis, Memcached, or APCu
Database Driver Methods Added (PR #3)
- New methods added to database drivers (may cause issues if you have custom driver implementations)
MySQL Driver (Mysql.php):
getVersion(): string- Returns MySQL/MariaDB/Aurora MySQL versiongetServerType(): string- Returns 'MySQL', 'MariaDB', or 'Aurora MySQL'utf8mb4Supported(): bool- Checks utf8mb4 character set supportintegerDisplayWidthDeprecated(): bool- Checks if integer display width is deprecated (MySQL 8.0.17+)
PostgreSQL Driver (Postgres.php):
getVersion(): string- Returns PostgreSQL version
Migration:
- If you have custom database drivers extending these classes, implement these methods
Database Charset Configuration Changes (PR #11)
- Character set configuration moved from
SET NAMESto DSN connection options - MySQL: Charset now in DSN (e.g.,
mysql:...;charset=utf8) - PostgreSQL: Client encoding in DSN options (e.g.,
pgsql:...;options='--client_encoding=UTF8') - PostgreSQL:
sslmodeparameter is now optional in DSN
Migration:
- No action required - changes are backward compatible
setEncoding()methods still work for runtime changes- More efficient connection setup with charset in DSN
SQL Server Driver Updates (PR #9)
Configuration Format
Schema: the
schemasetting selects the SQL Server schema of a connection and defaults todbo. To use tables in several schemas of one database, define one connection per schema:phppublic $default = [ 'datasource' => 'Database/Sqlserver', 'host' => 'localhost', 'port' => 1433, 'login' => 'user', 'password' => 'password', 'database' => 'my_app', 'schema' => 'dbo', ];1
2
3
4
5
6
7
8
9Connection options: SSL/TLS options now in
optionsarrayphp'options' => [ 'TrustServerCertificate' => 'yes', 'Encrypt' => 'no', ]1
2
3
4Port configuration: Specify port separately (automatically appended to server)
Method Signature Changes
describe($model): array- Now has explicit return typeinsertMulti()- Now returnsboolinstead ofvoid
Migration:
- Set
schemaif your tables are not in thedboschema - Move SSL/TLS options to
optionsarray if using inline DSN - If extending Sqlserver class, update method signatures to match
Mail Function Updates (PR #10)
MailTransport::_mail()method signature changed with strict types- Old:
protected function _mail($to, $subject, $message, $headers, $params = null) - New:
protected function _mail(string $to, string $subject, string $message, array|string $headers = [], string $params = ''): void
Migration:
- No action required unless you've extended
MailTransportclass - If extending, update method signature to match strict types
CSRF Token Security Enhancement (PR #5)
- New CSRF tokens use HMAC-SHA1 signatures (prevents CVE-2020-15400)
- Token format changed to base64-encoded (16-byte value + 20-byte HMAC)
Migration:
- No action required - automatic and backward compatible
- Existing tokens continue to work
- New tokens generated with enhanced security
See Security for details.
strftime() Replacement
strftime()deprecated in PHP 8.1, removed in PHP 8.2- Now uses
IntlDateFormattervia Symfony's ICU Polyfill - Fallback to
PHP81_BC\strftimefor compatibility
Migration:
- Most date formatting works identically
- Edge cases may produce slightly different output
- Test date formatting in your application
Development Tools Updates
PHP CodeSniffer (PR #8)
- Updated from 1.0.0 to 5.3
- Applied automatic formatting fixes
Migration:
- Development-time change only
- Update
phpcs.xmlif you have custom coding standards
See Coding Standards.
PHPUnit Compatibility
- Framework tests migrated to PHPUnit 9.6
- All deprecated PHPUnit features fixed
Migration:
- Update your tests if using deprecated PHPUnit features
PHP 8 Syntax Modernization (PR #7)
- Codebase modernized to PHP 8 syntax
Changes:
array()→[]get_class()→::classlist()→[]for array destructuringdirname(__FILE__)→__DIR__- Added null coalescing operators
- Native
str_contains(),str_starts_with(),str_ends_with()
Migration:
- No action required - syntax changes only, no functionality changes
CookieComponent 'cipher' Type - Insecure and Should Be Replaced
- The default
CookieComponentencryption type'cipher'is horribly insecure
WARNING
As stated in the CakePHP source code comments:
"Cipher is horribly insecure and only the default because of backwards compatibility. In new applications you should always change this to 'aes' or 'rijndael'."
Why 'cipher' is insecure:
- Uses
Security::cipher()with XOR encryption (cryptographically weak) - Uses
Security.cipherSeedwith undefined float-to-int casting behavior - The seed value:
mt_srand((int)(float)Configure::read('Security.cipherSeed'))has no guaranteed consistency - Not suitable for protecting sensitive data
Migration:
// OLD (insecure - DO NOT USE)
public $components = [
'Cookie' => [
'type' => 'cipher' // Default, horribly insecure
]
];
// NEW (recommended)
public $components = [
'Cookie' => [
'type' => 'aes',
],
];2
3
4
5
6
7
8
9
10
11
12
13
You can also call $this->Cookie->type('aes'); in beforeFilter().
Piece of Cake 2 fork
The 'type' setting in $components is honored since v2.12.3. In v2.12.2 and earlier it was ignored and cookies were encrypted with 'cipher'.
Since v2.12.6, 'cipher' is deprecated and logs a warning, and Cookie.type and Cookie.key in config/core.php set the default type and key; see v2.12.6.
On PHP 8, 'aes' also needs Security.useOpenSsl; see Enable OpenSSL below.
DANGER
Changing encryption type will invalidate existing cookies. Plan your migration strategy accordingly (e.g., support both types during transition period).
Additional notes for PHP 8
These points are not listed in UPGRADE.md but commonly affect applications moving to PHP 8.
Enable OpenSSL for Security::encrypt() and AES cookies
The mcrypt extension is not bundled with PHP 7.2 and later, and is usually not installed (it is available from PECL). Security::encrypt() and Security::decrypt() use OpenSSL only when Security.useOpenSsl is enabled; otherwise they call mcrypt_* functions and fail. The same applies to CookieComponent with the 'aes' type, which uses these methods. The framework does not enable the setting by default. Applications created from the pieceofcake2/app skeleton (v2.3.1 and later) already have it in config/core.php. In existing applications, add it to app/Config/core.php (legacy) or config/core.php (modern):
Configure::write('Security.useOpenSsl', true);WARNING
The 'rijndael' cookie type and Security::rijndael() always require mcrypt. On PHP 8 without mcrypt, use 'aes' with Security.useOpenSsl enabled.
String class removed
The deprecated String utility class has been removed (string is a reserved word since PHP 7). Use CakeText instead:
// Before
App::uses('String', 'Utility');
$uuid = String::uuid();
// After
$uuid = CakeText::uuid();2
3
4
5
6
