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.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. - 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'.
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
