Pull to refresh

Сохрание связанных моделей в Yii

Reading time 3 min
Views 11K
Я не так давно написал компонент, в котором реализовал сохранение связанных записей (CActiveRecord) и хотел бы поделиться этим кодом.

Я заметил, что часто пишется повторяющийся код, когда, например, нужно сохранить даные о клиенте со всеми его контактами, то пишется что-то типа такого (по крайней мере, я так писал):
   if ($client->save()) {
         foreach ($contacts as $contact) {
               $contact->clientId = $client->primaryKey;
               $contact->save();
         }
   }

Разумеется, этот код сопровождается валидацией и обработкой ошибок, а так же может быть заключен в трансакцию. Чего бы мне хотелось — так это сделать универсальный код для сохранения по разному связанных между собой моделей.

Например, необходимо сохранить такие данные с формы: нового клиента, у которого есть 1 адрес, и много контактных лиц; в то же время, клиент связан с создаваемым заказом и счетом (invoice); а заказ в свою очередь связан со счетом. В итоге, эти все модели можно сохранить так:
   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));
	}


Как видно, главной моделью является счет, с ним сохраняется клиент (user) и заказ. Вместе с заказом так же сохраняется клиент, а с клиентом список контактов и адрес.

Такое сохранение основано на том, что, когда мы сохраняем одну модель вместе с главной, мы должны сохранить в главной модели в поле внешнего ключа значение первичного ключа связанной модели, и поэтому третьим параметром в данном случае будет имя внешнего ключа в главной модели. В случае, когда мы сохраняем массив связанных моделей, то считаем, что третий параметр — это имя внешнего ключа в связанной модели.

Вторым параметром будут данные для одной модели или массив данных, эти данные запишутся в модель с помощью массового присваивания атрибутов:
$model->attributes = $_POST['Model'];

Единственным ограничением для второго параметра является необходимость соответсвия массива моделей массиву данных, то есть индексы массивов должны начинаться с 0 и не иметь пропусков. Возможно, подумав я смогу обойти это ограничение. Например, можно смотреть имеется ли во входных данных первичный ключ модели и загружать ее из базы, — в таком случае, не нужно будет предзагружать модели и следить за соответсвием.

Так же существует 4ый параметр, необязательный, который служит для валидации всего массива связанных моделей. Например, нам нужно проверить, чтобы сумма платежей, связанных со счетом, была равна сумме счета. Для этого создадим класс проверки, содержащий метод validate($models), принимающий список моделей и возвращающий true or false, в случае, если валидация прошла успешно или не прошла соответственно. Этот метод будет вызван для массива связанных моделей перед их сохранением.

Посмотреть на компонент, который реализован в виде CActiveRecordBehavior можно на Yii extensions

Будут интересны ваши мнения и другие варианты решения проблемы в комментах.
Tags:
Hubs:
+4
Comments 10
Comments Comments 10

Articles