I recently wrote a component in which I implemented saving related records (CActiveRecord) and I would like to share this code.
I noticed that repeating code is often written, for example, when you need to save data about a client with all his contacts, something like this is written (at least, I wrote this):
if ($client->save()) { foreach ($contacts as $contact) { $contact->clientId = $client->primaryKey; $contact->save(); } }
Of course, this code is accompanied by validation and error handling, and can also be enclosed in a transaction. What I would like to do is make a universal code for storing differently related models.
For example, it is necessary to save such data from the form: a new customer who has 1 address and many contact persons; at the same time, the client is associated with the order and invoice being created (invoice); and the order in turn is associated with the invoice. As a result, all these models can be saved as follows:
public function actionCreate() { $order = new Order; $address = new Address; $user = new User; $contacts = array(new Contact); $invoice = new Invoice; if (isset($_POST['Submit'])) { $user->saveWith($address, $_POST['Address'], 'addressId'); $user->saveWith($contacts, $_POST['Contact'], 'userId'); $order->saveWith($user, $_POST['User'], 'userId'); $invoice->saveWith($order, $_POST['Order'], 'orderId'); $invoice->saveWith($user, $_POST['User'], 'userId'); $invoice->attributes = $_POST['Invoice']; if ($invoice->relationalSave()) { echo 'Saved'; } else { echo 'Not saved'; } } $this->render('create', array('order' => $order, 'user' => $user, 'invoice' => $invoice, 'address' => $address, 'contacts' => $contacts)); }
As you can see, the main model is the account, with it the client (user) and the order are saved. Together with the order, the client is also saved, and with the client a list of contacts and address.
')
This preservation is based on the fact that when we save one model together with the main one, we must save the primary key value of the associated model in the foreign key field in the main model, and therefore the third parameter in this case will be the foreign key name in the main model. In the case when we save an array of related models, we assume that the third parameter is the name of the foreign key in the associated model.
The second parameter will be the data for one model or an array of data, these data will be written into the model using the mass attribute assignment:
$model->attributes = $_POST['Model'];
The only limitation for the second parameter is the need to match the array of models to the data array, that is, array indices must start at 0 and have no gaps. Perhaps, thinking I can get around this limitation. For example, you can see if there is a primary key of the model in the input data and load it from the database - in this case, you will not need to preload the models and monitor the compliance.
There is also a fourth parameter, optional, which serves to validate the entire array of related models. For example, we need to check that the amount of payments related to the account is equal to the amount of the account. To do this, create a validation class that contains the validate ($ models) method, which accepts a list of models and returns true or false if the validation was successful or did not pass, respectively. This method will be called for an array of related models before storing them.
You can look at the component that is implemented as CActiveRecordBehavior on
Yii extensionsYour opinions and other solutions to the problem in the comments will be interesting.