Пользователь
0,0
рейтинг
6 мая 2013 в 20:25

Разработка → Yii2. Знакомство

Yii*, PHP*

Введение

На днях, свершилось событие, которое Я и думаю еще немало людей ждали. Авторы Yii Framework выкатили превью-версию.

Спустя день здесь на хабре появился обучающий материал, прочтение которого вызвали странные впечатления и после выходных потраченных на изучения кода Yii2, я решил написать свою версию. Надеюсь получится не хуже.

Начало

Вторая версия отличается от первой кардинально. Список в краткой форме:

— Отделили ядро от дополнений. Выбросили много классов. Часть из них перекочуют в отдельные, официально поддерживаемые, расширения. Часть просто убрана за ненадобностью.

— Базовый CComponent разделили на Object и Component. Первый осуществляет работу геттеров и сеттеров, второй расширяя первый, добавляя события и поведения.

— Видоизменилось подключение событий и поведений. Подписываемся на событие
$post->on('update', function($event) {
    // send email notification
});

Настраиваем компонент
$component = \Yii::createObject(array(
  'class' => '\app\components\GoogleMap',
  'apiKey' => 'xyz',
  // Добавим событие
  'on eventName' => array('Event', 'run'),
  // Добавим поведение
  'as behaviorName' => array(/* Behavior config */),
));

— Добавили новый класс View, теперь у нас настоящий MVC фреймворк. Представление
<?php
use yii\helpers\base\Html;
/**
 * Обратите внимание $this в представлении это уже не контроллер.
 * Добраться к контроллеру можно $this->context
 * @var yii\base\View $this
 */
$this->title = 'Hello world';
?>
<h1><?php echo Html::encode($this->title); ?></h1>
<p class="lead">Привет мир!</p>
* View можно для каждого контроллера устанавливать, или использовать базовый для приложения.

— render() контроллера больше ничего не выводит. Оно возвращает данные
public function actionIndex()
{
	echo $this->render('index');
}

— В контроллере появились два события, на котрые можно подписываться: beforeAction, afterAction
public function init()
{
  $this->on('beforeAction', function($event) {
    // отменяем действие
    $event->isValid = false;
  });
}

— Убраны фильтры контроллера CFilter, теперь все делается через поведения
public function behaviors()
{
  return array(
    'AccessControl' => array(
      'class' => '\yii\web\AccessControl',
      'rules' =>array(/* тут ничего не поменялось */),
    ),
  );
}

— В контроллере появился отличный помощник — метод populate
public function actionLogin()
{
  $model = new LoginForm();
  if ($this->populate($_POST, $model) && $model->login()) {
    Yii::$app->response->redirect(array('site/index'));
  }

  echo $this->render('login', array(
    'model' => $model,
  ));
}

— Добавлены еще несколько статических классов-хелперов: ArrayHelper, StringHelper, SecurityHelper. Все хелперы теперь можно перекрыть через LSB. Ура, воскликнул я, т.к лично мне не раз нужно было перекрыть Html.

— Виджет ActiveForm тоже переписан, и скорее всего заменит форм-билдер CForm. Каждое поле формы теперь может быть представлено как объект ActiveField, который создает ActiveForm
$form = $this->beginWidget('yii\widgets\ActiveForm', array(
  'options' => array('class' => 'form-horizontal')
));

  echo $form->field($model, 'username')->textInput(); 
  echo $form->field($model, 'password')->passwordInput(); 
  echo $form->field($model, 'rememberMe')->checkbox();

  echo Html::tag('div', Html::submitButton('Login', null, null, array('class' => 'btn btn-primary')), array(
    'class' => 'form-actions'
   ));

$this->endWidget();
* Внимание: в Html::tag($tag, $content, $options) — изменили порядок параметров!

ActiveRecord

«По большей части, ActiveRecord осталась нетронутой»
— написано в предыдущей статье. Верно подмечено — не трогали.
Просто взяли и написали совсем другой ActiveRecord.

— Забываем про model()

— Убран CDbCriteria. Но не пугайтесь, работа с базой стала от этого легче. Появился ActiveQuery, который представляет себя гибрид CActiveFinder и CDbCriteria.
// получаем экземпляр ActiveQuery
$query = Post::find();

// все посты
$posts = $query->all();

// ищем все посты с условием
$posts = $query
    ->where(array('status' => Post::DRAFT))
    ->orderBy('time')
    ->all();

// ищем один пост 
$post = $query
   ->where(array('id' => 10, 'status' => Post::READ))
   ->one();

// или проще, условие where можно передавать прямо в фабричный метод
$post = Post::find(array('id' => 10, 'status' => Post::READ));

// передав в фабричный метод не массив а число эквивалентно поиску по первичному ключу 
$post = Post::find(10)
   ->where(array('status' => Post::READ))
   ->one();

// индексируем результат по нужному атрибуту
$posts = $query->indexBy('title')->all();

// результат в виде массива
$posts = $query->asArray()->all();

— Все общие методы теперь статические: getDb, tableName, find*, saveAll*, primaryKey. Выигрыш очевиден.

— Связи, куда же без них. Теперь связи определяются добавлением геттеров
class Post extends ActiveRecord
{
    public function getCreator()
    {
        return $this->hasOne('User', array('id' => 'user_id'));
    }
    public function getComments()
    {
        return $this->hasMany('Comment', array('post_id' => 'id'));
    }
    public function getTrustComments($isTrust = true)
    {
        return $this->hasMany('Comment', array('post_id' => 'id'))
            ->where('status = :status', array(
                     ':status' => $isTrust ? self::TRUST : self::UNTRUST,
              ))
            ->orderBy('id');
    }
}

— Для удобства работы со связями добавили link() и unlink(), который автоматически расставят ключи
$post = Post::find(1);

$comment = new Comment();
$comment->text = 'Yii Framework is cool!';
$post->link('comments', $comment);

— Именованные группы условий есть, но в другом виде. У нас же нет больше CDbCriteria, а значит и массивов условий тоже больше нет. Теперь это методы, причем статические, добавляющие условия в Query
class Post extends \yii\db\ActiveRecord
{
    /**
     * @param ActiveQuery $query
     */
    public static function byCreator($query, $userId)
    {
        $query->andWhere('user_id = :userId', array('userId' => $userId));
    }
    /**
     * @param ActiveQuery $query
     */
    public static function removed($query)
    {
        $query->andWhere('removed = 1');
    }
}

$posts = Post::find()->removed()->all();
$myPosts = Post::find()->byCreator(Yii::$app->user->id)->all();


Все

На этом поставлю точку. Статья получилась большая, много кода, но надеюсь вы выдержали и дочитали.

До связи.
Veaceslav M. @slavcopost
карма
20,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (54)

  • +7
    Отличная статья, такие обзоры-апи и надо делать, меньше воды и больше нормального кода ;) Определение связей отлично сделано, вообще AR очень хорошей получилась. Кстати новая ActiveForm тоже отличная) Yii2 это rails on php :D
    P.S. slavcopost это slavcodev с гитхаба?)
    • +3
      Да это я.
    • +3
      rails on php это точно. это сейчас две мои любимые технологи: рельсы и yii. ребята, все на yii!))
      • 0
        Если не затруднит, можно пару слов о yii? На основном рускоязычном сайте не написано, в чем же все прелести yii. На Вики и то больше узнал.
        Можно от знатаков узнать, что больше всего нравится? Или даже не так… почему он так нравится?
        • +1
          Я знатоком Yii себя не считаю) Yii он целиком и полностью не такой как другие PHP фреймворки. Те кто юзают там Zend или Symfony обычно смотрят на Yii вот такими вот глазами O_o Мне нравится там все: и архитектура, и виртуальные пути ФС, и ORM (ActiveRecord), и очень чистая реализация MVC, то как там виджеты создаются, как легко подключаются всякие MemCache/CacheLite и т.д. Тут можно долго говорить, надо просто попробовать!)
          • +1
            и про gii/giix не забывайте! автоматически создаваемые relations по foreign keys — это просто бальзам на душу
  • +3
    Чем-то мне этот ActiveRecord напоминает Kohana ORM
    • –2
      А мне почему то нет:
      Yii
      $query->andWhere('user_id = :userId', array('userId' => $userId));
      

      Kohana
      $model->where('user_id', '=', $userId);
      

      у коханы приятнее и логичнее с составлением запросов на мой взгляд
  • +1
    Большое спасибо за обзор из первых рук! Столько изменений, что я его уже хочу. Чего стоят одни только переделки связей — коротко и по делу. Кода, правда, больше, но зато он понятный. Кстати, всегда интересовал вопрос — статические методы действительно настолько быстрее методов объектов, чтобы уходить от последних? Есть ли где-нибудь сравнение по ресурсам/скорости 1.13 и 2.0?
    • +2
      image
  • +8
    Всегда было интересно: кто минусует такие топики?
    Ведь и тема свежая, и много примеров с кодом
    • +7
      Дизайнеры, к примеру, которые ждут иконок…
      • 0
        Так есть специально заточенный хаб, как-то глупо ждать иконок в посте о PHP фреймворке. А если мозолит глаза — всегда можно воспользоваться настройками ленты
        • +4
          блин, ну тогда по ошибке мышка прыгнула.)
    • +2
      Очевидно хейтеры Yii.
  • +2
    Магия магией погоняет.
    Было засилье инстанцовых методов бизнес логики.
    Стало процедурно-статическое раздолье в моделях. Создал статические метод — вызываешь его инстацово, да еще и без поддержки ИДЕ.

    Почему разработчики так хотят отделить мух от котлет, но, как мне кажется, у них это получилось плохо?! Почему бы просто не разделить букву М в MVC на две составляющие — данные и сервисные методы: Post и PostQuery. В последний поместить все угодные методы byCreator, removed, piblished и тд.

    А в целом команда Yii молодцы :) очень много хороших, на мой взгляд, решений и улучшений.
    • 0
      Кстати такая картина в symfony, в первой части там была модель Post и сразу PostPeer.
      А во второй symfony репозитории для каждой модели можно создать сразу.
      • 0
        Это в propel. Там и сейчас так
    • +1
      Почему бы просто не разделить букву М в MVC на две составляющие — данные и сервисные методы: Post и PostQuery

      И сейчас не сложно их разделить, и будет автокомплит в ИДЕ работать ( подменить метод ActiveRecord::createQuery создать дополнительный класс, подменить возвращаемый тип у find и findBySql для ИДЕ ):
      ...
      /**
       * @method static \app\models\PostActiveQuery find( $q = null )
       * @method static \app\models\PostActiveQuery findBySql( $sql, $params = array() )
       */
      class Post extends \yii\db\ActiveRecord {
      ...
      	/**
      	 * @return PostActiveQuery
      	 */
      	public static function createQuery() 	{
      		return new PostActiveQuery( array(
      			'modelClass' => get_called_class(),
      		) );
      	}
      }
      
      class PostActiveQuery extends ActiveQuery {
      	/**
      	 * @param $title
      	 * @return PostActiveQuery
      	 */
      	public function byTitle( $title ) {
      		$this->andWhere( 'title = :title', array( 'title' => $title ) );
      		return $this;
      	}
      }
      
  • +2
    Можно поинтересоваться, почему выбрано так именовать во фреймворке неймспейсы? \yii\db\ActiveRecord вместо Yii\Db\ActiveRecord?
    • –5
      Нравится и при этом всё по PSR-0. Почему нет?
      • +7
        Это как-то выделяется от прочих: Doctrine, Zend, Symfony, Monolog, Composer, и проч. Если бы использовали CamelCase это смотрелось бы более органично.
  • +1
    А можете подсказать в новом ActiveRecord можно делать запросы вида:
    SELECT ..... FROM (SELECT .... FROM tbl2).....
    

    В текущем виде AR + CDbCriteria выполнить такой запрос у меня так и не получилось.

    P.S. Если вопрос глупый, то прошу прощения, просто начал использовать Yii не так давно и всех тонкостей ещё не знаю.
    • +4
      $posts = Post::findBySql('SELECT ..... FROM (SELECT .... FROM tbl2)')->all();
      
      • 0
        А есть для PHP ORM уровня SQLAlchemy? Что бы в таких случаях не надо было опускаться до SQL? (а то СУБД бывают разные и хотелось бы иметь гарантированную прослойку)
        • 0
          Если вы делаете не инсталлируемый продукт, то проблема надумана. А так, чувствую, можно проблему решить без подзапроса. С SQLAlchemy детально не знаком, уровень оценить не могу.
  • +2
    Очень много действительно полезных изменений, спасибо авторам за проделанный труд!
    Интересует прогноз команды разработчиков, сколько примерно времени понадобится до стабильной версии yii2?
    • 0
      Думаю, до конца года точно ещё будем делать.
  • 0
    Что с поддержкой бутстрапа? Вижу в форме его используют. Какой функционал еще предоставлен? Спасибо.
    • 0
      Пока больше никакого. А что ещё нужно?
      • +1
        Ну полной поддержки не нужно. Мне не нравится как сделано в yii-booster где есть виджет даже на Hero unit. Но в своих проектах часто использую виджет на менюшки (удобна поддержка разделителя и многоуровневость, ну и классы соответствующие). Также удобны табы, бредкрампс, пагинация. Было бы удобно если бы был аналог CGridView.
  • 0
    Благодарю за пост, очень понятно и полезно.

    Говорят в продакшн лучше пока не использовать, а есть ли какие сроки, когда можно будет более или менее начать его использовать?
    Сам пока плохо знаком с YII, но хочу попробовать на нем написать что нибудь. Хотелось бы конечно начать с YII2, поэтому интересуют примерные сроки стабильных версий.
    Как я понял с первой версией YII2 несовместим, получается кучу отличных расширений надо будет подождать пока перепишут под вторую версию?
    • 0
      Правильно говорят. Это ещё даже не альфа. К концу года, думаю. Хотя, это совсем-совсем примерно.

      С расширениями к релизу всё будет нормально.
  • +2
    Заголовок «Yii2. Знакомство (для знакомых с предметом)» лучше бы подошел. А то читает новичок, радуется, что нашел статью, где узнает с самого начала о предмете, заходит — а там его страшными словами в дрожь вгоняют, притом со знанием дела!
  • 0
    Вот увидел код:
    $query->andWhere('user_id = :userId', array('userId' => $userId));
    

    Ранее, как я помню, надо было вводить
    $query->andWhere('user_id = :userId', array(':userId' => $userId));
    

    Я ошибаюсь или это опечатка?
    • 0
      Ранее тоже можно было без двоеточия в AR.
    • 0
      С PDO тоже самое.
  • –2
    Рискну кармой, но всё же поинтересуюсь.
    Ну вот делаем мы, к примеру, некий веб-проект. Дизайн нарисовали и сверстали. Бизнес-логику, если там вообще есть бизнес-логика, написали — хоть на РНР, хоть на С++, хоть на хранимых процедурах SQL — не суть.
    Осталось прикрутить веб-морду по сверстанному дизайну.
    И вот тут я вижу два варианта.
    Если наш проект — это публичный сайт, то мы берем битрикс (джумлу, друпал, что угодно), специально обученный по выбранной CMS админ расставляет галочки в админке и вписывает названия полей в верстку -> через пару дней проект готов.
    Если у нас веб-приложение, то класс View на сервере уже входит в стандартную поставку PHP и называется json_encode. Нужно только добавить орм по вкусу и приправить тоником.
    А вот кому сейчас может потребоваться Yii (теперь с классом View!)...? Мне действительно интересно.
    • +3
      Тому кому и Pylons,Ruby on Rails, Django требуется… «специально обученный по выбранной CMS админ » это если ваш публичный сайт полностью укладывается в концепцию CMS.
      У вас идёт упор на СУБД, а вот есть проекты где основные разработки идут в области шаблонов и фронтенда и тут гораздо удобнее такого рода фреймворки.
    • 0
      Тривиальный backend можно в Yii сделать за один рабочий день, так зачем мне CMS на незначительную кастомизацию которой могут уйти недели? Проблема только в том что порог вхождения выше в Yii, и прогера на поддержку сложнее наверное найти если что. Хотя для простеньких контентых сайтов действительно CMS юзать сподручнее
  • 0
    Не являюсь поклонником Yii, потому не пинайте :) Какой смысл в методе populate()? Почему контроллер должен каким-то образом заполнять модель? Намного логичнее выглядит $model->populate($_POST).
    • 0
      Раньше (в Yii первом) так и было: $model->setAttributes($_POST['Model']). Тоже не очень понятно, в чём смысл переноса этого метода в контроллер.
      • 0
        Не берусь отвечать за авторов, с $model->setAttributes($_POST['Model']) была как минимум одна проблема, там в цикле весь массив $_POST['Model'] обходился, в итоге можно было получить кучу проблем.

        Ну и мне кажется что все таки данные от пользователся лучше в контроллере обработать а не в модели.
        • 0
          Вот как раз-таки модель и должна определять, какие поля можно установить из этого массива, а какие отбросить.
          • 0
            Так и было, она это определяла с помощью предоставления списка валидаторов на поля, а уже куда пихнуть цикл на обход $_POST полей по заданным валидаторам это уже дело вкуса.
  • +2
    1) А связи охранятся автоматичесик без link?
    $post = Post::find(1);
    
    $comment = new Comment();
    $comment->text = 'Yii Framework is cool!';
    $post->setComments(array($comment));
    $post->save();
    

    Сеттер для связей тоже нужно писать самому? Или всегда вызывать link?
    Не шибко удобно получается.

    2) Dirty attributes есть?

    3)
    $form = $this->beginWidget('yii\widgets\ActiveForm', array(
      'options' => array('class' => 'form-horizontal')
    ));
    
      echo $form->field($model, 'username')->textInput(); 
      echo $form->field($model, 'password')->passwordInput(); 
      echo $form->field($model, 'rememberMe')->checkbox();
    
    

    тут можно передать модель сразу в форму? Чтобы не писать $model в каждом тэге.

    4) В чем смысл populate, если safe attributes хранятся в модели?

    5) Можно ли теперь делать так:
    $post->getComments()->approved()->all();// approved - scope класса Comment
    


    6) Зачем 'echo render;' И вообще явный рендер?
    Проще же делать как в зенде, рельсах и т.д. Рендеринг одноименного вью автоматически, а если нужно рендерить что-то другое, то уже вызываем явно.
    $this->view->var = 'value'; //zend
    $this->var = 'value'; // a-la rails. Переменная var доступна во вью.
    


    7) Yii::$app->response->redirect(array('site/index')); — зачем использовать статику? У контроллера же может быть поле request?
    $this->request->redirect(...);
    

    Думаю и для тестов так лучше.
    • 0
      1) Пока нет. creocoder работает над этим.
      2) Да.
      3) Пока нет. Синтаксис для виджетов ещё будет меняться.
      4) Это черновой вариант. Он нам пока не нравится.
      5) Да. Если нет — это баг.
      6) Неявность в этом случае совсем не нравится. Это одна строчка в контроллере, из который сразу можно понять, какой view рендерится и какие переменные в нём доступны.
      7) Может. Подумаем.
  • 0
    Спасибо за ответ. Насчет populate. В рельсах сейчас практикуется такой гем -https://github.com/rails/strong_parameters. Может заинтересует сам подход? Вообще, хорошая идея вынести safe attributes из модели.
    Не в ту ветку отправил… это для samdark
    • 0
      У нас нет такой проблемы изначально, чтобы её решать. По умолчанию массовое присваивание не работает. Нужно как раз сделать whitelist атрибутов в scenarios и rules.
      • 0
        Проблема есть, то что в рельсах называется attr_acсessible, в yii — rules. Идея в том, чтобы вынести эти rules из модели. Модель не должна знать о сценариях ее использования и белом списке. Тогда populate пригодится. Он отсеит то, что мы запретим, в контроллере. При использовании strong_parameters нам просто запрещают присваивать что-либо иное, кроме отфильтрованных данных. Модель становится чище, а безопасность не теряется.
        • 0
          Тут, по-моему, дело вкуса. По мне так приятней определить N сценариев использования в самой модели, а в контроллере указывать только имя сценария.
          • 0
            Мы используем giix для создания моделей, а именно двух классов. Первый из них — базовая «чистая» модель, второй — добавление или переопределение правил, поведений, методов для «жирных» моделей. Удобно и отделяются мухи от котлет.
  • 0
    Спасибо за статью!

    Такой вопрос: Будет ли в Yii2 RESTful Server из коробки?
    • 0
      Планируется.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.