New ORM Upgrade Guide

CakePHP 3.0 features a new ORM that has been re-written from the ground up.While the ORM used in 1.x and 2.x has served us well for a long time it hada few issues that we wanted to fix.

  • Frankenstein - Is it a record, or a table? Currently it’s both.
  • Inconsistent API - Model::read() for example.
  • No query object - Queries are always defined as arrays, this has somelimitations and restrictions. For example it makes doing unions andsub-queries much harder.
  • Returns arrays - This is a common complaint about CakePHP, and has probablyreduced adoption at some levels.
  • No record object - This makes attaching formatting methodsdifficult/impossible.
  • Containable - Should be part of the ORM, not a crazy hacky behavior.
  • Recursive - This should be better controlled as defining which associationsare included, not a level of recursiveness.
  • DboSource - It is a beast, and Model relies on it more than datasource. Thatseparation could be cleaner and simpler.
  • Validation - Should be separate, it’s a giant crazy function right now. Makingit a reusable bit would make the framework more extensible.
    The ORM in CakePHP 3.0 solves these and many more problems. The new ORMfocuses on relational data stores right now. In the future and through pluginswe will add non relational stores like ElasticSearch and others.

Design of the New ORM

The new ORM solves several problems by having more specialized and focusedclasses. In the past you would use Model and a Datasource for alloperations. Now the ORM is split into more layers:

  • Cake\Database\Connection - Provides a platform independent way to createand use connections. This class provides a way to use transactions,execute queries and access schema data.
  • Cake\Database\Dialect - The classes in this namespace provide platformspecific SQL and transform queries to work around platform specificlimitations.
  • Cake\Database\Type - Is the gateway class to CakePHP database typeconversion system. It is a pluggable framework for adding abstract columntypes and providing mappings between database, PHP representations and PDObindings for each data type. For example datetime columns are represented asDateTime instances in your code now.
  • Cake\ORM\Table - The main entry point into the new ORM. Provides accessto a single table. Handles the definition of association, use of behaviors andcreation of entities and query objects.
  • Cake\ORM\Behavior - The base class for behaviors, which act very similarto behaviors in previous versions of CakePHP.
  • Cake\ORM\Query - A fluent object based query builder that replacesthe deeply nested arrays used in previous versions of CakePHP.
  • Cake\ORM\ResultSet - A collection of results that gives powerful toolsfor manipulating data in aggregate.
  • Cake\ORM\Entity - Represents a single row result. Makes accessing dataand serializing to various formats a snap.
    Now that you are more familiar with some of the classes you’ll interact withmost frequently in the new ORM it is good to look at the three most importantclasses. The Table, Query and Entity classes do much of the heavylifting in the new ORM, and each serves a different purpose.

Table Objects

Table objects are the gateway into your data. They handle many of the tasks thatModel did in previous releases. Table classes handle tasks like:

  • Creating queries.
  • Providing finders.
  • Validating and saving entities.
  • Deleting entities.
  • Defining and accessing associations.
  • Triggering callback events.
  • Interacting with behaviors.
    The documentation chapter on Table Objects provides far more detailon how to use table objects than this guide can. Generally when moving existingmodel code over it will end up in a table object. Table objects don’t containany platform dependent SQL. Instead they collaborate with entities and the querybuilder to do their work. Table objects also interact with behaviors and otherinterested parties through published events.

Query Objects

While these are not classes you will build yourself, your application code willmake extensive use of the Query Builder which is central to the newORM. The query builder makes it easy to build simple or complex queriesincluding those that were previously very difficult in CakePHP like HAVING,UNION and sub-queries.

The various find() calls your application has currently will need to be updatedto use the new query builder. The Query object is responsible for containing thedata to make a query without executing the query itself. It collaborates withthe connection/dialect to generate platform specific SQL which is executedcreating a ResultSet as the output.

Entity Objects

In previous versions of CakePHP the Model class returned dumb arrays thatcould not contain any logic or behavior. While the community made thisshort-coming less painful with projects like CakeEntity, the array results wereoften a short coming that caused many developers trouble. For CakePHP 3.0, theORM always returns object result sets unless you explicitly disable thatfeature. The chapter on Entities covers the various tasks you canaccomplish with entities.

Entities are created in one of two ways. Either by loading data from thedatabase, or converting request data into entities. Once created, entities allowyou to manipulate the data they contain and persist their data by collaboratingwith table objects.

Key Differences

The new ORM is a large departure from the existing Model layer. There aremany important differences that are important in understanding how the new ORMoperates and how to update your code.

Inflection Rules Updated

You may have noticed that table classes have a pluralized name. In addition totables having pluralized names, associations are also referred in the pluralform. This is in contrast to Model where class names and association aliaseswere singular. There are a few reasons for this change:

  • Table classes represent collections of data, not single rows.
  • Associations link tables together, describing the relations between manythings.
    While the conventions for table objects are to always use plural forms, yourentity association properties will be populated based on the association type.

Note

BelongsTo and HasOne associations will use the singular form in entityproperties, while HasMany and BelongsToMany (HABTM) will use plural forms.

The convention change for table objects is most apparent when building queries.Instead of expressing queries like:

  1. // Wrong
  2. $query->where(['User.active' => 1]);

You need to use the plural form:

  1. // Correct
  2. $query->where(['Users.active' => 1]);

Find returns a Query Object

One important difference in the new ORM is that calling find on a table willnot return the results immediately, but will return a Query object; this servesseveral purposes.

It is possible to alter queries further, after calling find:

  1. $articles = TableRegistry::get('Articles');
  2. $query = $articles->find();
  3. $query->where(['author_id' => 1])->order(['title' => 'DESC']);

It is possible to stack custom finders to append conditions, sorting, limit andany other clause to the same query before it is executed:

  1. $query = $articles->find('approved')->find('popular');
  2. $query->find('latest');

You can compose queries one into the other to create subqueries easier thanever:

  1. $query = $articles->find('approved');
  2. $favoritesQuery = $article->find('favorites', ['for' => $user]);
  3. $query->where(['id' => $favoritesQuery->select(['id'])]);

You can decorate queries with iterators and call methods without even touchingthe database. This is great when you have parts of your view cached and havingthe results taken from the database is not actually required:

  1. // No queries made in this example!
  2. $results = $articles->find()
  3. ->order(['title' => 'DESC'])
  4. ->formatResults(function (\Cake\Collection\CollectionInterface $results) {
  5. return $results->extract('title');
  6. });

Queries can be seen as the result object, trying to iterate the query, callingtoArray() or any method inherited from collection,will result in the query being executed and results returned to you.

The biggest difference you will find when coming from CakePHP 2.x is thatfind('first') does not exist anymore. There is a trivial replacement for it,and it is the first() method:

  1. // Before
  2. $article = $this->Article->find('first');
  3.  
  4. // Now
  5. $article = $this->Articles->find()->first();
  6.  
  7. // Before
  8. $article = $this->Article->find('first', [
  9. 'conditions' => ['author_id' => 1]
  10. ]);
  11.  
  12. // Now
  13. $article = $this->Articles->find('all', [
  14. 'conditions' => ['author_id' => 1]
  15. ])->first();
  16.  
  17. // Can also be written
  18. $article = $this->Articles->find()
  19. ->where(['author_id' => 1])
  20. ->first();

If you are loading a single record by its primary key, it will be better tojust call get():

  1. $article = $this->Articles->get(10);

Finder Method Changes

Returning a query object from a find method has several advantages, but comes ata cost for people migrating from 2.x. If you had some custom find methods inyour models, they will need some modifications. This is how you create customfinder methods in 3.0:

  1. class ArticlesTable
  2. {
  3.  
  4. public function findPopular(Query $query, array $options)
  5. {
  6. return $query->where(['times_viewed' > 1000]);
  7. }
  8.  
  9. public function findFavorites(Query $query, array $options)
  10. {
  11. $for = $options['for'];
  12. return $query->matching('Users.Favorites', function ($q) use ($for) {
  13. return $q->where(['Favorites.user_id' => $for]);
  14. });
  15. }
  16. }

As you can see, they are pretty straightforward, they get a Query object insteadof an array and must return a Query object back. For 2.x users that implementedafterFind logic in custom finders, you should check out the Modifying Results with Map/Reducesection, or use the features found on thecollection objects. If in yourmodels you used to rely on having an afterFind for all find operations you canmigrate this code in one of a few ways:

  • Override your entity constructor method and do additional formatting there.
  • Create accessor methods in your entity to create the virtual fields.
  • Redefine findAll() and use formatResults.
    In the 3rd case above your code would look like:
  1. public function findAll(Query $query, array $options)
  2. {
  3. return $query->formatResults(function (\Cake\Collection\CollectionInterface $results) {
  4. return $results->map(function ($row) {
  5. // Your afterfind logic
  6. });
  7. })
  8. }

You may have noticed that custom finders receive an options array. You can passany extra information to your finder using this parameter. This is greatnews for people migrating from 2.x. Any of the query keys that were used inprevious versions will be converted automatically for you in 3.x to the correctfunctions:

  1. // This works in both CakePHP 2.x and 3.0
  2. $articles = $this->Articles->find('all', [
  3. 'fields' => ['id', 'title'],
  4. 'conditions' => [
  5. 'OR' => ['title' => 'Cake', 'author_id' => 1],
  6. 'published' => true
  7. ],
  8. 'contain' => ['Authors'], // The only change! (notice plural)
  9. 'order' => ['title' => 'DESC'],
  10. 'limit' => 10,
  11. ]);

If your application uses ‘magic’ or Dynamic Finders, you will have toadapt those calls. In 3.x the findAllBy methods have been removed, insteadfindBy always returns a query object. To get the first result, you need touse the first() method:

  1. $article = $this->Articles->findByTitle('A great post!')->first();

Hopefully, migrating from older versions is not as daunting as it first seems.Many of the features we have added will help you remove code as you can betterexpress your requirements using the new ORM and at the same time thecompatibility wrappers will help you rewrite those tiny differences in a fastand painless way.

One of the other nice improvements in 3.x around finder methods is thatbehaviors can implement finder methods with no fuss. By simply defining a methodwith a matching name and signature on a Behavior the finder will automaticallybe available on any tables the behavior is attached to.

Recursive and ContainableBehavior Removed

In previous versions of CakePHP you needed to use recursive,bindModel(), unbindModel() and ContainableBehavior to reduce theloaded data to the set of associations you were interested in. A common tacticto manage associations was to set recursive to -1 and use Containable tomanage all associations. In CakePHP 3.0 ContainableBehavior, recursive,bindModel, and unbindModel have all been removed. Instead the contain()method has been promoted to be a core feature of the query builder. Associationsare only loaded if they are explicitly turned on. For example:

  1. $query = $this->Articles->find('all');

Will only load data from the articles table as no associations have beenincluded. To load articles and their related authors you would do:

  1. $query = $this->Articles->find('all')->contain(['Authors']);

By only loading associated data that has been specifically requested you spendless time fighting the ORM trying to get only the data you want.

No afterFind Event or Virtual Fields

In previous versions of CakePHP you needed to make extensive use of theafterFind callback and virtual fields in order to create generated dataproperties. These features have been removed in 3.0. Because of how ResultSetsiteratively generate entities, the afterFind callback was not possible.Both afterFind and virtual fields can largely be replaced with virtualproperties on entities. For example if your User entity has both first and lastname columns you can add an accessor for full_name and generate the propertyon the fly:

  1. namespace App\Model\Entity;
  2.  
  3. use Cake\ORM\Entity;
  4.  
  5. class User extends Entity
  6. {
  7. protected function _getFullName()
  8. {
  9. return $this->first_name . ' ' . $this->last_name;
  10. }
  11. }

Once defined you can access your new property using $user->full_name.Using the Modifying Results with Map/Reduce features of the ORM allow you to build aggregateddata from your results, which is another use case that the afterFindcallback was often used for.

While virtual fields are no longer an explicit feature of the ORM, addingcalculated fields is easy to do in your finder methods. By using the querybuilder and expression objects you can achieve the same results that virtualfields gave:

  1. namespace App\Model\Table;
  2.  
  3. use Cake\ORM\Table;
  4. use Cake\ORM\Query;
  5.  
  6. class ReviewsTable extends Table
  7. {
  8. public function findAverage(Query $query, array $options = [])
  9. {
  10. $avg = $query->func()->avg('rating');
  11. $query->select(['average' => $avg]);
  12. return $query;
  13. }
  14. }

Associations No Longer Defined as Properties

In previous versions of CakePHP the various associations your models had weredefined in properties like $belongsTo and $hasMany. In CakePHP 3.0,associations are created with methods. Using methods allows us to sidestep themany limitations class definitions have, and provide only one way to defineassociations. Your initialize() method and all other parts of your applicationcode, interact with the same API when manipulating associations:

  1. namespace App\Model\Table;
  2.  
  3. use Cake\ORM\Table;
  4. use Cake\ORM\Query;
  5.  
  6. class ReviewsTable extends Table
  7. {
  8.  
  9. public function initialize(array $config)
  10. {
  11. $this->belongsTo('Movies');
  12. $this->hasOne('Ratings');
  13. $this->hasMany('Comments')
  14. $this->belongsToMany('Tags')
  15. }
  16.  
  17. }

As you can see from the example above each of the association types usesa method to create the association. One other difference is thathasAndBelongsToMany has been renamed to belongsToMany. To find out moreabout creating associations in 3.0 see the section on Associations - Linking Tables Together.

Another welcome improvement to CakePHP is the ability to create your ownassociation classes. If you have association types that are not covered by thebuilt-in relation types you can create a custom Association sub-class anddefine the association logic you need.

Validation No Longer Defined as a Property

Like associations, validation rules were defined as a class property in previousversions of CakePHP. This array would then be lazily transformed intoa ModelValidator object. This transformation step added a layer ofindirection, complicating rule changes at runtime. Furthermore, validation rulesbeing defined as a property made it difficult for a model to have multiple setsof validation rules. In CakePHP 3.0, both these problems have been remedied.Validation rules are always built with a Validator object, and it is trivialto have multiple sets of rules:

  1. namespace App\Model\Table;
  2.  
  3. use Cake\ORM\Table;
  4. use Cake\ORM\Query;
  5. use Cake\Validation\Validator;
  6.  
  7. class ReviewsTable extends Table
  8. {
  9.  
  10. public function validationDefault(Validator $validator)
  11. {
  12. $validator->requirePresence('body')
  13. ->add('body', 'length', [
  14. 'rule' => ['minLength', 20],
  15. 'message' => 'Reviews must be 20 characters or more',
  16. ])
  17. ->add('user_id', 'numeric', [
  18. 'rule' => 'numeric'
  19. ]);
  20. return $validator;
  21. }
  22.  
  23. }

You can define as many validation methods as you need. Each method should beprefixed with validation and accept a $validator argument.

In previous versions of CakePHP ‘validation’ and the related callbacks covereda few related but different uses. In CakePHP 3.0, what was formerly calledvalidation is now split into two concepts:

  • Data type and format validation.
  • Enforcing application, or business rules.
    Validation is now applied before ORM entities are created from request data.This step lets you ensure data matches the data type, format, and basic shapeyour application expects. You can use your validators when converting requestdata into entities by using the validate option. See the documentation onConverting Request Data into Entities for more information.

Application rules allow you to define rules thatensure your application’s rules, state and workflows are enforced. Rules aredefined in your Table’s buildRules() method. Behaviors can add rules usingthe buildRules() hook method. An example buildRules() method for ourarticles table could be:

  1. // In src/Model/Table/ArticlesTable.php
  2. namespace App\Model\Table;
  3.  
  4. use Cake\ORM\Table;
  5. use Cake\ORM\RulesChecker;
  6.  
  7. class ArticlesTable extends Table
  8. {
  9. public function buildRules(RulesChecker $rules)
  10. {
  11. $rules->add($rules->existsIn('user_id', 'Users'));
  12. $rules->add(
  13. function ($article, $options) {
  14. return ($article->published && empty($article->reviewer));
  15. },
  16. 'isReviewed',
  17. [
  18. 'errorField' => 'published',
  19. 'message' => 'Articles must be reviewed before publishing.'
  20. ]
  21. );
  22. return $rules;
  23. }
  24. }

Identifier Quoting Disabled by Default

In the past CakePHP has always quoted identifiers. Parsing SQL snippets andattempting to quote identifiers was both error prone and expensive. If you arefollowing the conventions CakePHP sets out, the cost of identifier quoting faroutweighs any benefit it provides. Because of this identifier quoting has beendisabled by default in 3.0. You should only need to enable identifier quoting ifyou are using column names or table names that contain special characters or arereserved words. If required, you can enable identifier quoting when configuringa connection:

  1. // In config/app.php
  2. 'Datasources' => [
  3. 'default' => [
  4. 'className' => 'Cake\Database\Driver\Mysql',
  5. 'username' => 'root',
  6. 'password' => 'super_secret',
  7. 'host' => 'localhost',
  8. 'database' => 'cakephp',
  9. 'quoteIdentifiers' => true,
  10. ]
  11. ],

Note

Identifiers in QueryExpression objects will not be quoted, and you willneed to quote them manually or use IdentifierExpression objects.

Updating Behaviors

Like most ORM related features, behaviors have changed in 3.0 as well. They nowattach to Table instances which are the conceptual descendant of theModel class in previous versions of CakePHP. There are a few keydifferences from behaviors in CakePHP 2.x:

  • Behaviors are no longer shared across multiple tables. This means you nolonger have to ‘namespace’ settings stored in a behavior. Each table usinga behavior will get its own instance.
  • The method signatures for mixin methods have changed.
  • The method signatures for callback methods have changed.
  • The base class for behaviors have changed.
  • Behaviors can add finder methods.

New Base Class

The base class for behaviors has changed. Behaviors should now extendCake\ORM\Behavior; if a behavior does not extend this class an exceptionwill be raised. In addition to the base class changing, the constructor forbehaviors has been modified, and the startup() method has been removed.Behaviors that need access to the table they are attached to should definea constructor:

  1. namespace App\Model\Behavior;
  2.  
  3. use Cake\ORM\Behavior;
  4.  
  5. class SluggableBehavior extends Behavior
  6. {
  7.  
  8. protected $_table;
  9.  
  10. public function __construct(Table $table, array $config)
  11. {
  12. parent::__construct($table, $config);
  13. $this->_table = $table;
  14. }
  15.  
  16. }

Mixin Methods Signature Changes

Behaviors continue to offer the ability to add ‘mixin’ methods to Table objects,however the method signature for these methods has changed. In CakePHP 3.0,behavior mixin methods can expect the same arguments provided to the table‘method’. For example:

  1. // Assume table has a slug() method provided by a behavior.
  2. $table->slug($someValue);

The behavior providing the slug() method will receive only 1 argument, and itsmethod signature should look like:

  1. public function slug($value)
  2. {
  3. // Code here.
  4. }

Callback Method Signature Changes

Behavior callbacks have been unified with all other listener methods. Instead oftheir previous arguments, they need to expect an event object as their firstargument:

  1. public function beforeFind(Event $event, Query $query, array $options)
  2. {
  3. // Code.
  4. }

See Lifecycle Callbacks for the signatures of all the callbacks a behaviorcan subscribe to.