Eintrag
Viele unerfahrene Webentwickler stehen vor der Notwendigkeit, Menüs, Verzeichnisse oder Kategorien für ihr Yii2-Projekt zu erstellen, die eine hierarchische Struktur haben, aber gleichzeitig die Mehrsprachigkeit unterstützen. Die Aufgabe ist recht einfach, aber im Rahmen dieses Rahmens nicht ganz offensichtlich. Es gibt eine große Anzahl vorgefertigter Erweiterungen zum Erstellen von Baumstrukturen (Menüs, Verzeichnisse usw.), aber es ist ziemlich schwierig, eine Lösung zu finden, die eine vollwertige Arbeit mit mehreren Sprachen unterstützt. Darüber hinaus geht es nicht um die Übersetzung der Benutzeroberfläche mit Standard-Framework-Tools, sondern um das Speichern von Daten in einer Datenbank in mehreren Sprachen. Es ist auch ziemlich schwierig, ein praktisches und voll funktionsfähiges Widget für die Verwaltung des Baums zu finden, das auch mit mehrsprachigen Inhalten ohne komplizierte Codemanipulationen funktionieren könnte.
Ich möchte ein Rezept zum Erstellen ähnlicher Module anhand eines Beispiels für eine Implementierung eines Menümoduls teilen. Zum Beispiel werde ich die Yii2 App Basic-Anwendungsvorlage verwenden, aber Sie können alles an Ihre Vorlage anpassen, wenn es von der Basisvorlage abweicht.
Vorbereitung
Um diese Aufgabe zu erfüllen, benötigen wir einige wunderbare Erweiterungen, nämlich:
Installieren Sie die Erweiterungsdaten über Composer:
composer require paulzi/yii2-adjacency-list composer require execut/yii2-widget-bootstraptreeview composer require creocoder/yii2-translateable
Um das Menü als Modul zu implementieren, erstellen Sie mit dem Gii-Generator (oder manuell) ein neues Menümodul und verbinden Sie es in den Anwendungseinstellungen.
Für das Projekt muss außerdem ein Sprachumschaltmechanismus konfiguriert sein. Ich bevorzuge diese Erweiterung für Yii2 .
Modellerstellung
Um das Menü (oder eine andere Entität mit Mehrsprachigkeit) in der Datenbank zu speichern, müssen zwei Tabellen erstellt werden. Tatsächlich können verschiedene Methoden zum Speichern mehrsprachiger Daten verwendet werden, aber ich mag die Option mit zwei Tabellen, von denen eine die Essenz selbst speichert und die zweite - ihre Sprachvariationen - mehr als die anderen. Es ist praktisch, Migrationen zum Erstellen von Tabellen zu verwenden. Hier ist ein Beispiel für eine solche Migration:
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}}'); } }
Legen Sie diese Migrationsdatei im Ordner / migrations unseres Projekts ab
Führen Sie den Befehl in der Konsole aus:
php yii migrate
Nachdem wir die erforderlichen Tabellen erstellt und mithilfe der Migration ein neues Menü hinzugefügt haben, müssen wir Modelle erstellen. Da Mehrsprachigkeit und Bäume nicht nur im Menü, sondern auch in anderen Entitäten (z. B. Websiteseiten) des Projekts zu finden sind, schlage ich vor, die Methoden, die den Mehrsprachigkeitsmechanismus und die Baumorganisation implementieren, in separate Merkmale einzuteilen, damit wir sie später problemlos verwenden können sie in anderen Modellen ohne Duplizierung von Code. Erstellen Sie einen Merkmalsordner im Anwendungsstamm (falls noch nicht vorhanden) und legen Sie dort zwei Dateien ab:
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; } }
Jetzt erstellen wir die Modelle direkt für die Arbeit mit dem Menü, in dem wir die Merkmale für den Baum und die Mehrsprachigkeit verbinden. Wir setzen die Modelle in / 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; } }
Controller erstellen
Für CRUD-Operationen an mehrsprachigen Bäumen benötigen wir einen Controller. Um unser Leben in Zukunft zu vereinfachen, werden wir einen grundlegenden Controller erstellen, in dem alle erforderlichen Aktionen ausgeführt werden, und für verschiedene Entitäten, ob es sich um ein Menü, ein Verzeichnis oder Seiten handelt, werden wir davon erben.
Die Klassen unseres Projekts, die wir als Basisklassen verwenden, werden im Ordner / base abgelegt. Erstellen Sie die Datei /base/controllers/AdminLangTreeController.php. Dieser Controller ist die Basis für CRUD aller Entitäten, in denen der Baum und die Mehrsprachigkeit implementiert sind:
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', ' ')); } } }
Erstellen Sie nun im Modul die Datei /modules/menu/controllers/AdminController.php. Dies ist der Hauptcontroller für die Verwaltung des Menüs. Da er den Baum und die Mehrsprachigkeit implementiert, erbt er von dem Basis-Controller, den wir bereits im vorherigen Schritt erstellt haben:
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'; }
Wie Sie sehen können, enthält der Code dieses Controllers nur die Namen der Modelle und ihrer Klassen. Das heißt, um CRUD-Controller für andere Module (Katalog, Rubrik usw.) zu erstellen, die ebenfalls den Baum und die Mehrsprachigkeit verwenden, können Sie dasselbe auf die gleiche Weise tun - den Basis-Controller erweitern.
Erstellen einer Schnittstelle zum Verwalten des Menüs
Die letzte Phase ist die Erstellung einer Schnittstelle zum Verwalten eines mehrsprachigen Baums. Die Bootstrap Treeview-Erweiterung übernimmt die Anzeige eines Baums, der sehr flexibel konfiguriert werden kann und viele praktische Funktionen unterstützt (z. B. Suche in einem Baum). Erstellen Sie eine Indexansicht, um den Baum selbst anzuzeigen, und fügen Sie ihn in /modules/menu/views/admin/index.php ein:
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>
Wir haben also die interessanteste Phase dieses Falls erreicht: Wie erstelle ich ein Formular zum Erstellen / Bearbeiten mehrsprachiger Daten? Erstellen Sie drei Dateien im Ordner / 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> <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> <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>
Vergessen Sie nicht, dass die Standardsprache in der Anwendung (Sprachparameter) und in den UrlManager-Parametern angegeben werden sollte - einem Array mit einer Liste der Sprachen (Sprachen), die wir verwenden werden. Die Standardsprache sollte die erste in diesem Array sein.
Fazit
Als Ergebnis sollten wir Folgendes erhalten:
- Ein vorgefertigtes Modul für ein mehrsprachiges baumartiges Menü einer Site mit einer praktischen und anpassbaren Oberfläche;
- Ein grundlegender CRUD-Controller, der beim Erstellen anderer Module mit Holz und Mehrsprachigkeit vererbt werden kann.
- Zwei Merkmale (mehrsprachig und Baum), die mit Modellen verbunden werden können, um die entsprechenden Funktionen zu implementieren.
Ich hoffe, dieser Artikel wird nützlich sein und Ihnen bei der Entwicklung neuer guter Projekte auf Yii2 helfen.