Сравнение ActiveRecord в Yii2 и Yii1

17 сентября, 2014
Метки: ,

Больше всего во второй версии Yii-framework мне понравилось то, как переделали ActiveRecord. Работать с ActiveRecord в Yii2 стало настолько удобно и приятно, что возвращаться на первую версию фреимворка совсем не хочется.

Методы ActiveRecord в Yii1

В Yii1 существовали следующие методы выборки строк из таблицы, к которой привязана модель:

  • find($condition, $params) — для получения одной строки, удовлетворяющей условию $condition
  • findAll($condition,$params) — для получения всех строк, удовлетворяющих условию $condition
  • findByPk($pk,$condition,$params) — для поиска одной записи по первичному ключу
  • findAllByPk ($pk,$condition,$params) — для получения множества строк по первичному ключу (как и в метод findByPk параметр $pk можно передавать в виде массива первичных ключей)
  • findByAttributes($attributes,$condition,$params) — для получения одной строки в соответствии с параметром $attributes
  • findAllByAttributes($attributes,$condition,$params) — для получения множества строк в соответствии с параметром $attributes
  • findBySql ($sql,$params) — выбор одной строки по sql-запросу
  • findAllBySql ($sql,$params) — выбор всех строк по sql-запросу
  • И куча методов для подсчета количества строк:
    count($condition,$params)
    countByAttributes($attributes,$condition,$params)
    countBySql

Примерно та же картина с методами Update и Delete

Методы ActiveRecord в Yii2

Во втором 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 эффективнее. Но по моему примеру легко понять смысл этого метода.

Построение условий запроса Yii1

Чтобы построить более сложный запрос, чем просто получение объектов модели по условию или первичному ключу, в качестве параметра $condition в методы ActiveRecord Yii1 нужно передавать объект CDbCriteria

$criteria=new CDbCriteria;
$criteria->with = 'service';
$criteria->addCondition("service.kid = ".$kID);
$criteria->order = "date";

ServiceStep::model()->findByPk($step, $criteria);

Построение условий запроса Yii2

В Yii2 нет объекта CDbCriteria. Вам не нужно отдельно создавать объект критериев и передавать его в объект ActiveRecord. Код стал намного читабельнее и понятней

 Text::find()
      ->joinWith('autor')
      ->where(['status' => 1])
      ->all();

Связи таблиц в Yii1

Для объединения таблиц в 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

В 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.


Метки: ,

Оставить комментарий

7 комментариев »

    Alex

    Не подскажите отличие joinWith и просто with? Я про последний пример с замыканием.

      Developer

      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, то это позволит создать запрос объединения, но не выполнять его.


    Alex

    Спасибо большое за пояснение.


    Konstantin

    Вот это:
    $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(…..); Но мне нужен динамический вариант.

      Developer

      Кажется во втором yii без костылей это не сделать. Вариант костылей, который подсказали на форуме yii.

              $query = new Query();
              $query->where(['id'=>37]);
      
              $userQuery = new ActiveQuery(User::className(),get_object_vars($query));
              $userQuery->all();
       

    Sasha

    Не помню в какой версии появился этот CDataProviderIterator класс, но он позволяет в yii1 делать костыль batch()
    не столь изящно, но все таки


Оставить комментарий:









Копирование материалов разрешено при наличии активной ссылки на источник