Saving Linked Models in Yii

    Not so long ago I wrote a component in which I implemented the preservation of related records (CActiveRecord) and would like to share this code.

    I noticed that often repeated code is written, when, for example, you need to save data about the client with all its contacts, then 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 concluded in a transaction. What I would like to do is to make a universal code for saving differently interconnected models.

    For example, it is necessary to save such data from the form: a new client who has 1 address and many contact persons; at the same time, the client is associated with the created order and invoice; and the order, in turn, is associated with the invoice. As a result, these all models can be saved like this:
       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 the contact list and address.

    Such saving is based on the fact that when we save one model along with the main one, we must save the primary key value of the associated model in the foreign model field, and therefore the third parameter in this case will be the name of the foreign key in the main model. In the case when we store an array of related models, we consider that the third parameter is the name of the foreign key in the associated model.

    The second parameter will be data for one model or an array of data, this data will be written to the model using mass attribute assignment:
    $model->attributes = $_POST['Model'];
    

    The only limitation for the second parameter is the need for the model array to correspond to the data array, that is, the indexes of the arrays must start at 0 and not have gaps. Perhaps by thinking I can get around this limitation. For example, you can look at whether the primary key of the model is in the input data and load it from the database - in this case, you will not need to preload the models and monitor the correspondence.

    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 associated with the account is equal to the amount of the account. To do this, create a validation class containing the validate ($ models) method, which accepts a list of models and returns true or false if the validation was successful or failed, respectively. This method will be called on an array of related models before saving them.

    You can look at the component that is implemented as CActiveRecordBehavior on Yii extensions.

    Your opinions and other solutions to the problem in the comments will be interesting.

    Also popular now: