The Cake Development Corporation team performs many code reviews. In fact, that is our starting point with every new client, as we offer free quick reviews. This is a good way to see where code stands, and how much work will need to be done to get it functioning properly.
One of the common errors we have found while doing Code Reviews of existing applications or just working with inherited code, it’s the way HasMany relations data is saved.
We have noticed that to save HasMany relations, some developers save the target relation, and then when the ID is retrieved after saving, they save one per one each item of the ‘many’ relation. There is no need to do this, as CakePHP can do all this in one single ‘save’! You won’t have any issue related to inconsistent data, because everything will be stored in one single transaction and your code will look much more clean.
Let’s see a quick and easy to follow example - We will have the following relations: ‘Users’ and one User could have many ‘Addresses’. We wish to save one user and this user will have two addresses.
First, you need to build the form in the proper way, the request data should follow the structure of your entities. The key in the form is the fieldName for the hasMany inputs. They must follow this format: {entityname}.{position}.{property}, for example: adddress.0.street_1
, adddress.0.street_2
, etc for the first item so store, for the second one: : adddress.1.street_1
, adddress.1.street_2
, and so on. More examples can be found here: https://book.cakephp.org/4/en/views/helpers/form.html#creating-inputs-for-associated-data.
<?= $this->Form->create($user) ?>
<fieldset>
<legend><?= __('Add User') ?></legend>
<?php
echo $this->Form->control('first_name');
echo $this->Form->control('last_name');
echo $this->Form->control('phone');
?>
<legend><?= __('Address 1') ?></legend>
<?php
echo $this->Form->control('addresses.0.street_1');
echo $this->Form->control('addresses.0.street_2');
echo $this->Form->control('addresses.0.zip');
echo $this->Form->control('addresses.0.city');
echo $this->Form->control('addresses.0.state');
?>
<legend><?= __('Address 2') ?></legend>
<?php
echo $this->Form->control('addresses.1.street_1');
echo $this->Form->control('addresses.1.street_2');
echo $this->Form->control('addresses.1.zip');
echo $this->Form->control('addresses.1.city');
echo $this->Form->control('addresses.1.state');
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
Now that we have the form, we need to convert the request data. The Table class provides an easy and efficient way to convert one or many entities from request data. It’s needed to define which associations should be marshalled, using associated
.
public function add()
{
$user = $this->Users->newEmptyEntity();
if ($this->request->is('post')) {
$user = $this->Users->patchEntity($user, $this->request->getData(), ['associated' => ['Addresses']]);
if ($this->Users->save($user)) {
$this->Flash->success(__('The user has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The user could not be saved. Please, try again.'));
}
$this->set(compact('user'));
}
In this example we are saving one user and two addresses for the given user.
Associated data is validated by default, If you wish to bypass data validation, pass the validate => false
option, for example: $this->Users->patchEntity($user, $this->request->getData(), ['associated' => ['Addresses' => [‘validate’ => false]]])
.
We are all about working smarter and in less time, so I hope this information will be useful! Take a look here for more information: https://book.cakephp.org/4/en/orm/saving-data.html#converting-request-data-into-entities
We will be posting more Common Errors while using CakePHP. Keep checking back!