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,      self::SCENARIO_UPDATE => self::OP_INSERT,   ]; } 

Это лишь некоторые методы, остальные Вы найдете в официальной документации.

Пример

Теперь я хочу показать как их можно использовать на примере

Модель

class Post extends ActiveRecord {     // Будем использовать транзакции при указанных сценариях     public function transactions()     {         return [             self::SCENARIO_INSERT => self::OP_INSERT,             self::SCENARIO_UPDATE => 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();         // Устанавливаем нужный сценарий,         // например чтоб запустить транзакцию при сохранении         $post->setScenario(Post::SCENARIO_INSERT);          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, живете в Кишиневе (Молдова) или поблизости, присоединяйтесь к нам! Мы хотим собраться в оффлайне.
Подробности здесь!

Ждем всех!

ссылка на оригинал статьи http://habrahabr.ru/post/226103/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *