Знакомство с Yii 2 на основе создания простого блога

Здравствуйте, уважаемые хабрапользователи!

Как вы уже поняли из заголовка, в данной статье пойдёт речь о новой версии Yii. Я попробую коротко, на живом примере, познакомить вас с замечательным Yii 2.

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

Основные моменты


В Yii2, как вы уже заметили, все построено на namespace, это, пожалуй, основная «изюминка» в новой версии.
Также очень важный момент: «C» префикс из имён файлов был удалён.

Установка и создание первого приложения


За основу мы будем использовать basic приложение.
Как её установить очень хорошо описано на Github по этому сразу же переходим к следующим пунктам.

Структура приложения


Для примера я выбрал модульную структуру. Это позволит нам узнать и понять принцип работы модулей в Yii2.
Итак, для того, чтобы реализовать такую структуру, нам, как и в первой версии, нужно создать основную папку всех модулей «modules», в которой мы уже будем создавать отдельные папки для отдельных модулей. Тут все, как в первой версии.

Единственное что поменялось в модулях – это название основного класса модуля. Как я понял, теперь уже не обязательно писать суффикс «Module», поэтому наши основные файлы будут иметь понятные имена без суффикса.

Пример:
Site.php, Users.php, Blogs.php, и.т.д.
Структуру вы сможете посмотреть уже на готовом примере, и останавливаться больше на этом не имеет смысла.

Настройка приложения: config/main.php


Как и в Yii 1.0, приложение настраивается через файла main.php, и многие элементы остались без изменений, но всё-таки есть много переименованных параметров, что следует помнить в случае неработоспособности приложения после конфигурации.

Несколько примеров таких изменений и особенностей:
  • Приркутка модулей теперь содержит обязательный параметр «class», который хранит имя основного класса модуля, и без которого подключить модуль не получится.
    'modules' => array(
    ...
    		'users' => array(
    			'class' => 'app\modules\users\Users'
    		),
    ...
    	),
    

  • Параметр «defaultController» убрали, и вместо него используется «defaultRoute».
  • При настройке путей к директориям, можно использовать заданные алиасы "@app, @www, @wwwroot", что очень удобно.
  • В UrlManager тоже переименовали несколько параметров. Теперь «urlFormat» заменили на «enablePrettyUrl», «useStrictParsing» на «enableStrictParsing».
  • В компоненте «db» переименовали «connectionString» на «dsn».

В случае неработоспособности каких-либо параметров, можно легко подсмотреть все в исходниках фрейма.
Как только мы настроили наше приложение и подключили наши модули, можем приступить к написанию нашего «CRUD».

CRUD


Подробный пример есть в демо коде, но, если честно, сильного отличия от первой версии нет. Единственное, что появилось новое, это ранее метод контроллера: populate(), который был перенесён на днях в модель и переименован в load().

Пример:
public function actionCreate()
	{
		$model = new Blog();
		if ($model->load($_POST) && $model->save()) {
				return Yii::$app->response->redirect(array('view', 'id' => $model->id));
		} else {
			echo $this->render('create', array('model' => $model));
		}
	}

$model->load($_POST)
// тоже самое что
if (isset($_POST['Blog'])) {
    $model->attributes = $_POST['Blog'];
}

Других особых различий в работе с CRUD нет.

Модель


Основным нововведением является метод «scenarios() за счёт которого можно настроить валидаторы модели в зависимости от указанного сценария.
public function scenarios()
{
    return array(
        'backend' => array('email', 'role'),
        'frontend' => array('email', '!name'),
    );
}

Привязка модели делается за счёт функции formName() которая в итоге возвращает имя Класса модели, к которой относится форма.
Также немаловажный момент — model() убрали, и теперь работа с моделью происходит, как и с другим обычным классом:
 MyModel::getAuthor();

ActiveRecord


Тут все переписали, теперь в Yii2 новый AR, который реально радует.

Примеры нескольких нововведений:
«scopes()» были заменены на обычные методы AR модели, которые сейчас имеют такой вид:
public function active($query)
	{
		return $query->andWhere('status = ' . self::STATUS_ACTIVE);
	}

Также радикально были изменены «relations()», которые сейчас задаются в виде гетерров, что более правильно. Доступны два типа relations: «hasOne()», «hasMany()».
public function getAuthor()
	{
		return $this->hasOne('app\modules\users\models\User', array('id' => 'author_id'));
                // Первый параметр – это у нас имя класса, с которым мы настраиваем связь.
               // Во втором параметре в виде массива задаётся имя удалённого PK ключа  (id) и FK из текущей таблицы модели (author_id), которые связываются между собой
	}

Также были переписаны функции выборки из базы, и дополнены новыми.
$customers = Customer::find()
    ->where(array('status' => $active))
    ->orderBy('id')
    ->all();
// return the customer whose PK is 1
$customer = Customer::find(1);
$customers = Customer::find(array('status'=>$active));
$customers = Customer::find()->asArray()->all();
$customers = Customer::find()->active()->asArray()->all();

И последнее, AR теперь делает автоматическую привязку модели к базе, за счёт функции «tableName()» которая по умолчанию возвращает такое значение «tbl_MODEL_NAME». Для примера, модель «User», будет привязана к таблице «tbl_user». Если же имя таблицы отличается, можно просто переопределить функцию.

События


В новой версии работать с событиями стало максимально просто. В демо приложении я привёл пример собственного события, — при добавлении нового пользователя, событие вызывается в afterSave(), хотя там можно использовать стандартные события, которые доступны в Yii, как например «EVENT_AFTER_INSERT».

Для того чтобы определить событие достаточно в нужном месте вызвать «trigger()» функцию, а уже потом в нужном месте задать для события обработчик.

Пример:
файл app\modules\users\models\User
...
public function afterSave($insert)
	{
               // Создаём событие
		$event = new ModelEvent;
		$this->trigger(self::EVENT_NEW_USER, $event);

		parent::afterSave($insert);
	}
...

файл app\modules\users\controllers\DefaultController
...
public function actionSignup()
	{
		$model = new User();
		$model->scenario = 'signup';
		if ($model->load($_POST)) {
			if (!$this->module->activeAfterRegistration)
                                // Задаём наш обработчик событий, для события [[EVENT_NEW_USER]]
				$model->on($model::EVENT_NEW_USER, array($this->module, 'onNewUser'));
			if ($model->save()) {
				Yii::$app->session->setFlash('success');
				return Yii::$app->response->refresh();
			}
		} else {
			echo $this->render('signup', array('model' => $model));
		}
	}
...

Есть несколько способов привязки обработчика:
function ($event) { ... }         // Анонимная функция
array($object, 'handleClick')    // $object->handleClick()
array('Page', 'handleClick')     // Page::handleClick()
'handleClick'                    // глобальная  функция handleClick()

View


В Yii2 появился новый класс, который отвечает за все представления приложения, и который выполняет непосредственно вывод информации.
Теперь в view файлах переменная "$this" относится уже не к контроллеру, а именно к новому классу «yii\base\View».
Для того, чтобы вызвать определённую функцию контроллера или виджета, к которому принадлежит представление, нужно обратится к методу: «context».

Пример:
// Файл app\modules\blogs\views\default\index
// $this->context относится к файлу app\modules\blogs\controllers\DefaultController
// Простой вызов параметра модуля, к которому относится контроллер из представления
echo $this->context->module->recordsPerPage; // Результат 10

//Файл app\modules\comments\widgets\comments\views\index
// $this->context относится к файлу app\modules\comments\widgets\comments\Comments
if ($this->context->model['id'] == 10 ) {...}

Widgets


Виджеты были дополнены новыми методами, плюс теперь они должны быть непосредственно выведены через «echo».

Пример:
// Note that you have to "echo" the result to display it
echo \yii\widgets\Menu::widget(array('items' => $items));

// Passing an array to initialize the object properties
$form = \yii\widgets\ActiveForm::begin(array(
    'options' => array('class' => 'form-horizontal'),
    'fieldConfig' => array('inputOptions' => array('class' => 'input-xlarge')),
));
... form inputs here ...
\yii\widgets\ActiveForm::end();

Action Filters


В новой версии фильтры контроллеров реализованы в виде «behaviors».
public function behaviors()
	{
		return array(
			'access' => array(
				'class' => \yii\web\AccessControl::className(),
				'rules' => array(
				    // allow authenticated users
					array(
						'allow' => true,
						'actions' => array('login', 'signup', 'activation'),
						'roles' => array('?')
					),
					array(
						'allow' => true,
						'actions' => array('logout'),
						'roles' => array('@')
					),
					array(
						'allow' => true,
						'actions' => array('index', 'view'),
						'roles' => array('guest')
					),
					array(
						'allow' => true,
						'actions' => array('edit', 'delete'),
						'roles' => array('@')
					),
					// deny all - можно не писать, он по умолчанию всё запрещает
					array(
						'allow' => false
					)
				)
			)
		);
	}

Меленький нюанс — из коробки остались только 2 роли:
  • @ — авторизованные
  • ? — гости
  • * — была удалена.

Static Helpers


В Yii2 добавлены много новых хелперов. Например, как тот же SecurityHelper, который ускоряет работу с паролями и генерированными кодами, ArrayHelper, Html, и другие, что снова радует.

ActiveForm


Формы теперь создаются ещё быстрее и удобнее, за счёт ActiveField класса, который упрощает стиль написания кода, что не может не радовать.
<?php $form = ActiveForm::begin(array('options' => array('class' => 'form-horizontal')));
echo $form->field($model, 'username')->textInput($model->isNewRecord ? array() : array('readonly' => true));
echo $form->field($model, 'email')->textInput();
if (!$model->isNewRecord) {
    if (Yii::$app->user->checkAccess('editProfile')) {
        echo $form->field($model, 'status')->dropDownList(array(
        	User::STATUS_ACTIVE => 'Active',
        	User::STATUS_INACTIVE => 'Inactive',
        	User::STATUS_DELETED => 'Deleted'
        ));
        echo $form->field($model, 'role')->dropDownList(array(
        	User::ROLE_USER => 'User',
        	User::ROLE_ADMIN => 'Admin'
        ));
    }
	echo $form->field($model, 'oldpassword')->passwordInput();
}
echo $form->field($model, 'password')->passwordInput();
echo $form->field($model, 'repassword')->passwordInput();
?>

<div class="form-actions">
	<?php echo Html::submitButton($model->isNewRecord ? 'Register' : 'Update', array('class' => 'btn btn-primary')); ?>
</div>

<?php ActiveForm::end(); ?>

User and Identity


Робота с пользователями теперь осуществляется через класс «yii\web\User» и интерфейс «yii\web\Identity», что более гибко в использовании.
Из-за этих изменений атрибуты пользователя можно получить через метод «identity» пользователя.

Пример:
echo Yii::$app->user->identity->username;

URL Management


Тут чуток видоизменили стили записи правил.

Пример:
...
array(
  'dashboard' => 'site/index',
 'PUT post/<id:\d+>' => 'post/update',
 'POST,PUT post/index' => 'post/create',
  'POST <controller:\w+>s' => '<controller>/create',
  '<controller:\w+>s' => '<controller>/index',
 'PUT <controller:\w+>/<id:\d+>'    => '<controller>/update',
 'DELETE <controller:\w+>/<id:\d+>' => '<controller>/delete',
 '<controller:\w+>/<id:\d+>'        => '<controller>/view',
);
...

RBAC


Работа с пользовательскими правами теперь доступна изначально из коробки без добавление своего кода, как, например, в случае с использованием файлового варианта в первой версии.
Ниже я напишу коротко инструкцию про RBAC и описание ролей в файле.

Для начала нам нужно создать свой класс, который будет наследоваться от "\yii\rbac\PhpManager".
В нашем примере он находится в модуле «rbac» в папке «components» под названием «PhpManager.php».
Код в нем простой. Мы просто задаём путь к нашему файлу с описанными ролями, и делаем привязку пользователя к нужной роли.
<?php 
namespace app\modules\rbac\components;

use Yii;

class PhpManager extends \yii\rbac\PhpManager
{
    public function init()
    {
        if ($this->authFile === NULL)
            $this->authFile = Yii::getAlias('@app/modules/rbac/components/rbac') . '.php';
 
        parent::init();
 
        if (!Yii::$app->user->isGuest)
            $this->assign(Yii::$app->user->identity->id, Yii::$app->user->identity->role);
    }
}

После, в той же папке мы создаём файл «rbac.php» где описываем нужные нам роли. (Код можно увидеть в демо приложении в папке: @app/modules/rbac/components/rbac)

Ну и, напоследок, нам осталось только настроить «authManager» в конфигурационном файле:
...
'authManager' => array(
			'class' => 'app\modules\rbac\components\PhpManager',
			'defaultRoles' => array('guest'),
		),
...

После чего в нужном нам месте мы можем смело делать проверки пользователя на наличие нужных прав:
if (Yii::$app->user->checkAccess('editOwnBlog', array('blog' => $model)) || Yii::$app->user->checkAccess('editBlog')) {
...
}

Это все что я успел узнать, но, уверен, ещё есть много чего не успелось.

На данный момент я не успел разобраться с ajax запросами в Yii 2, а именно – как правильно создавать нужный ответ при валидации модели. В 1.0 это делается так:
echo CActiveForm::validate($model);

если кто уже знает, пишите, уверен многим будет интересно.

Итог


Несмотря на ещё недоделанный вид, Yii 2 выглядит уже очень хорошо. Уверен, впереди нас ждёт только лучшее.

Самому Yii 2 очень понравился. Если честно, уже есть огромное желание начать писать рабочий проекты на новой версии, хотя до этого ещё очень рано.

Статья получилось большая, но по-другому просто не знаю как.

Спасибо всем за внимание. Удачи!

Демо блог на Github. Инструкция по установке присутствует.
Рабочий пример блога.

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 33

    +2
    Хорошая статья. Сценарии были и в первой версии Yii.
      +1
      Спасибо за статью, очень интересно. Жаль что релиза ещё долго ждать, и Макаров сам не рекомендовал использовать текущую версию Yii2 на продакшене из-за не стабильности и нерабочих кусков кода. Кстати на devconf2013 я у него спросил про LTS (Long Time Support), он обещал не забрасывать ветку первого Yii, и какие-то вещи туда дописывать, но в основном фиксы и заплатки. Остаётся только ждать :)
        0
        Скоро 1.1.14 релизнем. Один из самых внушительных релизов 1.1…
          +1
          Александр, вы заинтриговали! Ждем с нетерпеньем. Это мой первый фреймворк (CI в расчет не беру) и я в него влюбился. У нас в компании на нем все проекты сейчас реализовываются.
          А есть где посмотреть инфу о 14 релизе?
            +2
            > А есть где посмотреть инфу о 14 релизе?
            Читайте на здоровье!
              +1
              Спасибо большое! Плюсанул бы но карма маленькая пока, не дает :(
              0
              Присоединяюсь. Как и у вас, до Yii был только CI. Очень нравится Yii.
          0
          Хочу начать изучение Yii. Вопрос: есть ли смысл начинать писать на yii, если в будущем выйдет yii 2 и придется переходить на нее. Не получится что зря потрачу время, изучая yii? Или же базово все-таки 2-ая версия не будет отличаться от 1-ой?
          И такой вопрос: начинаю читать документацию. Смотрю есть даже пара тестовых проектов. А кто-нибудь знает существует ли для Yii какие-нибудь курсы (может быть даже платные), мастер-классы и т.д.? Может какие-то подробно разобранные проекты (тестовые проекты). Боязно соваться в изучение, т.к. есть страх, что окажется не под силу (никогда не использовал фреймворки и даже плохо себе представляю что это). Сейчас имею свой рукописный движок (без использования какого-либо фреймворка). В прицнипе рабочий, на нем сделал кучу сайтов и продолжаю делать, но хочу перейти к более высокому уровню и отойти уже от быдлокодерства. Но бросать его и начинать изучать yii, не зная к чему это приведет — боязно. На своем движке я могу выполнить какую угодно прихоть клиента, а вот смогу ли я это же делать на yii — не знаю. И главный вопрос — сколько времени уйдет на изучение? Как он вообще, сложен в освоении? Если бросить все на пару месяцев и заняться только им, реально начать писать?
            +2
            Господи, чего там изучать, какие курсы.
            Всё есть на сайте yii в прекрасном виде.
              +4
              Реально за 2 недели на нём начать писать. И потом оторваться не сможете.
                +1
                Я до этого работал только с цмсками и однажды с ci. Первый тестовый учебный проект делал по инструкции на сайте русского комьюнити. Сначала было очень тяжело, заняло пару часов так как в инструкции были косяки, не все части описаны, плюс разбирался в офф доках. Второй раз уже все как по маслу.

                По поводу разницы между 1.1 и 2. Лучше сейчас начать, так как концепцию понять можно на 1.1 версии. Зато потом легче будет осваивать вторую версию. У нас в компании решили пока сидеть на 1.1 и не спешить. Тем более версия 1.1 тоже восхитительна и реально быстра.

                Сейчас даже первый клиентский сайт буду делать на заказ именно на Yii, а не на битриксе, который я любил до появления Yii в моей жизни :)
                0
                Пара моментов:

                1. Правильно всё-таки не echo $this->render, а return $this->render, хотя работает и так и так.
                2. deny all можно не писать в фильтре доступа, он по умолчанию всё запрещает.
                  0
                  Спасибо Александр!
                  Поправил!
                    0
                    Присоединяюсь, использование echo в контроллере не хорошая практика. Совсем не хорошая. :)
                    0
                    в CRUD получается так, что валидация 2 раза запускается
                      0
                      Исправил. Спасибо!
                        0
                        Можно еще упростить до
                        $model = new Blog();
                        if ($model->load($_POST) && $model->save()) {
                            return Yii::$app->response->redirect(array('view', 'id' => $model->id));
                        } 
                        return $this->render('create', array('model' => $model));
                        
                          0
                          А потом до

                          $model = new Blog();
                          if ($model->load($_POST) && $model->save()) 
                              return Yii::$app->response->redirect(array('view', 'id' => $model->id));
                          return $this->render('create', array('model' => $model));
                          
                      0
                      Хоршая статья, хотя подучилось немного сумбурно.
                      Особенно меня радует в новой версии отдельные вьюшки. С отдельным классом удобней, что ли, стало.
                      Спасибо за статью. Добра вам.
                        0
                        Александр, какова была мотивация заменить точки на обратные слеши в псевдонимах классов?
                          0
                          Может потому, что теперь стали использовать пространства имен в ядре? Зачем городить точки, если есть обратные слеши? ;-)
                            0
                            HaruAtari верно предположил.
                            0
                            Хочу пример блога, где есть связь many-to-many, ибо AR больше не поддерживает, а такой тип связи никуда не исчез.
                              0
                              Все описанное понравилось кроме ActiveForm, уж слишком она громоздкая.
                              На мой взгляд было бы красивей
                              так
                              <?=$form = ActiveForm::begin($model, array('options' => array('class' => 'form-horizontal')));
                              echo $form->username->input($model->isNewRecord ? array() : array('readonly' => true));
                              echo $form->email->input();
                              if (!$model->isNewRecord) {
                                  if (Yii::$app->user->checkAccess('editProfile')) {
                                      echo $form->status->dropDown([
                                      	User::STATUS_ACTIVE => 'Active',
                                      	User::STATUS_INACTIVE => 'Inactive',
                                      	User::STATUS_DELETED => 'Deleted'
                                      ]);
                                      echo $form->role->dropDown([
                                      	User::ROLE_USER => 'User',
                                      	User::ROLE_ADMIN => 'Admin'
                                      ]);
                                  }
                                  echo Html::passwordInput(['name'=>'oldPassword']);
                              }
                              echo $form->password->passwordInput();
                              echo Html::passwordInput(['name'=>'rePassword']);
                              ?>
                              
                              <div class="form-actions">
                                  <?=$model->submitButton($model->isNewRecord ? 'Register' : 'Update', array('class' => 'btn btn-primary')); ?>
                              </div>
                              
                              <?=$form->end(); ?>
                              

                                0
                                А если модели две?
                                  0
                                  Ну тут я вижу 2 варианта
                                  1) Такое достаточно редко и можно этим пренебречь
                                  2) Можно использовать 2 объекта ActiveForm.
                                    0
                                    1) Это не так редко в проектах с сложной предметной областью.
                                    2) Нельзя. Форма-то одна.

                                      0
                                      Если поменять в методе field параметры местами, то можно в конструкторе биндить модель, а затем просто дёргать типа
                                      $form->field('username')->inputText();
                                      $form->field('group', $modelGroups)->inputSelect();

                                      $form->username — плохо, т.к. не будет автокомплита, да и IDE ругается на фейковые аттрибуты.

                                      Вам бы Yii::$app вынести в отдельный метод контроллера $this->app()->....., так и симпатичнее и легко мокать либо перегружать в наследниках
                                        0
                                        Довольно часто получается, что нет какой-то главной модели и в форме поля намешаны:

                                        model1Field1
                                        model2Field1
                                        model1Field2
                                        model3Field1
                                          0
                                          Ну так мой вариант не мешает работать с несколькими, просто есть основная модель и необязательный параметр. Всего-то нужно местами параметры поменять для удобства.

                                          Ну и если совсем уж теоретизировать, то модели всегда связанные и должна быть главная модель, а остальные просто браться от неё.
                                          $form->field('username');
                                          $form->field('Group.name'); // ооствественно тянется связанная модель 1-1 Group
                                          $form->field('Group.Permissions.0'); // Тянется 1-1 Grup и из неё 1-* Permissions
                                          $form->field('Group.Permissions.1');
                                          Но реализация такого варианта обычно порождает массу говнокода внутри, чтобы получить красивое апи снаружи. Так что лучше остановить на модели по умолчанию.
                                        0
                                        Обычно это все же связные модели, с которыми можно работать одновременно.
                                        Я даже не могу придумать варианте где на 1 форме могут быть поля из разных не связанных моделей.

                                        Можно тогда для более сложный форм использовать такой вариант
                                        Form::field($model, 'email')->textInput();
                                        
                                  0
                                  Ошибкаю Возможно после обновления фреймворка.
                                  modules\rbac\components\PhpManager.php – yii\rbac\PhpManager::assign()

                                  $this->assign(Yii::$app->user->identity->id, Yii::$app->user->identity->role);
                                    0
                                    Спасибо! Все исправил в новой версии приложения. Ссылки на неё в стаье подправил.

                                  Only users with full accounts can post comments. Log in, please.