![](http://habrastorage.org/getpro/habr/post_images/725/4e2/0af/7254e20afe3ec7fab7be38b495916b05.png)
На форуме русского сообщества, все больше вопросов и обсуждения.
Оказалось что у многих возникли трудности с работой моделью 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/
Добавить комментарий