Мультиязычные деревья в Yii2 на примере создания модуля меню

от автора

Вступление

Многие начинающие веб-разработчики сталкиваются с необходимостью создания меню, каталогов или рубрикаторов для своего проекта на Yii2, которые бы имели иерархическую структуру, но при этом поддерживали мультиязычность. Задача довольно простая, но не совсем очевидная в рамках данного фреймворка. Есть большое количество готовых расширений для создания древовидных структур (меню, каталогов итд.), но довольно сложно найти решение, которое бы поддерживало полноценную работу с несколькими языками. Причём речь тут идёт не о переводе интерфейса штатными средствами фреймворка, а про хранение данных в базе на нескольких языках. Также достаточно сложно найти удобный и полностью работоспособный виджет для управления деревом, который мог бы также работать с многоязычным контентом без сложных манипуляций с кодом.

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

Подготовка

Для реализации задачи нам понадобится несколько замечательных расширений, а именно:

  • Adjacency List — для хранения древовидной структуры
    меню в БД;
  • Yii2 Bootstrap Treeview — виджет
    для удобного отображения меню в виде дерева;
  • Translateable Behavior — поведение для
    поддержки мультиязычности в моделях;

Устанавливаем данные расширения через composer:

composer require paulzi/yii2-adjacency-list composer require execut/yii2-widget-bootstraptreeview composer require creocoder/yii2-translateable

Для реализации меню в виде модуля, с помощью Gii генератора (либо вручную) создаём новый модуль menu и подключаем его в настройках приложения.

В проекте также должен быть настроен механизм переключения языков. Я предпочитаю использовать вот это расширение для Yii2.

Создание моделей

Для хранения меню (либо другой сущности, которая имеет мультиязычность) в базе данных нам необходимо создать две таблицы. На самом деле, для хранения мультиязычных данных могут использоваться разные методики, но вариант с двумя таблицами, одна из которых хранит саму сущность, а вторая — её языковые вариации, мне нравится больше остальных. Для создания таблиц удобно использовать миграции. Вот пример такой миграции:

m180819_083502_menu_init.php

<?php  use yii\db\Schema; use yii\db\Migration;  class m180819_083502_menu_init extends Migration {      public function init()     {         $this->db = 'db';         parent::init();     }      public function safeUp()     {         $tableOptions = 'ENGINE=InnoDB';         $this->createTable('{{%menu}}', [             'id'=> $this->primaryKey(11),             'parent_id'=> $this->integer(11)->null()->defaultValue(null),             'link'=> $this->string(255)->notNull()->defaultValue('#'),             'link_attributes'=> $this->text()->notNull(),             'icon_class'=> $this->string(255)->notNull(),             'sort'=> $this->integer(11)->notNull()->defaultValue(0),             'status'=> $this->tinyInteger(1)->notNull()->defaultValue(1),         ], $tableOptions);         $this->createIndex('parent_sort', '{{%menu}}', ['parent_id','sort'], false);         $this->createTable('{{%menu_lang}}', [             'owner_id'=> $this->integer(11)->notNull(),             'language'=> $this->string(2)->notNull(),             'name'=> $this->string(255)->notNull(),             'title'=> $this->text()->notNull(),         ], $tableOptions);         $this->addPrimaryKey('pk_on_menu_lang', '{{%menu_lang}}', ['owner_id','language']);         $this->addForeignKey(             'fk_menu_lang_owner_id',             '{{%menu_lang}}',             'owner_id',             '{{%menu}}',             'id',             'CASCADE',             'CASCADE'         );         // Insert sample data         $this->batchInsert(             '{{%menu}}',             ['id', 'parent_id', 'link', 'link_attributes', 'icon_class', 'sort', 'status'],             [                 [                     'id' => '1',                     'parent_id' => null,                     'link' => '#',                     'link_attributes' => '',                     'icon_class' => '',                     'sort' => '0',                     'status' => '0',                 ],                 [                     'id' => '2',                     'parent_id' => '1',                     'link' => '/',                     'link_attributes' => '',                     'icon_class' => 'fa fa-home',                     'sort' => '0',                     'status' => '1',                 ],             ]         );         $this->batchInsert(             '{{%menu_lang}}',             ['owner_id', 'language', 'name', 'title'],             [                 [                     'owner_id' => '1',                     'language' => 'ru',                     'name' => 'Главное меню',                     'title' => '',                 ],                 [                     'owner_id' => '1',                     'language' => 'en',                     'name' => 'Main menu',                     'title' => '',                 ],                 [                     'owner_id' => '2',                     'language' => 'ru',                     'name' => 'Главная',                     'title' => 'Главная страница сайта',                 ],                 [                     'owner_id' => '2',                     'language' => 'en',                     'name' => 'Home',                     'title' => 'Site homepage',                 ],             ]         );     }      public function safeDown()     {         $this->truncateTable('{{%menu}} CASCADE');         $this->dropForeignKey('fk_menu_lang_owner_id', '{{%menu_lang}}');         $this->dropTable('{{%menu}}');         $this->dropPrimaryKey('pk_on_menu_lang', '{{%menu_lang}}');         $this->dropTable('{{%menu_lang}}');     } }

Поместим данный файл миграции в папку /migrations нашего проекта, и
выполним в консоли команду:

php yii migrate

После того, как мы создали необходимые таблицы и добавили в них новое меню с помощью миграции, нам нужно создать модели. Так как в проекте мультиязычность и деревья могут встречаться не только в меню, но и в других сущностях (например, страницы сайта), я предлагаю вынести методы, которые реализуют механизм мультиязычности и организацию дерева, в отдельные трейты, чтобы в дальнейшем мы могли легко использовать их в других моделях без дублирования кода. Создадим в корне приложения папочку traits (если её там ещё нет) и поместим туда два файла:

LangTrait.php

<?php  namespace app\traits;  use Yii; use yii\behaviors\SluggableBehavior; use creocoder\translateable\TranslateableBehavior;  trait LangTrait {     public static function langClass()     {         return self::class . 'Lang';     }      public static function langTableName()     {         return self::tableName() . '_lang';     }      public function langBehaviors($translationAttributes)     {         return [             'translateable' => [                 'class' => TranslateableBehavior::class,                 'translationAttributes' => $translationAttributes,                 'translationRelation' => 'translations',                 'translationLanguageAttribute' => 'language',             ],         ];     }      public function transactions()     {         return [             self::SCENARIO_DEFAULT => self::OP_INSERT | self::OP_UPDATE,         ];     }      public function getLang()     {         return $this->hasOne(self::langClass(), ['owner_id' => 'id'])->where([self::langTableName() . '.language' => Yii::$app->language]);     }      public function getTranslations()     {         return $this->hasMany(self::langClass(), ['owner_id' => 'id']);     }  }

TreeTrait.php

<?php  namespace app\traits;  use Yii; use yii\helpers\Html; use paulzi\adjacencyList\AdjacencyListBehavior;  trait TreeTrait {      private static function getQueryClass()     {         return self::class . 'Query';     }      public function treeBehaviors()     {         return [             'tree' => [                 'class' => AdjacencyListBehavior::class,                 'parentAttribute' => 'parent_id',                 'sortable' => [                     'step' => 10,                 ],                 'checkLoop' => false,                 'parentsJoinLevels' => 5,                 'childrenJoinLevels' => 5,             ],         ];     }      public static function find()     {         $queryClass = self::getQueryClass();         return new $queryClass(get_called_class());     }      public static function listTree($node = null, $level = 1, $nameAttribute = 'name', $prefix = '-->')     {         $result = [];         if (!$node) {             $node = self::find()->roots()->one()->populateTree();         }         if ($node->isRoot()) {             $result[$node['id']] = mb_strtoupper($node[$nameAttribute ?: 'slug']);         }         if ($node['children']) {             foreach ($node['children'] as $child) {                 $result[$child['id']] = str_repeat($prefix, $level) . $child[$nameAttribute];                 $result = $result + self::listTree($child, $level + 1, $nameAttribute);             }         }         return $result;     }      public static function treeViewData($node = null)     {         if ($node === null) {             $node = self::find()->roots()->one()->populateTree();         }         $result = null;         $items = null;         $children = null;         if ($node['children']) {             foreach ($node['children'] as $child) {                 $items[] = self::treeViewData($child);             }             $children = call_user_func_array('array_merge', $items);         }         $result[] = [             'text' => Html::a($node['lang']['name'] ?: $node['id'], ['update', 'id' => $node['id']], ['title' => Yii::t('app', 'Редактировать элемент')]),             'tags' => [                 Html::a(                     '<i class="glyphicon glyphicon-arrow-down"></i>',                     ['move-down', 'id' => $node['id']],                     ['title' => Yii::t('app', 'Передвинуть вниз')]                 ),                 Html::a(                     '<i class="glyphicon glyphicon-arrow-up"></i>',                     ['move-up', 'id' => $node['id']],                     ['title' => Yii::t('app', 'Передвинуть вверх')]                 )             ],             'backColor' => $node['status'] == 0 ? '#ccc' : '#fff',             'selectable' => false,             'nodes' => $children,         ];         return $result;     } }

Теперь создадим непосредственно сами модели для работы с меню, в которых подключим трейты для дерева и мультиязычности. Модели помещаем в /modules/menu/models:

Menu.php

<?php  namespace app\modules\menu\models;  use Yii;  class Menu extends \yii\db\ActiveRecord {     use \app\traits\TreeTrait;     use \app\traits\LangTrait;      const STATUS_ACTIVE = 1;     const STATUS_INACTIVE = 0;      public function behaviors()     {         $behaviors = [];         return array_merge(             $behaviors,             $this->treeBehaviors(),             $this->langBehaviors(['name', 'title'])         );     }      public static function tableName()     {         return 'menu';     }      public function rules()     {         return [             [['parent_id', 'sort', 'status'], 'integer'],             [['link', 'icon_class'], 'string', 'max' => 255],             [['link_attributes'], 'string'],             [['link'], 'default', 'value' => '#'],             [['link_attributes', 'icon_class'], 'default', 'value' => ''],             [['parent_id'], 'exist', 'skipOnError' => true, 'targetClass' => self::class, 'targetAttribute' => ['parent_id' => 'id']],         ];     }      public function attributeLabels()     {         return [             'id' => Yii::t('app', 'ID'),             'parent_id' => Yii::t('app', 'Родитель'),             'link' => Yii::t('app', 'Ссылка'),             'link_attributes' => Yii::t('app', 'Атрибуты ссылки (JSON массив)'),             'icon_class' => Yii::t('app', 'Класс иконки'),             'sort' => Yii::t('app', 'Сортировка'),             'status' => Yii::t('app', 'Опубликован'),         ];     }      public static function menuItems($node = null)     {         if ($node === null) {             $node = self::find()->roots()->one()->populateTree();         }         $result = null;         $items = null;         $children = null;         if ($node['children']) {             foreach ($node['children'] as $child) {                 $items[] = self::menuItems($child);             }             $children = call_user_func_array('array_merge', $items);         }         $result[] = [             'label' => ($node['icon_class'] ? '<i class="' . $node['icon_class'] . '"></i> ' . ($node['lang']['name'] ?: $node['id']) : ($node['lang']['name'] ?: $node['id'] )),             'encode' => ($node['icon_class'] ? false : true),             'url' => [$node['link'], 'language' => Yii::$app->language],             'active' => $node['link'] == Yii::$app->request->url ? true : false,             'linkOptions' => ($node['link_attributes'] ? array_merge(json_decode($node['link_attributes'], true), ['title' => ($node['lang']['title'] ?: $node['lang']['name'])]) : ['title' => ($node['lang']['title'] ?: $node['lang']['name'])]),             'items' => $children,         ];         return $result;     } }

MenuLang.php

<?php  namespace app\modules\menu\models;  use Yii;  class MenuLang extends \yii\db\ActiveRecord {     public static function tableName()     {         return 'menu_lang';     }      public function rules()     {         return [             [['name'], 'required'],             [['name', 'title'], 'string', 'max' => 255],         ];     }      public function attributeLabels()     {         return [             'owner_id' => Yii::t('app', 'Владелец'),             'language' => Yii::t('app', 'Язык'),             'name' => Yii::t('app', 'Название'),             'title' => Yii::t('app', 'Всплывающая подсказка'),         ];     }      public function getOwner()     {         return $this->hasOne(Menu::class, ['id' => 'owner_id']);     } }

MenuQuery.php

<?php  namespace app\modules\menu\models;  use paulzi\adjacencyList\AdjacencyListQueryTrait;  class MenuQuery extends \yii\db\ActiveQuery {     use AdjacencyListQueryTrait; }

MenuSearch.php

<?php  namespace app\modules\menu\models;  use Yii; use yii\base\Model; use yii\data\ActiveDataProvider; use app\modules\menu\models\Menu;  class MenuSearch extends Menu {     public $name;      public function rules()     {         return [             [['id', 'parent_id', 'sort', 'status'], 'integer'],             [['link', 'link_attributes', 'icon_class'], 'safe'],             [['name'], 'safe'],         ];     }      public function scenarios()     {         return Model::scenarios();     }      public function search($params)     {         $query = parent::find()->joinWith(['lang']);         $dataProvider = new ActiveDataProvider([             'query' => $query,             'sort' => ['defaultOrder' => ['sort' => SORT_ASC]]         ]);         $dataProvider->sort->attributes['name'] = [             'asc' => [                 'menu_lang.name' => SORT_ASC,             ],             'desc' => [                 'menu_lang.name' => SORT_DESC,             ],         ];         $this->load($params);         if (!$this->validate()) {             return $dataProvider;         }         $query->andFilterWhere([             'id' => $this->id,             'parent_id' => $this->parent_id,             'sort' => $this->sort,             'status' => $this->status,         ]);         $query->andFilterWhere(['like', 'link', $this->link]);         $query->andFilterWhere(['like', 'link_attributes', $this->link_attributes]);         $query->andFilterWhere(['like', 'icon_class', $this->icon_class]);         $query->andFilterWhere(['like', 'name', $this->name]);         return $dataProvider;     } }

Создание контроллеров

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

Классы нашего проекта, которые мы будем использовать как базовые, мы разместим в папке /base. Создадим файл /base/controllers/AdminLangTreeController.php. Этот контроллер у нас будет базовым для CRUD всех сущностей, в которых реализовано дерево и мультиязычность:

AdminLangTreeController.php

<?php  namespace app\base\controllers;  use Yii; use yii\web\Controller; use yii\web\NotFoundHttpException; use yii\filters\VerbFilter; use yii\helpers\Url;  class AdminLangTreeController extends Controller {     public $modelClass;     public $modelClassSearch;     public $modelName;     public $modelNameLang;      public function behaviors()     {         return [             'verbs' => [                 'class' => VerbFilter::class,                 'actions' => [                     'delete' => ['POST'],                 ],             ],         ];     }      public function actionIndex()     {         // Если корневой элемент дерева отсутствует, он будет создан автоматически         if (count($this->modelClass::find()->roots()->all()) == 0) {             $model = new $this->modelClass;             $model->makeRoot()->save();             Yii::$app->session->setFlash('info', Yii::t('app', 'Корневой элемент создан автоматически'));             return $this->redirect(['index']);         }         $searchModel = new $this->modelClassSearch;         $dataProvider = $searchModel->search(Yii::$app->request->queryParams);         $dataProvider->pagination = false;         return $this->render('index', [             'searchModel' => $searchModel,             'dataProvider' => $dataProvider,         ]);     }      public function actionCreate()     {         // Проверка наличия корневого элемента         if (count($this->modelClass::find()->roots()->all()) == 0) {             return $this->redirect(['index']);         }         // Создание новой записи и привязка её к дереву         $model = new $this->modelClass;         $root = $model::find()->roots()->one();         $model->parent_id = $root->id;         // Загрузка моделей из формы         if ($model->load(Yii::$app->request->post()) && $model->validate()) {             $parent = $model::findOne($model->parent_id);             $model->appendTo($parent)->save();             // Сохраняем мультиязычные данные                         foreach (Yii::$app->request->post($this->modelNameLang, []) as $language => $data) {                 foreach ($data as $attribute => $translation) {                     $model->translate($language)->$attribute = $translation;                 }             }             $model->save();             Yii::$app->session->setFlash('success', Yii::t('app', 'Создание прошло успешно'));             return $this->redirect(['update', 'id' => $model->id]);         } else {             return $this->render('create', [                 'model' => $model,             ]);         }     }      public function actionUpdate($id)     {         // Находим нужную модель         $model = $this->modelClass::find()->with('translations')->where(['id' => $id])->one();         if ($model === null) {             throw new NotFoundHttpException(Yii::t('app', 'Страница не найдена'));         }         // Загрузка данных из формы         if ($model->load(Yii::$app->request->post()) && $model->save()) {             foreach (Yii::$app->request->post($this->modelNameLang, []) as $language => $data) {                 foreach ($data as $attribute => $translation) {                     $model->translate($language)->$attribute = $translation;                 }             }             $model->save();             Yii::$app->session->setFlash('success', Yii::t('app', 'Обновление произведено успешно'));             if (Yii::$app->request->post('save') !== null) {                 return $this->redirect(['index']);             }             return $this->redirect(['update', 'id' => $model->id]);         } else {             return $this->render('update', [                 'model' => $model,             ]);         }     }      public function actionDelete($id)     {         $model = $this->findModel($id);         // Запрещаем удаление узла, если у него есть потомки         if (count($model->children) > 0) {             Yii::$app->session->setFlash('error', Yii::t('app', 'Элемент не может быть удалён, так как содержит дочерние элементы. Сначала нужно удалить все дочерние элементы'));             return $this->redirect(['index']);         }         // Запрещаем удаление корневого элемента         if ($model->isRoot()) {             Yii::$app->session->setFlash('error', Yii::t('app', 'Нельзя удалять корневой элемент'));             return $this->redirect(['index']);         }         // Удаляем элемент         if ($model->delete()) {             Yii::$app->session->setFlash('success', Yii::t('app', 'Удаление произведено успешно'));         }         return $this->redirect(['index']);     }      public function actionMoveUp($id)     {         $model = $this->findModel($id);         if ($prev = $model->getPrev()->one()) {             $model->moveBefore($prev)->save();             $model->reorder(false);         } else {             Yii::$app->session->setFlash('error', Yii::t('app', 'Невозможно передвинуть элемент вверх'));         }         return $this->redirect(Yii::$app->request->referrer);     }      public function actionMoveDown($id)     {         $model = $this->findModel($id);         if ($next = $model->getNext()->one()) {             $model->moveAfter($next)->save();             $model->reorder(false);         } else {             Yii::$app->session->setFlash('error', Yii::t('app', 'Невозможно передвинуть элемент вниз'));         }         return $this->redirect(Yii::$app->request->referrer);     }      protected function findModel($id)     {         if (($model = $this->modelClass::findOne($id)) !== null) {             return $model;         } else {             throw new NotFoundHttpException(Yii::t('app', 'Страница не найдена'));         }     } }

Теперь в модуле создадим файл /modules/menu/controllers/AdminController.php. Это будет основной контроллер для управления меню, и, так как он реализует дерево и мультиязычность, будет наследоваться от базового, который мы уже создали в предыдущем шаге:

AdminController.php

<?php  namespace app\modules\menu\controllers;  use app\base\controllers\AdminLangTreeController as BaseController;  class AdminController extends BaseController {     public $modelClass = \app\modules\menu\models\Menu::class;     public $modelClassSearch = \app\modules\menu\models\MenuSearch::class;     public $modelName = 'Menu';     public $modelNameLang = 'MenuLang'; }

Как видите, код данного контроллера содержит лишь названия моделей и их классов. Тоесть, для создания CRUD контроллеров других модулей (каталога, рубрикатора итд.), которые также будут использовать дерево и мультиязычность, можно поступать аналогичным способом — расширять базовый контроллер.

Создание интерфейса для управления меню

Завершающий этап — создание интерфейса для управления мультиязычным деревом. С задачей отображения дерева отлично справляется расширение Bootstrap Treeview, которое можно достаточно гибко настроить и оно поддерживает множество удобных функций (например, поиск по дереву). Создадим индексный view для отображения самого дерева, и поместим его в /modules/menu/views/admin/index.php:

index.php

<?php  use yii\helpers\Html; use yii\grid\GridView; use yii\widgets\ActiveForm; use execut\widget\TreeView;  $this->title = Yii::t('app', 'Меню сайта'); $this->params['breadcrumbs'][] = $this->title; ?> <div class="row">     <div class="col-md-6">         <div class="panel panel-primary">             <div class="panel-heading">                 <?= Html::a(Yii::t('app', 'Создать'), ['create'], ['class' => 'btn btn-success btn-flat']) ?>             </div>             <div class="panel-body">                 <?= TreeView::widget([                     'id' => 'tree',                     'data' => $searchModel::treeViewData($searchModel::find()->roots()->one()),                     'header' => Yii::t('app', 'Меню сайта'),                     'searchOptions' => [                         'inputOptions' => [                             'placeholder' => Yii::t('app', 'Поиск по дереву') . '...'                         ],                     ],                     'clientOptions' => [                         'selectedBackColor' => 'rgb(40, 153, 57)',                         'borderColor' => '#fff',                         'levels' => 10,                         'showTags' => true,                         'tagsClass' => 'badge',                         'enableLinks' => true,                     ],                 ]) ?>             </div>         </div>     </div> </div>

Вот мы дошли до самого интересного этапа данного кейса: как правильно создать форму для создания/редактирования мультиязычных данных. Создаём в папке /modules/menu/views/admin три файла:

create.php

<?php  use yii\helpers\Html;  $this->title = Yii::t('app', 'Создать'); $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Меню сайта'), 'url' => ['index']]; $this->params['breadcrumbs'][] = $this->title;  echo $this->render('_form', [     'model' => $model, ]);

update.php

<?php  use yii\helpers\Html;  $this->title = Yii::t('app', 'Обновить') . ': ' . $model->name; $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Меню сайта'), 'url' => ['index']]; $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['update', 'id' => $model->id]]; $this->params['breadcrumbs'][] = Yii::t('app', 'Обновить');  echo $this->render('_form', [     'model' => $model, ]);

_form.php

<?php  use yii\helpers\Html; use yii\widgets\ActiveForm;  if ($model->isNewRecord) {     $model->status = true; } ?> <div class="panel panel-primary">     <?php $form = ActiveForm::begin(); ?>     <div class="panel-body">         <fieldset>             <legend><?= Yii::t('app', 'Общие настройки') ?></legend>             <div class="row">                 <div class="col-md-4">                     <?php if (!$model->isRoot()) { ?>                         <?= $form->field($model, 'parent_id')->dropDownList($model::listTree()) ?>                                         <?php } ?>                     <?= $form->field($model, 'link')->textInput(['maxlength' => true]) ?>                     <?= $form->field($model, 'link_attributes')->textInput(['maxlength' => true]) ?>                     <?= $form->field($model, 'icon_class')->textInput(['maxlength' => true]) ?>                     <?= $form->field($model, 'status')->checkbox() ?>                 </div>             </div>         </fieldset>         <fieldset>             <legend><?= Yii::t('app', 'Содержание') ?></legend>                 <!-- Nav tabs -->             <ul class="nav nav-tabs" role="tablist">                 <?php foreach (Yii::$app->urlManager->languages as $key => $language) { ?>                     <li role="presentation" <?= $key == 0 ? 'class="active"' : '' ?>>                         <a href="#tab-content-<?= $language ?>" aria-controls="tab-content-<?= $language ?>" role="tab" data-toggle="tab"><?= $language ?></a>                     </li>                 <?php } ?>             </ul>             <!-- Tab panes -->             <div class="tab-content">                 <?php foreach (Yii::$app->urlManager->languages as $key => $language) { ?>                     <div role="tabpanel" class="tab-pane <?= $key == 0 ? 'active' : '' ?>" id="tab-content-<?= $language ?>">                         <?= $form->field($model->translate($language), "[$language]name")->textInput() ?>                         <?= $form->field($model->translate($language), "[$language]title")->textInput() ?>                     </div>                 <?php } ?>             </div>         </fieldset>             </div>     <div class="box-footer">         <?= Html::submitButton($model->isNewRecord ? '<i class="fa fa-plus"></i> ' . Yii::t('app', 'Создать') : '<i class="fa fa-refresh"></i> ' . Yii::t('app', 'Обновить'), ['class' => $model->isNewRecord ? 'btn btn-primary' : 'btn btn-success']) ?>     <?= !$model->isNewRecord ? Html::submitButton('<i class="fa fa-save"></i> ' . Yii::t('app', 'Сохранить'), ['class' => 'btn btn-warning', 'name' => 'save']) : ''; ?>     <?= !$model->isNewRecord ? Html::a('<i class="fa fa-trash"></i> ' . Yii::t('app', 'Удалить'), ['delete', 'id' => $model->id], ['class' => 'btn btn-danger', 'data' => ['confirm' => Yii::t('app', 'Вы уверены, что хотите удалить этот элемент?'), 'method' => 'post']]) : ''; ?>     </div>     <?php ActiveForm::end(); ?> </div>

Не забываем, что в приложении должен быть указан язык по умолчанию (параметр language), а в параметрах UrlManager — массив со списком языков (languages), которые мы будем использовать. Язык по умолчанию должен быть первым в это массиве.

Заключение

В итоге мы должны получить следующее:

  • Готовый модуль для мультиязычного древовидного меню сайта с удобным и настраиваемым интерфейсом;
  • Базовый CRUD контроллер, который можно наследовать при создании других модулей, в которых используется дерево и мультиязычность;
  • Два трейта (мультиязычность и дерево), которые можно подключать к моделям для имплементации соответствующих функций.

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


ссылка на оригинал статьи https://habr.com/post/426625/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *