Больше всего во второй версии Yii-framework мне понравилось то, как переделали ActiveRecord. Работать с ActiveRecord в Yii2 стало настолько удобно и приятно, что возвращаться на первую версию фреимворка совсем не хочется.
В Yii1 существовали следующие методы выборки строк из таблицы, к которой привязана модель:
Примерно та же картина с методами Update и Delete
Во втором Yii из кучи методов оставили только методы find() и findBySql($sql, $params). Основным методом теперь является метод find(), который, в отличии от первой версии, не возвращает строки, а возвращает объект ActiveQuery. Метод findBySql является оберткой над методом find и возвращает все тот же объект ActiveQuery. В первой версии, аналогом класса ActiveQuery был класс CDbCriteria.
В Yii2 количество возвращаемых строк регулируется методом ActiveQuery со звучными названиями all и one
// получаем одну запись из таблицы, к которой привязана модель Texts Texts::find()->one(); // или все Texts::find()->all();
Помимо этого появилась возможность работы с пакетами результатов.
foreach (Texts::find()->batch(100) as $text) { // обрабатываем по 100 записей в массиве $text }
Метод batch является методом объекта Query, от которого наследуется ActiveQuery.
Это важный функционал для работы с большим массивом данных, когда хранить в массиве php тысячи строк не позволяет ограничение памяти. Для первой версии фреимворка можно написать аналог
$n = 100; $all = Texts::model()->count(); $criteria=new CDbCriteria; for($i=0; $i<=$all; $i+=$n){ $criteria->limit = $i + $n; $criteria->offset = $i; $text = Texts::model()->findAll($criteria); // обрабатываем по 100 записей в массиве $text }
Уверена, решение, применяемое в методе batch эффективнее. Но по моему примеру легко понять смысл этого метода.
Чтобы построить более сложный запрос, чем просто получение объектов модели по условию или первичному ключу, в качестве параметра $condition в методы ActiveRecord Yii1 нужно передавать объект CDbCriteria
$criteria=new CDbCriteria; $criteria->with = 'service'; $criteria->addCondition("service.kid = ".$kID); $criteria->order = "date"; ServiceStep::model()->findByPk($step, $criteria);
В Yii2 нет объекта CDbCriteria. Вам не нужно отдельно создавать объект критериев и передавать его в объект ActiveRecord. Код стал намного читабельнее и понятней
Text::find() ->joinWith('autor') ->where(['status' => 1]) ->all();
Для объединения таблиц в Yii1 используются relations
public function relations() { return array( 'service' => array(self::HAS_ONE, 'Service', array('id' => 'service_id')), ); }
Доступные варианты отношений BELONGS_TO, HAS_ONE, HAS_MANY, MANY_MANY. При этом в связи можно указать в качестве не обязательных параметров условия для объединяющей таблицы, тип объединения, связь объединяющей таблицы с другими и тп.
public function relations() { return array( 'posts'=>array(self::HAS_MANY, 'Post', 'author_id', 'order'=>'posts.create_time DESC', 'with'=>'categories', 'through'=>'users', 'joinType'=>'INNER JOIN', 'on'=>'user.id = t.user_id and user.status=1', 'condition' => 't.status =1' ), ); }
Конечно в реальном проекте вам врятли понадобятся все эти параметры сразу, но в примере я объединила все что знала, чтобы показать какие существуют и как их применять 🙂
Далее применяем нужные связи в запросе:
//так $groups = Group::model()->with('service')->findAll(); //или так $criteria=new CDbCriteria; $criteria->with = 'service'; $groups = Group::model()->findAll($criteria);
Можно переопределить или добавить условия непосредственно при использовании объединения:
$groups = Group::model() ->with(array( 'service' => array( 'condition'=>'status = 1' )) )->findAll();
В Yii2 убрали из модели метод relations(). Связи таблиц объявляются через геттеры
public function getZodiak() { return $this->hasOne(ChinaZodiak::className(), ['id' => 'zodiak_id']); }
Из вариантов отношений оставили только hasOne и hasMany, что по-моему хорошо. Этих типов достаточно, чтобы описать все отношения в реляционных БД. И путаницы меньше. BELONGS_TO по сути это hasOne, а MANY_MANY это hasMany в обоих моделях по отношению друг к другу.
Оба метода (hasOne и hasMany) возвращают объект ActiveQuery, а значит к ним применимы все те же методы, что и при построение запроса в Yii2
public function getBigOrders($threshold = 100) { return $this->hasMany(Order::className(), ['customer_id' => 'id']) ->where('subtotal > :threshold', [':threshold' => $threshold]) ->orderBy('id'); }
Далее применяем нужные связи в запросе:
$result = ChinaZodiakGoroskop::find() ->joinWith('zodiak') ->all();
В метод joinWith класса ActiveQuery имеет следующее объявление:
joinWith($with, $eagerLoading = true, $joinType = ‘LEFT JOIN’)
Те первым параметром передает одну или массив связей. Второй параметр отвечает за «жадную загрузку» — те сразу ли выберутся записи связанной таблицы или только тогда, когда понадобятся. И третий — тип объединения, по умолчанию LEFT JOIN.
Yii2, по заявлениям разработчиков, полноценно использует возможности php5, в котором начиная с версии php5.3 появились анонимные функции (они же замыкания). Именно они используются для расширения связи непосредственно в запросе:
Order::find()->joinWith([ 'books' => function ($query) { $query->orderBy('name'); } ])->all();
Таким образом можно менять связь не посредственно при использовании в Yii2.
Не подскажите отличие joinWith и просто with? Я про последний пример с замыканием.
joinWith — не только объединяет таблицы, но и выбирает связанные записи из объединяемой таблицы (так же как при использовании with). Просто join только объединяет таблицы, не возвращая данные из объединенной таблицы.
По сути можно было бы использовать with, а в методе get<имя_связи> прописать правила. Но с помощью joinWith это можно делать более гибко под каждый случай.
PS: смотрите метод buildJoinWith в классе ActiveQuery
UDP: Поспешила и не правильно прочла вопрос. Исправляюсь:
Отличия joinWith от with: вот что написано в коде:
«This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations.»
Те если не менять значение по умолчанию для второго параметра, то joinWith — выполняется так же как и with. Если второй параметр передать как false, то это позволит создать запрос объединения, но не выполнять его.
Спасибо большое за пояснение.
Вот это:
$criteria=new CDbCriteria;
$criteria->with = ‘service’;
$criteria->addCondition(«service.kid = «.$kID);
$criteria->order = «date»;
ServiceStep::model()->findByPk($step, $criteria);
Очень легко можно заменить на вот это: (для справедливости я заменил имена на одинаковые, а то в Yii1 они были уж слишком длинные по сравнению с Yii2 ;))
Text::model()
->with(‘autor’)
->byStatus(1)
->findAll();
Или вот это:
Text::model()
->with(‘autor’)
->byStatus(1)
->findAllByAttributes([
‘status’ => 1,
]);
Что не сильно отличается от Yii2:
Text::find()
->joinWith(‘autor’)
->where([‘status’ => 1])
->all();
Подскажите пожалуйста. Раньше была возможность собрать критерии запросов отдельно. И это достаточно удобно, как это сделать сейчас? Меня интересует именно отдельно, т.к. необходимо организовать выборку из произвольной модели по произвольным условиям заданным от пользователя.
Упрощенный пример:
$className = ‘Users’;
$model = $className::find(); //в статичном варианте я бы написал продолжение этой строчки ->where(…..); Но мне нужен динамический вариант.
Кажется во втором yii без костылей это не сделать. Вариант костылей, который подсказали на форуме yii.
Не помню в какой версии появился этот CDataProviderIterator класс, но он позволяет в yii1 делать костыль batch()
не столь изящно, но все таки