Yii2. Связи Active Record
Yii2 еще только в бета-тестировани, но, видимо, это никого не пугает. Кто-то просто изучает новые возможности, но многие начали использовать его даже в продакшене и под дулом пистолета отказываются даже смотреть на код первой версии.На форуме русского сообщества всё больше вопросов и обсуждения. Оказалось что у многих возникли трудности с ActiveRecord и связанными данными.
Я решил написать свой рецепт. Возможно, вы посчитаете его полезным, возможно поймёте как делать не надо. В любом случае, надеюсь, материал будет полезен.
Что нам предлагает фреймворк для работы со связями? Связи в новой версии фреймворка объявляются при помощи геттеров: public function getCategory () { return $this→hasOne (Category: className (), ['id' => 'category_id']); } Геттер возвращает ActiveQuery, который можно дополнительно настроить перед загрузкой связанной модели: $posts = Category: find ($id)→getPosts ()→limit (5)→order ('created_at')→all (); Замечание: Когда Вы используете магию $post→category, вместо геттера, помните, так вы получаете результат запроса Query-объекта.Другими словами $post→category === $post→getCategory ()→one ()
Методы работы со связями populateRelation ($relationName, $relatedModelOrArray) -добавляет связанную модель в родительскую. Замечание: Этот метод не проверяет, объявлена ли связь между этими моделями (геттер), а также не устанавливает нужные значения в атрибуты.
$post = new Post (); $post→populateRelation ('category', new Category ()); $post→populateRelation ('tags', [new Tag (), new Tag ()]); link ($relationName, relatedModel, $extraColumns = []) — в отличии от populateRelation, этот метод, кроме добавления связанной модели, также привязывает модели, расставляя нужные индексы. Сразу он сохраняет ТОЛЬКО связанную модель. $extraColumns сохранятся в pivot table, если связь осуществляется через неё. $post = new Post (); $post→link ('category', new Category ()); $post→link ('tags', new Tag ()); $post→link ('tags', new Tag ()); Вам, возможно, захочется сохранять модели вместе со связями в одной транзакции. Для этого в Yii2 есть встроенные средства: public function transactions () { return [ // scenario name => operation (insert, update or delete) self: SCENARIO_DEFAULT => self: OP_INSERT | self: OP_UPDATE, ]; } Это лишь некоторые методы. Остальные Вы найдёте в официальной документации.Пример Теперь я хочу показать как их можно использовать на примере.Модель class Post extends ActiveRecord { // Будем использовать транзакции при указанных сценариях public function transactions () { return [ self: SCENARIO_DEFAULT => self: OP_INSERT | self: OP_UPDATE, ]; }
public function getTags () { return $this→hasMany (Tag: className (), ['id' => 'tag_id']) →viaTable ('post_tag', ['post_id' => 'id']); }
// Я предлагаю использовать сеттеры для связей, // хотя это дополнительное телодвижение, //, но совсем не сложно писать сразу рядом с геттером. // Зато очень удобно, т.к. сразу можно делать дополнительные // изменения модели public function setTags ($tags) { $this→populateRelation ('tags', $tags); $this→tags_count = count ($tags); }
// Сеттер для получения тегов из строки, разделенных запятой public function setTagsString ($value) { $tags = []; foreach (explode (',' $value) as $name) { $tag = new Tag (); $tag→name = $name; $tag[] = $tag; } $this→setTags ($tags); }
public function getCover () { return $this→hasOne (Image: className (), ['id' => 'cover_id']); }
public function setCover ($cover) { $this→populateRelation ('cover', $cover); }
public function getImages () { return $this→hasMany (Image: className (), ['post_id' => 'id']); }
public function setImages ($images) { $this→populateRelation ('images', $images);
if (!$this→isRelationPopulated ('cover') && !$this→getCover ()→one ()) { $this→setCover (reset ($images)); } }
public function loadUploadedImage () { $images = [];
foreach (UploadedFile: getInstances (new Image (), 'image') as $file) { $image = new Image (); $image→name = $file→name; $images[] = $image; }
$this→setImages ($images); }
public function beforeSave ($insert) { if (! parent: beforeSave ($insert)) { return false; }
// В beforeSave мы сохраняем связанные модели // которые нужно сохранить до основной, т.е. нужны их ИД // Не волнуйтесь о транзакции т.к. мы настроили, // она будет начата при вызове метода `insert ()` и `update ()`
// Получаем все связанные модели, те что загружены или установлены $relatedRecords = $this→getRelatedRecords ();
if (isset ($relatedRecords['cover'])) { $this→link ('cover', $relatedRecords['cover']); } return true; }
public function afterSave ($insert) {
// В afterSave мы сохраняем связанные модели // которые нужно сохранять после основной модели, т.к. нужен ее ИД
// Получаем все связанные модели, те что загружены или установлены $relatedRecords = $this→getRelatedRecords ();
if (isset ($relatedRecords['tags'])) { foreach ($relatedRecords['tags'] as $tag) { $this→link ('tags', $tag); } } if (isset ($relatedRecords['images'])) { foreach ($relatedRecords['images'] as $image) { $this→link ('images', $image); } } } } Контролер class PostController extends Controller { public function actionCreate () { $post = new Post ();
if ($post→load (Yii::$app→request→post ())) { // Сохраняем загруженные файлы $this→loadUploadedImages ();
if ($post→save ()) { return $this→redirect (['view', 'id' => $post→id]); } } return $this→render ('create', [ 'post' => $post, ]); } } Вместо заключения Если вы знаете что такое Yii Framework, живете в Кишиневе (Молдова) или поблизости, присоединяйтесь к нам! Мы хотим собраться в оффлайне.Подробности здесь! Ждем всех!