The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

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\Network to Cake\Http and other streamlined paths
  • Application skeleton modernization: src/Application.php now extends BaseApplication with 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:

  1. Manual rewrite: Rebuilding the application from scratch on CakePHP 4
  2. Automated tooling: Using the official cakephp/upgrade package with Rector
  3. 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:

  1. Version Control: Your application must be under Git (or another VCS). Create a dedicated branch for the upgrade work:
git checkout -b upgrade/cakephp4
  1. 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.

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

  3. 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\ClientCake\Http\Client)
  • Interface implementations (e.g., Cake\Event\EventDispatcherInterfaceCake\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.php rewrite)
  • 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 use statements
  • 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.4 to align with actively supported PHP versions. If you’re ready for PHP 8, you can use >=8.0.
  • cakephp/cakephp: The ^4.0 constraint 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 4
  • friendsofcake/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:

  1. Change the base class to BaseApplication
  2. Implement middleware() method with proper middleware queue
  3. Move any custom middleware logic into the queue
  4. Ensure bootstrap() calls parent::bootstrap()
  5. Verify routes() method calls parent::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:

  • $next parameter 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 require statements 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 : ResponseInterface return type
  • Actions that let CakePHP render a view implicitly return null
  • You can also return $this->response or a Response object 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. Use Entity::isDirty('field_name') for field checking.
  • Entity::extract(): Return types more strictly defined
  • Entity::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:

  • Inflector methods: Some Inflector methods changed signatures or moved
  • Hash class: Methods adjusted for stricter typing
  • CakeText: Deprecated in favor of Text utility class
  • CakeTime: Deprecated in favor of Time utility class
  • CakeNumber: Deprecated in favor of Number utility 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:

  1. Check error messages: They typically indicate what’s still broken
  2. Review the specific test failures: They point to code needing adjustment
  3. 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-master branch 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\ClientCake\Http\Client
  • Cake\Network\RequestCake\Http\ServerRequest (if you’re using PSR-7 ServerRequest)
  • Cake\Event\EventCake\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: ?ResponseInterface or ResponseInterface
  • 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

  1. 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
  1. Isolate Changes: Use git diff to see exactly what Rector changed. Look for patterns that might indicate over-aggressive modifications.

  2. Revert Problematic Sections: If a particular file seems incorrectly transformed, revert it:

git checkout main -- src/Controller/ProblematicController.php
# Then manually apply necessary changes
  1. 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 -o to regenerate autoloader
  • Verify interface names haven’t changed (e.g., EventDispatherInterface vs EventManagerInterface)

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:

  1. Consult the official migration guide: https://book.cakephp.org/4/en/appendices/4-0-migration-guide.html
  2. Search CakePHP issues: Many upgrade problems are already discussed on GitHub
  3. Ask the community: CakePHP Discord or Stack Overflow (tag cakephp)
  4. 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