Saving Data

  • class Cake\ORM\Table

After you have loaded your data youwill probably want to update and save the changes.

A Glance Over Saving Data

Applications will usually have a couple of ways in which data is saved. Thefirst one is obviously through web forms and the other is by directly generatingor changing data in the code to be sent to the database.

Inserting Data

The easiest way to insert data in the database is by creating a new entity andpassing it to the save() method in the Table class:

  1. use Cake\ORM\TableRegistry;
  2.  
  3. $articlesTable = TableRegistry::getTableLocator()->get('Articles');
  4. $article = $articlesTable->newEmptyEntity();
  5.  
  6. $article->title = 'A New Article';
  7. $article->body = 'This is the body of the article';
  8.  
  9. if ($articlesTable->save($article)) {
  10. // The $article entity contains the id now
  11. $id = $article->id;
  12. }

Updating Data

Updating your data is equally easy, and the save() method is also used forthat purpose:

  1. use Cake\ORM\TableRegistry;
  2.  
  3. $articlesTable = TableRegistry::getTableLocator()->get('Articles');
  4. $article = $articlesTable->get(12); // Return article with id 12
  5.  
  6. $article->title = 'CakePHP is THE best PHP framework!';
  7. $articlesTable->save($article);

CakePHP will know whether to perform an insert or an update based on the returnvalue of the isNew() method. Entities that were retrieved with get() orfind() will always return false when isNew() is called on them.

Saving With Associations

By default the save() method will also save one level of associations:

  1. $articlesTable = TableRegistry::getTableLocator()->get('Articles');
  2. $author = $articlesTable->Authors->findByUserName('mark')->first();
  3.  
  4. $article = $articlesTable->newEmptyEntity();
  5. $article->title = 'An article by mark';
  6. $article->author = $author;
  7.  
  8. if ($articlesTable->save($article)) {
  9. // The foreign key value was set automatically.
  10. echo $article->author_id;
  11. }

The save() method is also able to create new records for associations:

  1. $firstComment = $articlesTable->Comments->newEmptyEntity();
  2. $firstComment->body = 'The CakePHP features are outstanding';
  3.  
  4. $secondComment = $articlesTable->Comments->newEmptyEntity();
  5. $secondComment->body = 'CakePHP performance is terrific!';
  6.  
  7. $tag1 = $articlesTable->Tags->findByName('cakephp')->first();
  8. $tag2 = $articlesTable->Tags->newEmptyEntity();
  9. $tag2->name = 'awesome';
  10.  
  11. $article = $articlesTable->get(12);
  12. $article->comments = [$firstComment, $secondComment];
  13. $article->tags = [$tag1, $tag2];
  14.  
  15. $articlesTable->save($article);

Associate Many To Many Records

The previous example demonstrates how to associate a few tags to an article.Another way of accomplishing the same thing is by using the link()method in the association:

  1. $tag1 = $articlesTable->Tags->findByName('cakephp')->first();
  2. $tag2 = $articlesTable->Tags->newEmptyEntity();
  3. $tag2->name = 'awesome';
  4.  
  5. $articlesTable->Tags->link($article, [$tag1, $tag2]);

Unlinking many to many records is done via the unlink() method:

  1. $tags = $articlesTable
  2. ->Tags
  3. ->find()
  4. ->where(['name IN' => ['cakephp', 'awesome']])
  5. ->toList();
  6.  
  7. $articlesTable->Tags->unlink($article, $tags);

When modifying records by directly setting or changing the properties novalidation happens, which is a problem when accepting form data. The followingsections will demonstrate how to efficiently convert form data into entities sothat they can be validated and saved.

Converting Request Data into Entities

Before editing and saving data back to your database, you’ll need to convertthe request data from the array format held in the request, and the entitiesthat the ORM uses. The Table class provides an easy and efficient way to convertone or many entities from request data. You can convert a single entity using:

  1. // In a controller
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4.  
  5. // Validate and convert to an Entity object
  6. $entity = $articles->newEntity($this->request->getData());

Note

If you are using newEntity() and the resulting entities are missing some orall of the data they were passed, double check that the columns you want toset are listed in the $_accessible property of your entity. See Mass Assignment.

The request data should follow the structure of your entities. For example ifyou have an article, which belonged to a user, and had many comments, yourrequest data should resemble:

  1. $data = [
  2. 'title' => 'CakePHP For the Win',
  3. 'body' => 'Baking with CakePHP makes web development fun!',
  4. 'user_id' => 1,
  5. 'user' => [
  6. 'username' => 'mark'
  7. ],
  8. 'comments' => [
  9. ['body' => 'The CakePHP features are outstanding'],
  10. ['body' => 'CakePHP performance is terrific!'],
  11. ]
  12. ];

By default, the newEntity() method validates the data that gets passed toit, as explained in the Validating Data Before Building Entities section. If you wish tobypass data validation pass the 'validate' => false option:

  1. $entity = $articles->newEntity($data, ['validate' => false]);

When building forms that save nested associations, you need to define whichassociations should be marshalled:

  1. // In a controller
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4.  
  5. // New entity with nested associations
  6. $entity = $articles->newEntity($this->request->getData(), [
  7. 'associated' => [
  8. 'Tags', 'Comments' => ['associated' => ['Users']]
  9. ]
  10. ]);

The above indicates that the ‘Tags’, ‘Comments’ and ‘Users’ for the Commentsshould be marshalled. Alternatively, you can use dot notation for brevity:

  1. // In a controller
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4.  
  5. // New entity with nested associations using dot notation
  6. $entity = $articles->newEntity($this->request->getData(), [
  7. 'associated' => ['Tags', 'Comments.Users']
  8. ]);

You may also disable marshalling of possible nested associations like so:

  1. $entity = $articles->newEntity($data, ['associated' => []]);
  2. // or...
  3. $entity = $articles->patchEntity($entity, $data, ['associated' => []]);

Associated data is also validated by default unless told otherwise. You may alsochange the validation set to be used per association:

  1. // In a controller
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4.  
  5. // Bypass validation on Tags association and
  6. // Designate 'signup' validation set for Comments.Users
  7. $entity = $articles->newEntity($this->request->getData(), [
  8. 'associated' => [
  9. 'Tags' => ['validate' => false],
  10. 'Comments.Users' => ['validate' => 'signup']
  11. ]
  12. ]);

The Using A Different Validation Set For Associations chapter has moreinformation on how to use different validators for associated marshalling.

The following diagram gives an overview of what happens inside thenewEntity() or patchEntity() method:

Flow diagram showing the marshalling/validation process.

You can always count on getting an entity back from newEntity(). Ifvalidation fails your entity will contain errors, and any invalid fields willnot be populated in the created entity.

Converting BelongsToMany Data

If you are saving belongsToMany associations you can either use a list of entitydata or a list of ids. When using a list of entity data your request data shouldlook like:

  1. $data = [
  2. 'title' => 'My title',
  3. 'body' => 'The text',
  4. 'user_id' => 1,
  5. 'tags' => [
  6. ['name' => 'CakePHP'],
  7. ['name' => 'Internet'],
  8. ]
  9. ];

The above will create 2 new tags. If you want to link an article with existingtags you can use a list of ids. Your request data should look like:

  1. $data = [
  2. 'title' => 'My title',
  3. 'body' => 'The text',
  4. 'user_id' => 1,
  5. 'tags' => [
  6. '_ids' => [1, 2, 3, 4]
  7. ]
  8. ];

If you need to link against some existing belongsToMany records, and create newones at the same time you can use an expanded format:

  1. $data = [
  2. 'title' => 'My title',
  3. 'body' => 'The text',
  4. 'user_id' => 1,
  5. 'tags' => [
  6. ['name' => 'A new tag'],
  7. ['name' => 'Another new tag'],
  8. ['id' => 5],
  9. ['id' => 21]
  10. ]
  11. ];

When the above data is converted into entities, you will have 4 tags. The firsttwo will be new objects, and the second two will be references to existingrecords.

When converting belongsToMany data, you can disable entity creation, byusing the onlyIds option:

  1. $result = $articles->patchEntity($entity, $data, [
  2. 'associated' => ['Tags' => ['onlyIds' => true]],
  3. ]);

When used, this option restricts belongsToMany association marshalling to onlyuse the _ids data.

Converting HasMany Data

If you want to update existing hasMany associations and update theirproperties, you should first ensure your entity is loaded with the hasManyassociation populated. You can then use request data similar to:

  1. $data = [
  2. 'title' => 'My Title',
  3. 'body' => 'The text',
  4. 'comments' => [
  5. ['id' => 1, 'comment' => 'Update the first comment'],
  6. ['id' => 2, 'comment' => 'Update the second comment'],
  7. ['comment' => 'Create a new comment'],
  8. ]
  9. ];

If you are saving hasMany associations and want to link existing records to anew parent record you can use the _ids format:

  1. $data = [
  2. 'title' => 'My new article',
  3. 'body' => 'The text',
  4. 'user_id' => 1,
  5. 'comments' => [
  6. '_ids' => [1, 2, 3, 4]
  7. ]
  8. ];

When converting hasMany data, you can disable the new entity creation, by usingthe onlyIds option. When enabled, this option restricts hasMany marshallingto only use the _ids key and ignore all other data.

Converting Multiple Records

When creating forms that create/update multiple records at once you can usenewEntities():

  1. // In a controller.
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4. $entities = $articles->newEntities($this->request->getData());

In this situation, the request data for multiple articles should look like:

  1. $data = [
  2. [
  3. 'title' => 'First post',
  4. 'published' => 1
  5. ],
  6. [
  7. 'title' => 'Second post',
  8. 'published' => 1
  9. ],
  10. ];

Once you’ve converted request data into entities you can save:

  1. // In a controller.
  2. foreach ($entities as $entity) {
  3. // Save entity
  4. $articles->save($entity);
  5. }

The above will run a separate transaction for each entity saved. If you’d liketo process all the entities as a single transaction you can usesaveMany() or saveManyOrFail():

  1. // Get a boolean indicating success
  2. $articles->saveMany($entities);
  3.  
  4. // Get a PersistenceFailedException if any records fail to save.
  5. $articles->saveManyOrFail($entities);

Changing Accessible Fields

It’s also possible to allow newEntity() to write into non accessible fields.For example, id is usually absent from the _accessible property. Insuch case, you can use the accessibleFields option. It could be useful tokeep ids of associated entities:

  1. // In a controller
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4. $entity = $articles->newEntity($this->request->getData(), [
  5. 'associated' => [
  6. 'Tags', 'Comments' => [
  7. 'associated' => [
  8. 'Users' => [
  9. 'accessibleFields' => ['id' => true]
  10. ]
  11. ]
  12. ]
  13. ]
  14. ]);

The above will keep the association unchanged between Comments and Users for theconcerned entity.

Note

If you are using newEntity() and the resulting entities are missing some orall of the data they were passed, double check that the columns you want toset are listed in the $_accessible property of your entity. SeeMass Assignment.

Merging Request Data Into Entities

In order to update entities you may choose to apply request data directly to anexisting entity. This has the advantage that only the fields that actuallychanged will be saved, as opposed to sending all fields to the database to bepersisted. You can merge an array of raw data into an existing entity using thepatchEntity() method:

  1. // In a controller.
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4. $article = $articles->get(1);
  5. $articles->patchEntity($article, $this->request->getData());
  6. $articles->save($article);

Validation and patchEntity

Similar to newEntity(), the patchEntity method will validate the databefore it is copied to the entity. The mechanism is explained in theValidating Data Before Building Entities section. If you wish to disable validation whilepatching an entity, pass the validate option as follows:

  1. // In a controller.
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4. $article = $articles->get(1);
  5. $articles->patchEntity($article, $data, ['validate' => false]);

You may also change the validation set used for the entity or any of theassociations:

  1. $articles->patchEntity($article, $this->request->getData(), [
  2. 'validate' => 'custom',
  3. 'associated' => ['Tags', 'Comments.Users' => ['validate' => 'signup']]
  4. ]);

Patching HasMany and BelongsToMany

As explained in the previous section, the request data should follow thestructure of your entity. The patchEntity() method is equally capable ofmerging associations, by default only the first level of associations aremerged, but if you wish to control the list of associations to be merged ormerge deeper to deeper levels, you can use the third parameter of the method:

  1. // In a controller.
  2. $associated = ['Tags', 'Comments.Users'];
  3. $article = $articles->get(1, ['contain' => $associated]);
  4. $articles->patchEntity($article, $this->request->getData(), [
  5. 'associated' => $associated
  6. ]);
  7. $articles->save($article);

Associations are merged by matching the primary key field in the source entitiesto the corresponding fields in the data array. Associations will construct newentities if no previous entity is found for the association’s target property.

For example give some request data like the following:

  1. $data = [
  2. 'title' => 'My title',
  3. 'user' => [
  4. 'username' => 'mark'
  5. ]
  6. ];

Trying to patch an entity without an entity in the user property will createa new user entity:

  1. // In a controller.
  2. $entity = $articles->patchEntity(new Article, $data);
  3. echo $entity->user->username; // Echoes 'mark'

The same can be said about hasMany and belongsToMany associations, withan important caveat:

Note

For belongsToMany associations, ensure the relevant entity hasa property accessible for the associated entity.

If a Product belongsToMany Tag:

  1. // in the Product Entity
  2. protected $_accessible = [
  3. // .. other properties
  4. 'tags' => true,
  5. ];

Note

For hasMany and belongsToMany associations, if there were any entities thatcould not be matched by primary key to a record in the data array, thenthose records will be discarded from the resulting entity.

Remember that using either patchEntity() or patchEntities() does notpersist the data, it just edits (or creates) the given entities. In order tosave the entity you will have to call the table’s save() method.

For example, consider the following case:

  1. $data = [
  2. 'title' => 'My title',
  3. 'body' => 'The text',
  4. 'comments' => [
  5. ['body' => 'First comment', 'id' => 1],
  6. ['body' => 'Second comment', 'id' => 2],
  7. ]
  8. ];
  9. $entity = $articles->newEntity($data);
  10. $articles->save($entity);
  11.  
  12. $newData = [
  13. 'comments' => [
  14. ['body' => 'Changed comment', 'id' => 1],
  15. ['body' => 'A new comment'],
  16. ]
  17. ];
  18. $articles->patchEntity($entity, $newData);
  19. $articles->save($entity);

At the end, if the entity is converted back to an array you will obtain thefollowing result:

  1. [
  2. 'title' => 'My title',
  3. 'body' => 'The text',
  4. 'comments' => [
  5. ['body' => 'Changed comment', 'id' => 1],
  6. ['body' => 'A new comment'],
  7. ]
  8. ];

As you can see, the comment with id 2 is no longer there, as it could not bematched to anything in the $newData array. This happens because CakePHP isreflecting the new state described in the request data.

Some additional advantages of this approach is that it reduces the number ofoperations to be executed when persisting the entity again.

Please note that this does not mean that the comment with id 2 was removed fromthe database, if you wish to remove the comments for that article that are notpresent in the entity, you can collect the primary keys and execute a batchdelete for those not in the list:

  1. // In a controller.
  2.  
  3. $comments = TableRegistry::getTableLocator()->get('Comments');
  4. $present = (new Collection($entity->comments))->extract('id')->filter()->toList();
  5. $comments->deleteAll([
  6. 'article_id' => $article->id,
  7. 'id NOT IN' => $present
  8. ]);

As you can see, this also helps creating solutions where an association needs tobe implemented like a single set.

You can also patch multiple entities at once. The consideration made forpatching hasMany and belongsToMany associations apply for patching multipleentities: Matches are done by the primary key field value and missing matches inthe original entities array will be removed and not present in the result:

  1. // In a controller.
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4. $list = $articles->find('popular')->toList();
  5. $patched = $articles->patchEntities($list, $this->request->getData());
  6. foreach ($patched as $entity) {
  7. $articles->save($entity);
  8. }

Similarly to using patchEntity(), you can use the third argument forcontrolling the associations that will be merged in each of the entities in thearray:

  1. // In a controller.
  2. $patched = $articles->patchEntities(
  3. $list,
  4. $this->request->getData(),
  5. ['associated' => ['Tags', 'Comments.Users']]
  6. );

Modifying Request Data Before Building Entities

If you need to modify request data before it is converted into entities, you canuse the Model.beforeMarshal event. This event lets you manipulate therequest data just before entities are created:

  1. // Include use statements at the top of your file.
  2. use Cake\Event\EventInterface;
  3. use ArrayObject;
  4.  
  5. // In a table or behavior class
  6. public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
  7. {
  8. if (isset($data['username'])) {
  9. $data['username'] = mb_strtolower($data['username']);
  10. }
  11. }

The $data parameter is an ArrayObject instance, so you don’t have toreturn it to change the data used to create entities.

The main purpose of beforeMarshal is to assist the users to pass thevalidation process when simple mistakes can be automatically resolved, or whendata needs to be restructured so it can be put into the right fields.

The Model.beforeMarshal event is triggered just at the start of thevalidation process, one of the reasons is that beforeMarshal is allowed tochange the validation rules and the saving options, such as the field whitelist.Validation is triggered just after this event is finished. A common example ofchanging the data before it is validated is trimming all fields before saving:

  1. // Include use statements at the top of your file.
  2. use Cake\Event\EventInterface;
  3. use ArrayObject;
  4.  
  5. // In a table or behavior class
  6. public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
  7. {
  8. foreach ($data as $key => $value) {
  9. if (is_string($value)) {
  10. $data[$key] = trim($value);
  11. }
  12. }
  13. }

Because of how the marshalling process works, if a field does not passvalidation it will automatically be removed from the data array and not becopied into the entity. This is to prevent inconsistent data from entering theentity object.

Moreover, the data in beforeMarshal is a copy of the passed data. This isbecause it is important to preserve the original user input, as it may be usedelsewhere.

Validating Data Before Building Entities

The Validating Data chapter has more information on how to use thevalidation features of CakePHP to ensure your data stays correct and consistent.

Avoiding Property Mass Assignment Attacks

When creating or merging entities from request data you need to be careful ofwhat you allow your users to change or add in the entities. For example, bysending an array in the request containing the user_id an attacker couldchange the owner of an article, causing undesirable effects:

  1. // Contains ['user_id' => 100, 'title' => 'Hacked!'];
  2. $data = $this->request->getData();
  3. $entity = $this->patchEntity($entity, $data);
  4. $this->save($entity);

There are two ways of protecting you against this problem. The first one is bysetting the default columns that can be safely set from a request using theMass Assignment feature in the entities.

The second way is by using the fieldList option when creating or mergingdata into an entity:

  1. // Contains ['user_id' => 100, 'title' => 'Hacked!'];
  2. $data = $this->request->getData();
  3.  
  4. // Only allow title to be changed
  5. $entity = $this->patchEntity($entity, $data, [
  6. 'fields' => ['title']
  7. ]);
  8. $this->save($entity);

You can also control which properties can be assigned for associations:

  1. // Only allow changing the title and tags
  2. // and the tag name is the only column that can be set
  3. $entity = $this->patchEntity($entity, $data, [
  4. 'fields' => ['title', 'tags'],
  5. 'associated' => ['Tags' => ['fieldList' => ['name']]]
  6. ]);
  7. $this->save($entity);

Using this feature is handy when you have many different functions your userscan access and you want to let your users edit different data based on theirprivileges.

Saving Entities

  • Cake\ORM\Table::save(Entity $entity, array $options = [])

When saving request data to your database you need to first hydrate a new entityusing newEntity() for passing into save(). For example:

  1. // In a controller
  2.  
  3. $articles = TableRegistry::getTableLocator()->get('Articles');
  4. $article = $articles->newEntity($this->request->getData());
  5. if ($articles->save($article)) {
  6. // ...
  7. }

The ORM uses the isNew() method on an entity to determine whether or not aninsert or update should be performed. If the isNew() method returns trueand the entity has a primary key value, an ‘exists’ query will be issued. The‘exists’ query can be suppressed by passing 'checkExisting' => false in the$options argument:

  1. $articles->save($article, ['checkExisting' => false]);

Once you’ve loaded some entities you’ll probably want to modify them and updateyour database. This is a pretty simple exercise in CakePHP:

  1. $articles = TableRegistry::getTableLocator()->get('Articles');
  2. $article = $articles->find('all')->where(['id' => 2])->first();
  3.  
  4. $article->title = 'My new title';
  5. $articles->save($article);

When saving, CakePHP will apply your rules, and wrapthe save operation in a database transaction. It will also only updateproperties that have changed. The above save() call would generate SQLlike:

  1. UPDATE articles SET title = 'My new title' WHERE id = 2;

If you had a new entity, the following SQL would be generated:

  1. INSERT INTO articles (title) VALUES ('My new title');

When an entity is saved a few things happen:

  • Rule checking will be started if not disabled.
  • Rule checking will trigger the Model.beforeRules event. If this event isstopped, the save operation will fail and return false.
  • Rules will be checked. If the entity is being created, the create ruleswill be used. If the entity is being updated, the update rules will beused.
  • The Model.afterRules event will be triggered.
  • The Model.beforeSave event is dispatched. If it is stopped, the save willbe aborted, and save() will return false.
  • Parent associations are saved. For example, any listed belongsToassociations will be saved.
  • The modified fields on the entity will be saved.
  • Child associations are saved. For example, any listed hasMany, hasOne, orbelongsToMany associations will be saved.
  • The Model.afterSave event will be dispatched.
  • The Model.afterSaveCommit event will be dispatched.The following diagram illustrates the above process:

Flow diagram showing the save process.

See the Applying Application Rules section for more information on creating andusing rules.

Warning

If no changes are made to the entity when it is saved, the callbacks willnot fire because no save is performed.

The save() method will return the modified entity on success, and falseon failure. You can disable rules and/or transactions using the$options argument for save:

  1. // In a controller or table method.
  2. $articles->save($article, ['checkRules' => false, 'atomic' => false]);

Saving Associations

When you are saving an entity, you can also elect to save some or all of theassociated entities. By default all first level entities will be saved. Forexample saving an Article, will also automatically update any dirty entitiesthat are directly related to articles table.

You can fine tune which associations are saved by using the associatedoption:

  1. // In a controller.
  2.  
  3. // Only save the comments association
  4. $articles->save($entity, ['associated' => ['Comments']]);

You can define save distant or deeply nested associations by using dot notation:

  1. // Save the company, the employees and related addresses for each of them.
  2. $companies->save($entity, ['associated' => ['Employees.Addresses']]);

Moreover, you can combine the dot notation for associations with the optionsarray:

  1. $companies->save($entity, [
  2. 'associated' => [
  3. 'Employees',
  4. 'Employees.Addresses'
  5. ]
  6. ]);

Your entities should be structured in the same way as they are when loaded fromthe database. See the form helper documentation for how to build inputsfor associations.

If you are building or modifying association data after building your entitiesyou will have to mark the association property as modified with setDirty():

  1. $company->author->name = 'Master Chef';
  2. $company->setDirty('author', true);

Saving BelongsTo Associations

When saving belongsTo associations, the ORM expects a single nested entity named withthe singular, underscored version of the association name. For example:

  1. // In a controller.
  2. $data = [
  3. 'title' => 'First Post',
  4. 'user' => [
  5. 'id' => 1,
  6. 'username' => 'mark'
  7. ]
  8. ];
  9.  
  10. $articles = TableRegistry::getTableLocator()->get('Articles');
  11. $article = $articles->newEntity($data, [
  12. 'associated' => ['Users']
  13. ]);
  14.  
  15. $articles->save($article);

Saving HasOne Associations

When saving hasOne associations, the ORM expects a single nested entity named with thesingular, underscored version of the association name. For example:

  1. // In a controller.
  2. $data = [
  3. 'id' => 1,
  4. 'username' => 'cakephp',
  5. 'profile' => [
  6. 'twitter' => '@cakephp'
  7. ]
  8. ];
  9.  
  10. $users = TableRegistry::getTableLocator()->get('Users');
  11. $user = $users->newEntity($data, [
  12. 'associated' => ['Profiles']
  13. ]);
  14. $users->save($user);

Saving HasMany Associations

When saving hasMany associations, the ORM expects an array of entities named with theplural, underscored version of the association name. For example:

  1. // In a controller.
  2. $data = [
  3. 'title' => 'First Post',
  4. 'comments' => [
  5. ['body' => 'Best post ever'],
  6. ['body' => 'I really like this.']
  7. ]
  8. ];
  9.  
  10. $articles = TableRegistry::getTableLocator()->get('Articles');
  11. $article = $articles->newEntity($data, [
  12. 'associated' => ['Comments']
  13. ]);
  14. $articles->save($article);

When saving hasMany associations, associated records will either be updated, orinserted. For the case that the record already has associated records in thedatabase, you have the choice between two saving strategies:

  • append
  • Associated records are updated in the database or, if not matching anyexisting record, inserted.
  • replace
  • Any existing records that do not match the records provided will be deletedfrom the database. Only provided records will remain (or be inserted).

By default the append saving strategy is used.See HasMany Associations for details on defining the saveStrategy.

Whenever you add new records to an existing association you should always markthe association property as ‘dirty’. This lets the ORM know that the associationproperty has to be persisted:

  1. $article->comments[] = $comment;
  2. $article->setDirty('comments', true);

Without the call to setDirty() the updated comments will not be saved.

If you are creating a new entity, and want to add existing records to a hasmany/belongs to many association you need to initialize the association propertyfirst:

  1. $article->comments = [];

Without initialization calling $article->comments[] = $comment; will have no effect.

Saving BelongsToMany Associations

When saving belongsToMany associations, the ORM expects an array of entities named withthe plural, underscored version of the association name. For example:

  1. // In a controller.
  2. $data = [
  3. 'title' => 'First Post',
  4. 'tags' => [
  5. ['tag' => 'CakePHP'],
  6. ['tag' => 'Framework']
  7. ]
  8. ];
  9.  
  10. $articles = TableRegistry::getTableLocator()->get('Articles');
  11. $article = $articles->newEntity($data, [
  12. 'associated' => ['Tags']
  13. ]);
  14. $articles->save($article);

When converting request data into entities, the newEntity() andnewEntities() methods will handle both arrays of properties, as well as alist of ids at the _ids key. Using the _ids key makes it easy to build aselect box or checkbox based form controls for belongs to many associations. Seethe Converting Request Data into Entities section for more information.

When saving belongsToMany associations, you have the choice between two savingstrategies:

  • append
  • Only new links will be created between each side of this association. Thisstrategy will not destroy existing links even though they may not be presentin the array of entities to be saved.
  • replace
  • When saving, existing links will be removed and new links will be created inthe junction table. If there are existing link in the database to some ofthe entities intended to be saved, those links will be updated, not deletedand then re-saved.

See BelongsToMany Associations for details on defining the saveStrategy.

By default the replace strategy is used. Whenever you add new records intoan existing association you should always mark the association property as‘dirty’. This lets the ORM know that the association property has to bepersisted:

  1. $article->tags[] = $tag;
  2. $article->setDirty('tags', true);

Without the call to setDirty() the updated tags will not be saved.

Often you’ll find yourself wanting to make an association between two existingentities, eg. a user coauthoring an article. This is done by using the methodlink(), like this:

  1. $article = $this->Articles->get($articleId);
  2. $user = $this->Users->get($userId);
  3.  
  4. $this->Articles->Users->link($article, [$user]);

When saving belongsToMany Associations, it can be relevant to save someadditional data to the junction Table. In the previous example of tags, it couldbe the vote_type of person who voted on that article. The vote_type canbe either upvote or downvote and is represented by a string. Therelation is between Users and Articles.

Saving that association, and the vote_type is done by first adding some datato _joinData and then saving the association with link(), example:

  1. $article = $this->Articles->get($articleId);
  2. $user = $this->Users->get($userId);
  3.  
  4. $user->_joinData = new Entity(['vote_type' => $voteType], ['markNew' => true]);
  5. $this->Articles->Users->link($article, [$user]);

Saving Additional Data to the Join Table

In some situations the table joining your BelongsToMany association, will haveadditional columns on it. CakePHP makes it simple to save properties into thesecolumns. Each entity in a belongsToMany association has a _joinData propertythat contains the additional columns on the junction table. This data can beeither an array or an Entity instance. For example if Students BelongsToManyCourses, we could have a junction table that looks like:

  1. id | student_id | course_id | days_attended | grade

When saving data you can populate the additional columns on the junction tableby setting data to the _joinData property:

  1. $student->courses[0]->_joinData->grade = 80.12;
  2. $student->courses[0]->_joinData->days_attended = 30;
  3.  
  4. $studentsTable->save($student);

The _joinData property can be either an entity, or an array of data if youare saving entities built from request data. When saving junction table datafrom request data your POST data should look like:

  1. $data = [
  2. 'first_name' => 'Sally',
  3. 'last_name' => 'Parker',
  4. 'courses' => [
  5. [
  6. 'id' => 10,
  7. '_joinData' => [
  8. 'grade' => 80.12,
  9. 'days_attended' => 30
  10. ]
  11. ],
  12. // Other courses.
  13. ]
  14. ];
  15. $student = $this->Students->newEntity($data, [
  16. 'associated' => ['Courses._joinData']
  17. ]);

See the Creating Inputs for Associated Data documentation for how to build inputs withFormHelper correctly.

Saving Complex Types

Tables are capable of storing data represented in basic types, like strings,integers, floats, booleans, etc. But It can also be extended to accept morecomplex types such as arrays or objects and serialize this data into simplertypes that can be saved in the database.

This functionality is achieved by using the custom types system. See theAdding Custom Types section to find out how to build customcolumn Types:

  1. use Cake\Database\Type;
  2.  
  3. Type::map('json', 'Cake\Database\Type\JsonType');
  4.  
  5. // In src/Model/Table/UsersTable.php
  6. use Cake\Database\Schema\TableSchema;
  7.  
  8. class UsersTable extends Table
  9. {
  10. protected function _initializeSchema(TableSchema $schema)
  11. {
  12. $schema->setColumnType('preferences', 'json');
  13.  
  14. return $schema;
  15. }
  16. }

The code above maps the preferences column to the json custom type.This means that when retrieving data for that column, it will be unserializedfrom a JSON string in the database and put into an entity as an array.

Likewise, when saved, the array will be transformed back into its JSONrepresentation:

  1. $user = new User([
  2. 'preferences' => [
  3. 'sports' => ['football', 'baseball'],
  4. 'books' => ['Mastering PHP', 'Hamlet']
  5. ]
  6. ]);
  7. $usersTable->save($user);

When using complex types it is important to validate that the data you arereceiving from the end user is the correct type. Failing to correctly handlecomplex data could result in malicious users being able to store data theywould not normally be able to.

Strict Saving

  • Cake\ORM\Table::saveOrFail($entity, $options = [])

Using this method will throw anCake\ORM\Exception\PersistenceFailedException if:

  • the application rules checks failed
  • the entity contains errors
  • the save was aborted by a callback.

Using this can be helpful when you performing complex databaseoperations without human monitoring, for example, inside a Shell task.

Note

If you use this method in a controller, be sure to catch thePersistenceFailedException that could be raised.

If you want to track down the entity that failed to save, you can use theCake\ORMException\PersistenceFailedException::getEntity() method:

  1. try {
  2. $table->saveOrFail($entity);
  3. } catch (\Cake\ORM\Exception\PersistenceFailedException $e) {
  4. echo $e->getEntity();
  5. }

As this internally perfoms a Cake\ORM\Table::save() call, allcorresponding save events will be triggered.

Find or Create an Entity

  • Cake\ORM\Table::findOrCreate($search, $callback = null, $options = [])

Find an existing record based on $search or create a new record using theproperties in $search and calling the optional $callback. This method isideal in scenarios where you need to reduce the chance of duplicate records:

  1. $record = $table->findOrCreate(
  2. ['email' => 'bobbi@example.com'],
  3. function ($entity) use ($otherData) {
  4. // Only called when a new record is created.
  5. $entity->name = $otherData['name'];
  6. }
  7. );

If your find conditions require custom order, associations or conditions, thenthe $search parameter can be a callable or Query object. If you usea callable, it should take a Query as its argument.

The returned entity will have been saved if it was a new record. The supportedoptions for this method are:

  • atomic Should the find and save operation be done inside a transaction.
  • defaults Set to false to not set $search properties into thecreated entity.

Saving Multiple Entities

  • Cake\ORM\Table::saveMany($entities, $options = [])

Using this method you can save multiple entities atomically. $entities canbe an array of entities created using newEntities() / patchEntities().$options can have the same options as accepted by save():

  1. $data = [
  2. [
  3. 'title' => 'First post',
  4. 'published' => 1
  5. ],
  6. [
  7. 'title' => 'Second post',
  8. 'published' => 1
  9. ],
  10. ];
  11.  
  12. $articles = TableRegistry::getTableLocator()->get('Articles');
  13. $entities = $articles->newEntities($data);
  14. $result = $articles->saveMany($entities);

The result will be updated entities on success or false on failure.

Bulk Updates

  • Cake\ORM\Table::updateAll($fields, $conditions)

There may be times when updating rows individually is not efficient ornecessary. In these cases it is more efficient to use a bulk-update to modifymany rows at once, by assigning the new field values, and conditions for the update:

  1. // Publish all the unpublished articles.
  2. function publishAllUnpublished()
  3. {
  4. $this->updateAll(
  5. [ // fields
  6. 'published' => true,
  7. 'publish_date' => FrozenTime::now()
  8. ],
  9. [ // conditions
  10. 'published' => false
  11. ]
  12. );
  13. }

If you need to do bulk updates and use SQL expressions, you will need to use anexpression object as updateAll() uses prepared statements under the hood:

  1. use Cake\Database\Expression\QueryExpression;
  2.  
  3. ...
  4.  
  5. function incrementCounters()
  6. {
  7. $expression = new QueryExpression('view_count = view_count + 1');
  8. $this->updateAll([$expression], ['published' => true]);
  9. }

A bulk-update will be considered successful if 1 or more rows are updated.

Warning

updateAll will not trigger beforeSave/afterSave events. If you need thosefirst load a collection of records and update them.

updateAll() is for convenience only. You can use this more flexibleinterface as well:

  1. // Publish all the unpublished articles.
  2. function publishAllUnpublished()
  3. {
  4. $this->query()
  5. ->update()
  6. ->set(['published' => true])
  7. ->where(['published' => false])
  8. ->execute();
  9. }

Also see: Updating Data.