Upgrading CakePHP 3.x to CakePHP 4.x
In the African savanna, an elephant herd’s survival depends on the matriarch’s memory—decades of knowledge about water sources, migration routes, and predator territories passed down through generations. When drought strikes or landscapes change, that accumulated wisdom guides the herd to adapt without losing their way. The herd doesn’t abandon its territory; it learns new paths while honoring what has worked before.
Similarly, your CakePHP application carries accumulated investment—custom business logic, domain models, years of refinements. Upgrading from CakePHP 3 to 4 represents a changed landscape: new conventions, stricter typing, modernized APIs. You’re not starting over. You’re applying the framework’s evolution to your existing knowledge base, preserving what works while adapting to new paths.
This guide will walk you through that transition—the automated tools that handle routine transformations, the manual adjustments that require your attention, and the verification steps that ensure you arrive at your destination with your application’s integrity intact.
Understanding the Upgrade Landscape
Before we dive into the mechanics, let’s understand what we’re navigating. CakePHP 4 represents a deliberate evolution toward modern PHP practices: strict typing, cleaner separation of concerns, and streamlined conventions. This isn’t merely a version bump—it’s a philosophical shift toward maintainability and forward compatibility.
What Changed in CakePHP 4
CakePHP 4 introduces several significant departures from version 3:
- Strict typing enforcement: The framework now expects
declare(strict_types=1)in all classes and encourages explicit return type hints - Namespace reorganization: Many classes moved from
Cake\NetworktoCake\Httpand other streamlined paths - Application skeleton modernization:
src/Application.phpnow extendsBaseApplicationwith a middleware-focused architecture - ORM enhancements: More strictly typed entities and tables with improved method signatures
- Removed deprecated functionality: Several CakePHP 3.x features reached end-of-life
One may wonder: why does the CakePHP team make such changes? The answer lies in PHP’s own evolution—as PHP adopted modern features like scalar type declarations and return types (PHP 7.0+), CakePHP followed, ensuring applications built on the framework can leverage the full power of contemporary PHP.
Why Bother with the Upgrade?
You might be thinking: “My CakePHP 3 application works fine. Why upgrade?” Let’s acknowledge that perspective—if your application is stable and meets your needs, upgrading requires effort. However, consider these factors:
- Security: CakePHP 4 receives active security updates; version 3 reached end-of-life
- PHP compatibility: Newer PHP versions (8.0+) bring performance improvements and language features your application cannot use without upgrading
- Tooling ecosystem: Modern development tools increasingly expect contemporary framework patterns
- Long-term maintainability: New developers joining your team will find CakePHP 4 skills more readily available
Of course, the decision depends on your specific circumstances. If you maintain a legacy application that won’t see significant future development, staying on CakePHP 3 might be reasonable—but be aware you’re accepting the associated risks.
Upgrade Approaches: A Survey
We have several paths forward when upgrading a CakePHP application:
- Manual rewrite: Rebuilding the application from scratch on CakePHP 4
- Automated tooling: Using the official
cakephp/upgradepackage with Rector - Hybrid approach: Automated tools for mechanical changes, manual refinement for logic
Manual rewrite gives you complete control but requires extensive effort and risks losing functionality. Automated tools accelerate the process but cannot handle every nuance. The hybrid approach—which we’ll focus on—offers the best balance: automation handles predictable transformations while you retain control over architectural decisions.
Let’s be clear: the automated upgrade path isn’t trivial—it requires careful review and testing. But it’s far more efficient than manual conversion.
Prerequisites: The Pre-Flight Checklist
Before you begin, ensure you have the following in place:
- Version Control: Your application must be under Git (or another VCS). Create a dedicated branch for the upgrade work:
git checkout -b upgrade/cakephp4
-
Comprehensive Test Suite: A passing test suite on your current CakePHP 3 application is non-negotiable. It’s your safety net—we’ll run tests after each major step to catch regressions immediately. If you don’t have tests, consider adding at least smoke tests for critical paths before upgrading.
-
PHP Version: CakePHP 4 requires PHP 7.2 or higher—but we strongly recommend PHP 7.4 or, better yet, PHP 8.0+. Upgrading PHP simultaneously adds complexity; we recommend upgrading PHP first, verifying your CakePHP 3 app works on the newer PHP version, then upgrading to CakePHP 4.
-
Complete Backup: While Git protects your code, ensure you have database backups and can deploy the current version to a staging environment if needed.
Step 1: Automated Upgrade with Rector
Now we’re ready to begin the upgrade process. The CakePHP team provides an excellent upgrade tool built on Rector, which automates the tedious mechanical changes: class renaming, namespace updates, method signature adjustments.
Installing the Upgrade Tool
First, require the upgrade package as a development dependency:
composer require --dev cakephp/upgrade
You should see output similar to:
Using version ^2.0 for cakephp/upgrade
...
cakephp/upgrade suggests installing rector/rector (for automated refactoring)
...
Package operations: 1 install, 0 updates, 0 removals
Note: The upgrade package depends on Rector; Composer will install it automatically as a dependency.
Running Rector on Your Application
The upgrade tool provides Rector rulesets tailored for CakePHP migrations. Run Rector against your application’s src, config, and tests directories:
vendor/bin/rector process src
vendor/bin/rector process config
vendor/bin/rector process tests
Rector will process each file, applying transformations. You’ll see output like:
Processing src/Controller/AppController.php ... ok
Processing src/Controller/UsersController.php ... ok
...
Important: Rector modifies files in-place. This is why we work on a dedicated Git branch—we can review changes, revert if needed, and track exactly what transformed.
What Rector Handles Automatically
The CakePHP upgrade Rector rules handle numerous breaking changes, including:
- Class renames (e.g.,
Cake\Network\Http\Client→Cake\Http\Client) - Interface implementations (e.g.,
Cake\Event\EventDispatcherInterface→Cake\Event\EventManagerInterface) - Method signature updates to match new type hints
- Deprecated constant replacements
- Collection method updates
One may wonder: does Rector handle everything? The answer is no—some changes require human judgment, particularly around:
- Application skeleton structure (e.g.,
Application.phprewrite) - Custom middleware configurations
- Plugin compatibility issues
- Edge cases in ORM usage
Reviewing Automated Changes
After Rector completes, review the changes with git diff:
git diff
We recommend scanning the diff with particular attention to:
- Modified namespaces
- Changed method signatures
- Removed or added
usestatements - Updated type hints
You also may notice Rector made changes to files you didn’t expect. That’s normal—the upgrade rules are comprehensive. If something looks incorrect, you can revert individual files and make manual adjustments.
Tip: Commit your Rector changes separately from later manual modifications. This creates a clean history and makes it easier to understand what the automated tool did versus your manual refinements:
git add .
git commit -m "chore: apply CakePHP 4 automated upgrade via Rector"
Step 2: Updating Dependencies with Composer
With automated code changes applied, we now update our composer.json to require CakePHP 4 and compatible versions of related packages.
Modifying composer.json
Open your composer.json file and update the require and require-dev sections. A typical CakePHP 4 configuration looks like:
{
"require": {
"php": ">=7.4",
"cakephp/cakephp": "^4.0",
"cakephp/migrations": "^3.0",
"cakephp/plugin-installer": "^1.0",
"mobiledetect/mobiledetectlib": "^2.8"
},
"require-dev": {
"cakephp/bake": "^2.0",
"cakephp/cakephp-codesniffer": "^4.0",
"cakephp/debug_kit": "^4.0",
"phpunit/phpunit": "^8.5 || ^9.3"
}
}
Let’s examine these requirements:
- PHP version: CakePHP 4 requires PHP 7.2+, but we specify
>=7.4to align with actively supported PHP versions. If you’re ready for PHP 8, you can use>=8.0. - cakephp/cakephp: The
^4.0constraint allows any 4.x version but prevents automatic upgrades to 5.0 (which will have its own breaking changes). - cakephp/migrations: The migrations plugin changed version numbering—version 3.x is compatible with CakePHP 4.
- cakephp/plugin-installer: This remains at version 1.x; version 2.x targets CakePHP 5.
- cakephp/bake: The code generation tool updated to version 2.x for CakePHP 4 compatibility.
- cakephp/debug_kit: DebugKit 4.x provides debugging tools for CakePHP 4.
- phpunit/phpunit: The version constraint accounts for compatibility across PHP versions—PHP 7.4 typically uses PHPUnit 8.5, while PHP 8.x can use PHPUnit 9.3+.
Plugin Considerations
If you use additional CakePHP plugins, you must upgrade them to versions compatible with CakePHP 4. Check each plugin’s documentation or packagist page for compatible versions. For example:
cakephp/authorization: Version 2.x for CakePHP 4friendsofcake/crud: Version 7.x or later- Any custom plugins: You’ll need to upgrade those manually following similar patterns
One may wonder: what happens if a plugin doesn’t have a CakePHP 4 compatible version? In that case, you must either:
- Find an alternative plugin with equivalent functionality
- Fork and upgrade the plugin yourself
- Remove dependency on that plugin (reimplementing its functionality)
- Continue running the incompatible code and accept potential breakage
Running Composer Update
After updating composer.json, run Composer:
composer update
Composer will resolve dependencies and install the appropriate versions. You should see output indicating CakePHP 4 packages are being installed or updated:
Loading composer repositories with package information
Updating dependencies (including require-dev)
...
- Installing cakephp/cakephp (4.3.6)
- Installing cakephp/debug_kit (4.0.0)
...
Package operations: 15 installs, 5 updates, 2 removals
Important: The exact version numbers you see will vary—newer releases appear regularly. This is expected and fine; CakePHP maintains backward compatibility within major versions.
If Composer encounters dependency conflicts, examine the error messages. They typically indicate version constraints that cannot be simultaneously satisfied. You may need to:
- Relax version constraints on other packages
- Update other packages to newer compatible versions
- Remove conflicting packages temporarily
Verifying Composer Changes
After Composer completes, check the installed versions:
composer show cakephp/cakephp
Output should show something like:
name : cakephp/cakephp
descrip. : The CakePHP framework
versions : * 4.3.6
If it still shows a 3.x version, the update didn’t succeed—review your composer.json constraints and run composer update again.
Step 3: Manual Code Adjustments
Rector handles many changes automatically, but manual intervention remains necessary. Let’s address the most common manual updates.
Application Skeleton Modernization
CakePHP 4 introduced significant changes to the application skeleton. Your existing src/Application.php (from CakePHP 3) needs revision.
The New Application.php Structure
A CakePHP 4 Application.php typically looks like this:
<?php
declare(strict_types=1);
namespace App;
use Cake\Core\Configure;
use Cake\Core\Exception\MissingPluginException;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\BaseApplication;
use Cake\Http\MiddlewareQueue;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
class Application extends BaseApplication
{
public function bootstrap(): void
{
parent::bootstrap();
// Bootstrap your application code here
// This is where you might load configuration files, plugins, etc.
}
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue
->add(new ErrorHandlerMiddleware(Configure::read('Error')))
->add(new AssetMiddleware())
->add(new RoutingMiddleware($this));
// Add your custom middleware here
return $middlewareQueue;
}
public function routes(\Cake\Routing\RouteBuilder $routes): void
{
// Load your route definitions here
parent::routes($routes);
}
}
Compare this with your CakePHP 3 Application.php (which likely extended Cake\Application or used a different structure). You’ll need to:
- Change the base class to
BaseApplication - Implement
middleware()method with proper middleware queue - Move any custom middleware logic into the queue
- Ensure
bootstrap()callsparent::bootstrap() - Verify
routes()method callsparent::routes($routes)
Important: The middleware order matters—ErrorHandler should come first, then AssetMiddleware for static files, then RoutingMiddleware. Review the CakePHP 4 documentation for details.
Migrating Custom Middleware
If your CakePHP 3 application had custom middleware, you’ll need to adapt it to the CakePHP 4 middleware interface. In CakePHP 3, middleware typically had this signature:
// CakePHP 3 style
public function __invoke($request, $response, $next)
{
// Your logic
return $next($request, $response);
}
In CakePHP 4, middleware has stronger typing:
// CakePHP 4 style
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $handler)
{
// Your logic
return $handler->handle($request);
}
Note the changes:
$nextparameter renamed to$handler(more semantically accurate)- Strongly typed to PSR-7 interfaces
- Handler is called via
$handler->handle($request)instead of$next($request, $response)
Configuration File Updates
Check your config/app.php and other configuration files for breaking changes.
bootstrap.php Simplification
CakePHP 4 significantly simplifies config/bootstrap.php. Many constants defined in CakePHP 3 (like APP, ROOT, WWW_ROOT) remain available, but some initialization logic moved to Application::bootstrap().
Review your bootstrap file:
- Remove any direct
requirestatements for core files—CakePHP handles autoloading via Composer - Configuration constants you define should still work
- Plugin loading typically moved to
bootstrap()method
Strict Typing Enforcement
CakePHP 4 strongly encourages strict types. Add declare(strict_types=1); to the top of every PHP file in your src/, config/, and tests/ directories:
<?php
declare(strict_types=1);
namespace App\Controller;
// ... rest of your class
Note: Rector may have already added this declaration to many files, but double-check it’s present everywhere.
Return Type Hints
You must add return type hints to controller actions, table methods, and other public APIs. CakePHP 4’s skeleton uses them extensively.
Controller actions should return either a ResponseInterface or null:
<?php
declare(strict_types=1);
namespace App\Controller;
use Cake\Http\ResponseInterface;
class UsersController extends AppController
{
public function index(): ?ResponseInterface
{
$users = $this->paginate($this->Users);
$this->set(compact('users'));
return null; // Renders view automatically
}
public function view(int $id): ResponseInterface
{
$user = $this->Users->get($id);
$this->set(compact('user'));
return $this->response;
}
}
Key points:
- Actions returning a response must have
: ResponseInterfacereturn type - Actions that let CakePHP render a view implicitly return
null - You can also return
$this->responseor aResponseobject directly
One may wonder: why enforce return types so strictly? PHP’s type system prevents subtle bugs where a method returns an unexpected type. The CakePHP team made this a framework requirement to elevate code quality across the ecosystem.
ORM and Entity Adjustments
If you use the ORM extensively, review these common changes:
Entity Method Signature Changes
The Entity class has updated method signatures. Common issues:
Entity::isDirty(): This method no longer takes an optional field name parameter in CakePHP 4. UseEntity::isDirty('field_name')for field checking.Entity::extract(): Return types more strictly definedEntity::set(): Method signature adjusted
Review your entity classes and any model methods that extend Entity.
Table and Query Changes
Table classes now expect more precise return types. Common adjustments:
// CakePHP 3
public function findActive($options = [])
{
return $this->find('all', $options)->where(['active' => true]);
}
// CakePHP 4 - specify return type
public function findActive(\Cake\ORM\Query $query, array $options): \Cake\ORM\Query
{
return $query->where(['active' => true]);
}
The find() method signature changed to require explicit type hints. Rector should handle this automatically, but verify.
Validator Method Signatures
If you have custom validation rules, method signatures changed:
// CakePHP 3
public function validateCustom($value, $context)
{
// validation logic
}
// CakePHP 4
public function validateCustom($value, $context): bool
{
// validation logic returning bool
}
Check your validation methods—they should return bool explicitly.
Deprecated Features Removed
CakePHP 4 removed several deprecated features from CakePHP 3. Common ones to check:
Inflectormethods: SomeInflectormethods changed signatures or movedHashclass: Methods adjusted for stricter typingCakeText: Deprecated in favor ofTextutility classCakeTime: Deprecated in favor ofTimeutility classCakeNumber: Deprecated in favor ofNumberutility class
Review the official migration guide for complete lists.
Step 4: Run Your Test Suite
Now comes the critical validation step. Run your full test suite:
vendor/bin/phpunit
If you’re using CakePHP’s default test setup, you should see output like:
PHPUnit 9.3.10 by Sebastian Bergmann and contributors.
................................................. 45 / 100 ( 45%)
................................................. 90 / 100 ( 90%)
...
Time: 00:02.345, Memory: 20.00 MB
OK (100 tests, 250 assertions)
Address any failing tests systematically:
- Check error messages: They typically indicate what’s still broken
- Review the specific test failures: They point to code needing adjustment
- Fix one issue at a time: Re-run tests after each fix to verify progress
Common test failures include:
- Missing type hints causing return value mismatches
- ORM queries using old method signatures
- Entity comparisons failing due to stricter equality checks
- Plugin compatibility issues
If you don’t have a comprehensive test suite, now is the time to add at least basic tests for critical paths. Better to catch regressions with automated tests than in production.
Warning: Do not proceed to deployment until your test suite passes completely on CakePHP 4. Partial upgrades are dangerous.
Performance Testing
Beyond unit tests, consider performance testing:
# Simple benchmark with ApacheBench (ab)
ab -n 1000 -c 10 http://localhost/your-app/
# Or use wrk for more sophisticated testing
wrk -t12 -c400 -d30s http://localhost/your-app/
Compare performance with your CakePHP 3 baseline—CakePHP 4 should be comparable or faster, but it’s worth verifying.
Common Pitfalls and Their Solutions
Let’s look at issues you’re likely to encounter and how to resolve them.
Plugin Incompatibility
Problem: A plugin you rely on hasn’t released a CakePHP 4 compatible version.
Solution: Check if:
- A beta or dev version exists (use
dev-masterbranch with caution) - An alternative plugin provides similar functionality
- You can temporarily remove the plugin and reimplement its critical features
If you must use an incompatible plugin, you’ll need to upgrade it yourself—copy the plugin into your plugins/ directory and apply similar Rector transformations, then adjust any CakePHP 3-specific code manually.
Missing Class Errors After Rector
Problem: Your application reports “Class not found” errors for classes that existed in CakePHP 3.
Solution: The class likely moved namespaces. Common migrations:
Cake\Network\Http\Client→Cake\Http\ClientCake\Network\Request→Cake\Http\ServerRequest(if you’re using PSR-7 ServerRequest)Cake\Event\Event→Cake\Event\EventInterface(interface-based)
Check the migration guide for exact namespace changes.
Type Hint Mismatches
Problem: PHP throws TypeError because a method returns the wrong type or doesn’t declare a return type that child classes expect.
Solution: Add missing return type declarations. CakePHP 4 expects:
- Controller actions:
?ResponseInterfaceorResponseInterface - Table finder methods:
Query - Entity getters: appropriate scalar or object types
- Service methods: concrete return types
If you’re extending framework classes, ensure your method signatures exactly match parent signatures (including nullable types).
Middleware Configuration Issues
Problem: Requests aren’t routing correctly, or middleware doesn’t execute.
Solution: Check src/Application.php:
- Does your
middleware()method return the modified queue? - Are you adding middleware in the correct order?
- Did you remove any CakePHP 3-specific middleware that no longer exists?
Use CakePHP’s built-in debugging to inspect middleware:
// In your Application::middleware(), temporarily add:
debug($middlewareQueue);
die();
Database Connection Problems
Problem: Database connections fail after upgrade.
Solution: CakePHP 4 uses ConnectionManager with slightly different configuration:
// config/app.php
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'host' => 'localhost',
'username' => 'my_app',
'password' => 'secret',
'database' => 'my_app',
'encoding' => 'utf8mb4',
'timezone' => 'UTC',
'cacheMetadata' => true,
'quoteIdentifiers' => false,
],
],
Verify your database configuration matches the new structure.
Asset and Routing Issues
Problem: Static assets (CSS, JavaScript) return 404, or URLs don’t resolve to expected controllers.
Solution: Ensure AssetMiddleware and RoutingMiddleware are properly added in Application::middleware(). Check that your config/routes.php loads correctly via Application::routes().
Troubleshooting
When things go wrong—and they sometimes do—here’s a systematic approach.
Debugging Step by Step
- Rollback to Known Good: First, ensure you can return to a working state:
git status # Verify you're on upgrade branch
git checkout main # Or your stable branch
# Verify your app still works
-
Isolate Changes: Use
git diffto see exactly what Rector changed. Look for patterns that might indicate over-aggressive modifications. -
Revert Problematic Sections: If a particular file seems incorrectly transformed, revert it:
git checkout main -- src/Controller/ProblematicController.php
# Then manually apply necessary changes
- Incrementally Apply Manual Changes: Apply manual modifications in small commits, running tests after each:
git commit -m "refactor: update Application.php for CakePHP 4"
vendor/bin/phpunit
git commit -m "refactor: add strict types to src/"
vendor/bin/phpunit
Specific Error Patterns
“Interface not found” errors:
- Check that you’ve updated Composer dependencies
- Run
composer dump-autoload -oto regenerate autoloader - Verify interface names haven’t changed (e.g.,
EventDispatherInterfacevsEventManagerInterface)
Method signature mismatches:
- Ensure you’ve added all required type hints
- Check that your method signatures match parent class or interface exactly
- Use
?for nullable return types
Missing trait or trait method conflicts:
- Some traits changed or split in CakePHP 4
- Check official docs for trait migration patterns
When All Else Fails
If you’re stuck:
- Consult the official migration guide: https://book.cakephp.org/4/en/appendices/4-0-migration-guide.html
- Search CakePHP issues: Many upgrade problems are already discussed on GitHub
- Ask the community: CakePHP Discord or Stack Overflow (tag
cakephp) - Create a minimal reproducible example: Strip your problem down to smallest code that exhibits the issue—don’t post entire applications
Conclusion: Welcome to CakePHP 4
You’ve now upgraded your application to CakePHP 4. Your application stands on a more modern foundation: stricter typing, cleaner architecture, and ongoing community support.
Remember:
- Test coverage is your friend—without it, you’re navigating blind
- Automated tools help but aren’t magic—review everything
- Plugins often determine upgrade complexity—verify compatibility early
- Documentation is your map—the official CakePHP 4 book covers many details we couldn’t include here
What’s next? Consider:
- Upgrading your PHP version further (to PHP 8.0 or 8.1) for additional performance and features
- Taking advantage of CakePHP 4’s new features like improved ORM queries, stricter validation, and enhanced caching
- Refactoring areas that felt awkward during upgrade—the structure is now modern, take advantage of it
The upgrade journey, like the elephant’s migration across changing terrain, requires patience, memory, and careful navigation. You’ve arrived with your application intact and positioned for future growth.
Good luck, and happy coding!
Sponsored by Durable Programming
Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.
Hire Durable Programming