Yii2: Делаем модуль для управления модулями

от автора

Приветствую всех! На текущем проекте мы используем Yii2 и в процессе разработки понадобилась некая сущность как модуль.

В Yii2 уже реализована модульная система, но есть один минус в том что модуль не позволяет выводить один модуль в другом модуле, а использования виджетов тоже не подходит, т.к. это часть вида и не умеет обрабатывать действия, например входящий POST-запрос (хотя одно время мы использовали виджеты так с некими костылями).

Мы создадим модуль, который будет содержать вложенные модули, которые можно будет использовать в любом контроллере.

В итоге мы получим:

  • Модули выводятся в любом контроллере
  • Управление через БД состоянием модуля (включен/выключен) и его позицией
  • Обработка входящих запросов
  • Вывод только на определенных страницах нужные модули

За идею взято реализация модулей в CMS OpenCart.

Начнем с создания модуля dispatcher через gii и подключим его в конфиге web.php

'dispatcher' => [      'class' => 'app\modules\dispatcher\Module', ], 

В директории модуля app\modules\dispatcher создадим класс BasicModule, который наследуется от \yii\base\Module.

BasicModule.php

<?php  namespace app\modules\dispatcher;  use app\modules\dispatcher\components\Controller; use app\modules\dispatcher\models\LayoutModule;  /**  *  * Class Module  * @package app\modules\dispatcher\components  *  */ class BasicModule extends \yii\base\Module {     const POSITION_HEADER = 'header';     const POSITION_FOOTER = 'footer';     const POSITION_LEFT = 'left';     const POSITION_RIGHT = 'right';      /**      * @var array of positions      */     static protected $positions = [         self::POSITION_HEADER,         self::POSITION_FOOTER,         self::POSITION_LEFT,         self::POSITION_RIGHT,     ];      /**      * @var string controller name      */     public $defaultControllerName = 'DefaultController';      /**      * @var string dir of modules catalog      */     public $modulesDir = 'catalog';      /**      * @var string modules namespace      */     private $_modulesNamespace;      /**      * @var string absolute path to modules dir      */     public $modulePath;      /**      *      * @throws \yii\base\InvalidParamException      */     public function init()     {         parent::init();          $this->_setModuleVariables();          $this->loadModules();     }      /**      * Load modules from directory by path      * @throws \yii\base\InvalidParamException      */     protected function loadModules()     {         $handle = opendir($this->modulePath);          while (($dir = readdir($handle)) !== false) {             if ($dir === '.' || $dir === '..') {                 continue;             }              $class = $this->_modulesNamespace . '\\' . $dir . '\\Module';              if (class_exists($class)) {                 $this->modules = [                     $dir => [                         'class' => $class,                     ],                 ];             }         }          closedir($handle);     }      /**      * @param $layout      * @param array $positions      * @return array      * @throws \yii\base\InvalidConfigException      */     public function run($layout, array $positions = [])     {         $model = $this->findModel($layout, $positions);          $data = [];          foreach ($model as $item) {             if ($controller = $this->findModuleController($item['module'])) {                 $data[$item['position']][] = \Yii::createObject($controller, [$item['module'], $this])->index();             }         }          return $data;     }      /**      * @param $layout_id      * @param array $positions      * @return array|\yii\db\ActiveRecord[]      * @internal param $layout      */     public function findModel($layout_id, array $positions = [])     {         if (empty($positions)) {             $positions = self::$positions;         }          return LayoutModule::find()             ->where([                 'layout_id' => $layout_id,                 'position' => $positions,                 'status' => LayoutModule::STATUS_ACTIVE,             ])->orderBy([                 'sort_order' => SORT_ASC             ])->asArray()->all();     }      /**      * @param $name      * @return null|string      */     public function findModuleController($name)     {         $className = $this->_modulesNamespace . '\\' . $name . '\controllers\\' . $this->defaultControllerName;          return is_subclass_of($className, Controller::class) ? $className : null;     }      /**      * Set modules namespace and path      */     private function _setModuleVariables()     {         $class = new \ReflectionClass($this);         $this->_modulesNamespace = $class->getNamespaceName() . '\\' . $this->modulesDir;         $this->modulePath = dirname($class->getFileName()) . DIRECTORY_SEPARATOR . $this->modulesDir;     } } 

Унаследуем класс модуля app\modules\dispatcher\Module от BasicModule

Module.php

<?php  namespace app\modules\dispatcher;  /**  * dispatcher module definition class  */ class Module extends BasicModule {     /**      * @inheritdoc      */     public function init()     {         parent::init();     } } 

Создадим и выполним миграцию:

Миграция

    public $table = '{{%layout_module}}';      public function safeUp()     {         $tableOptions = null;         if ($this->db->driverName === 'mysql') {             $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';         }           $this->createTable($this->table, [             'id' => $this->primaryKey(),             'layout_id' => $this->integer()->notNull(), // id страницы для вывода нашего модуля             'module' => $this->string(150)->notNull(),  //название модуля             'status' => $this->boolean()->defaultValue(true),             'position' => $this->string(30)->notNull(),             'sort_order' => $this->integer()->defaultValue(1),         ], $tableOptions);     }      public function safeDown()     {         $this->dropTable($this->table);     } 

Заполним созданную таблицу:

INSERT INTO `layout_module` VALUES ('1', '1', 'test', '1', 'header', '1'); INSERT INTO `layout_module` VALUES ('2', '1', 'test', '1', 'footer', '1'); INSERT INTO `layout_module` VALUES ('3', '1', 'test', '1', 'left', '1'); 

В корне нашего модуля dispatcher добавим директорию components. Создадим класс Controller который будет наследовать \yii\web\Controller. Переопределим в нем метод render().

Controller.php

<?php  namespace app\modules\dispatcher\components;  /**  *  * Class Controller  * @package app\modules\dispatcher\components  */ class Controller extends \yii\web\Controller {     /**      * @param string $view      * @param array $params      * @return string      * @throws \yii\base\InvalidParamException      * @throws \yii\base\ViewNotFoundException      * @throws \yii\base\InvalidCallException      */     public function render($view, $params = [])     {         $controller = str_replace('Controller', '', $this->module->defaultControllerName);          $path = '@app/modules/dispatcher/' . $this->module->modulesDir . '/' . $this->id . '/views/' . $controller;          return $this->getView()->render($path . '/' . 'index', $params, $this);     } } 

В корне модуля dispatcher добавим директорию catalog — это родительская директория для наших модулей.

Дальше мы создаем наш первый модуль, который по своей структуре ничем не отличается от обычно модуля Yii2. Создаем директорию test, в ней создаем класс Module:

Module.php

<?php namespace app\modules\dispatcher\catalog\test;  /**  * test module definition class  */ class Module extends \yii\base\Module {     /**      * @inheritdoc      */     public $controllerNamespace = 'app\modules\dispatcher\catalog\test\controllers'; } 

Создаем директорию controllers и в ней класс DefaultController который наследуем от нашего app\modules\dispatcher\components\Controller.

DefaultController.php

<?php  namespace app\modules\dispatcher\catalog\test\controllers;  use app\modules\dispatcher\components\Controller;  /**  * Default controller for the `test` module  */ class DefaultController extends Controller {     /**      * Renders the index view for the module      * @return string      * @throws \yii\base\InvalidParamException      * @throws \yii\base\ViewNotFoundException      * @throws \yii\base\InvalidCallException      */     public function index()     {         return $this->render('index');     } } 

Важно: чтоб работал наш модуль он всегда должен наследоваться от app\modules\dispatcher\components\Controller и содержать метод index

Создадим директории для представления views/default и файл нашего представления:

index.php

<div class="dispatcher-default-index">     <p>         You may customize this page by editing the following file:<br>         <code><?= __FILE__ ?></code>     </p> </div> 

Почти все готово, осталось только сделать вызов наших модулей. Для этого создадим компонент Dispatcher в app\modules\dispatcher\components:

Dispatcher.php

<?php  namespace app\modules\dispatcher\components;  use yii\base\Object;  class Dispatcher extends Object {     /**      * @var \app\modules\dispatcher\Module      */     private $_module;      public $module = 'dispatcher';      /**      * Dispatcher constructor.      * @param array $config      */     public function __construct(array $config = [])     {         parent::__construct($config);          $this->_module = \Yii::$app->getModule($this->module);     }      /**      * Get modules by layout      *      * @param $layout      * @param array $positions      * @return array      * @throws \yii\base\InvalidConfigException      */     public function modules($layout, array $positions = [])     {         return $this->_module->run($layout, $positions);     } } 

Теперь надо подключить наш компонент в web.php

        'dispatcher' => [             'class' => 'app\modules\dispatcher\components\Dispatcher',         ], 

Не забываем что компонент надо добавить в массив components.

В любом контроллере, например SiteController, в методе actionIndex() добавим

	/* @var $modules Dispatcher */ 	$modules = \Yii::$app->dispatcher->modules(1);  	return $this->render('index', compact('modules')); 

Осталось только добавить в наше представление позиции для вывода модулей views/site/index.php:

index.php

<?php  /* @var $this yii\web\View */  $this->title = 'My Yii Application';  use app\modules\dispatcher\Module;  ?> <div class="site-index">      <?php if (isset($modules[Module::POSITION_HEADER])) { ?>         <div class="row">             <?php foreach ($modules[Module::POSITION_HEADER] as $module) {                 echo $module;             } ?>         </div>     <?php } ?>       <div class="jumbotron">         <h1>Congratulations!</h1>          <p class="lead">You have successfully created your Yii-powered application.</p>          <p><a class="btn btn-lg btn-success" href="http://www.yiiframework.com">Get started with Yii</a></p>     </div>      <div class="body-content">          <div class="row">             <div class="col-lg-4">                 <h2>Heading</h2>                  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor                     incididunt ut                     labore et                     dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris                     nisi                     ut aliquip                     ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse                     cillum dolore eu                     fugiat nulla pariatur.</p>                  <p><a class="btn btn-default" href="http://www.yiiframework.com/doc/">Yii                         Documentation »</a></p>             </div>             <div class="col-lg-4">                 <h2>Heading</h2>                  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor                     incididunt ut                     labore et                     dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris                     nisi                     ut aliquip                     ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse                     cillum dolore eu                     fugiat nulla pariatur.</p>                  <p><a class="btn btn-default" href="http://www.yiiframework.com/forum/">Yii                         Forum »</a>                 </p>             </div>             <div class="col-lg-4">                 <h2>Heading</h2>                  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor                     incididunt ut                     labore et                     dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris                     nisi                     ut aliquip                     ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse                     cillum dolore eu                     fugiat nulla pariatur.</p>                  <p><a class="btn btn-default" href="http://www.yiiframework.com/extensions/">Yii                         Extensions »</a></p>             </div>         </div>     </div>      <?php if (isset($modules[Module::POSITION_FOOTER])) { ?>         <div class="row">             <?php foreach ($modules[Module::POSITION_FOOTER] as $module) {                 echo $module;             } ?>         </div>     <?php } ?> </div> 

Рекомендую официальную документацию по модулям.

Весь код выложен на GitHub.
ссылка на оригинал статьи https://habrahabr.ru/post/317950/


Комментарии

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

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