One reason to migrate from CakePHP 2.x to newer versions, is the very powerful ORM system that was introduced in CakePHP 3.x.
Improved ORM Objects
The CakePHP model layer in CakePHP 3.x uses the Data Mapper pattern.
Model classes in CakePHP 3.x ORM are split into two separate objects. Entity represents a single row in the database and it is responsible for keeping record state. Table class provides access to a collection of database records and describe associations, and provides api to work with a database.
One notable change is afterFind callback removal. In CakePHP 3.x, it is possible to use Entity level getters to provide calculated fields on entity level.
Association Upgrade
In CakePHP 2.x associations are defined as arrays properties like this:
public $belongsTo = [
'Profile' => [
'className' => 'Profile',
'foreignKey' => 'profile_id',
]
];
In CakePHP 3.x and 4.x associations are declared in the initialize method. This gives much more flexibility in association configuration.
public function initialize(): void
{
$this->belongsTo('Profile', [
'className' => 'Profile',
'foreignKey' => 'profile_id',
]);
}
Or using setters syntax, it could be done this way:
public function initialize(): void
{
$this->belongsTo('Profile')
->setForeignKey('profile_id')
}
Behavior Upgrade
In CakePHP 2.x, behaviors are initialized as arrays properties:
public $actsAs = [
'Sluggable' => [
'label' => 'name',
],
];
In CakePHP 3.x and 4.x, behaviors are configured in the initialize method. This gives much more flexibility in configuration, as in params it's possible to pass anonymous functions.
public function initialize(): void
{
$this->addBehavior('Sluggable', [
'label' => 'name',
]);
}
Validation Upgrade
In CakePHP 2.x, behaviors are initialized as arrays properties:
public $validation = [
'title' => [
'notBlank' => [
'rule' => ['notBlank'],
'allowEmpty' => false,
'required' => true,
'message' => 'Title is required'
],
],
];
In CakePHP 3.x and 4.x, validation is defined in validationDefault method which builds validation rules.
public function validationDefault(Validator $validator): Validator
{
$validator
->scalar('title')
->requirePresence('title', 'create')
->notEmptyString('title');
return $validator;
}
Additionally, CakePHP introduced the buildRules method, which is where described foreign keys constraints, uniqueness, or business level rules.
public function buildRules(RulesChecker $rules): RulesChecker
{
$rules->add($rules->existsIn(['user_id'], 'Users'));
$rules->add($rules->isUnique(['username'], __('username should be unique')));
return $rules;
}
Finder Methods
In CakePHP 2.x, the custom finder method is called twice - before and after fetching data from the database, which is defined by the $state parameter. Parameter $query contains current query state, and in $results passed data returned from database.
protected function _findIndex($state, $query, $results = array()) {
if ($state == 'before') {
$query['contain'] = ['User'];
} else {
// ...
}
}
In CakePHP 3.x, custom finder method accepts query object and some options passed from client code and returns an updated query. This allows for combining multiple finder methods in the same call, and has better grained finder logic.
public function findIndex(Query $query, array $options): Query
{
return $query->contain(['Users']);
}
The afterFind method could be implemented with the Query::formatResults method, which accepts an anonymous function to map each collection item.