Приветствую всех! На текущем проекте мы используем Yii2 и в процессе разработки понадобилась некая сущность как модуль.
В Yii2 уже реализована модульная система, но есть один минус в том что модуль не позволяет выводить один модуль в другом модуле, а использования виджетов тоже не подходит, т.к. это часть вида и не умеет обрабатывать действия, например входящий POST-запрос (хотя одно время мы использовали виджеты так с некими костылями).
Мы создадим модуль, который будет содержать вложенные модули, которые можно будет использовать в любом контроллере.
В итоге мы получим:
- Модули выводятся в любом контроллере
- Управление через БД состоянием модуля (включен/выключен) и его позицией
- Обработка входящих запросов
- Вывод только на определенных страницах нужные модули
За идею взято реализация модулей в CMS OpenCart.
Начнем с создания модуля dispatcher через gii и подключим его в конфиге web.php
'dispatcher' => [
'class' => 'appmodulesdispatcherModule',
],
В директории модуля appmodulesdispatcher создадим класс BasicModule, который наследуется от yiibaseModule.
<?php
namespace appmodulesdispatcher;
use appmodulesdispatchercomponentsController;
use appmodulesdispatchermodelsLayoutModule;
/**
*
* Class Module
* @package appmodulesdispatchercomponents
*
*/
class BasicModule extends yiibaseModule
{
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 yiibaseInvalidParamException
*/
public function init()
{
parent::init();
$this->_setModuleVariables();
$this->loadModules();
}
/**
* Load modules from directory by path
* @throws yiibaseInvalidParamException
*/
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 yiibaseInvalidConfigException
*/
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|yiidbActiveRecord[]
* @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;
}
}
Унаследуем класс модуля appmodulesdispatcherModule от BasicModule
<?php
namespace appmodulesdispatcher;
/**
* 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 который будет наследовать yiiwebController. Переопределим в нем метод render().
<?php
namespace appmodulesdispatchercomponents;
/**
*
* Class Controller
* @package appmodulesdispatchercomponents
*/
class Controller extends yiiwebController
{
/**
* @param string $view
* @param array $params
* @return string
* @throws yiibaseInvalidParamException
* @throws yiibaseViewNotFoundException
* @throws yiibaseInvalidCallException
*/
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:
<?php
namespace appmodulesdispatchercatalogtest;
/**
* test module definition class
*/
class Module extends yiibaseModule
{
/**
* @inheritdoc
*/
public $controllerNamespace = 'appmodulesdispatchercatalogtestcontrollers';
}
Создаем директорию controllers и в ней класс DefaultController который наследуем от нашего appmodulesdispatchercomponentsController.
<?php
namespace appmodulesdispatchercatalogtestcontrollers;
use appmodulesdispatchercomponentsController;
/**
* Default controller for the `test` module
*/
class DefaultController extends Controller
{
/**
* Renders the index view for the module
* @return string
* @throws yiibaseInvalidParamException
* @throws yiibaseViewNotFoundException
* @throws yiibaseInvalidCallException
*/
public function index()
{
return $this->render('index');
}
}
Важно: чтоб работал наш модуль он всегда должен наследоваться от appmodulesdispatchercomponentsController и содержать метод index
Создадим директории для представления views/default и файл нашего представления:
<div class="dispatcher-default-index">
<p>
You may customize this page by editing the following file:<br>
<code><?= __FILE__ ?></code>
</p>
</div>
Почти все готово, осталось только сделать вызов наших модулей. Для этого создадим компонент Dispatcher в appmodulesdispatchercomponents:
<?php
namespace appmodulesdispatchercomponents;
use yiibaseObject;
class Dispatcher extends Object
{
/**
* @var appmodulesdispatcherModule
*/
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 yiibaseInvalidConfigException
*/
public function modules($layout, array $positions = [])
{
return $this->_module->run($layout, $positions);
}
}
Теперь надо подключить наш компонент в web.php
'dispatcher' => [
'class' => 'appmodulesdispatchercomponentsDispatcher',
],
Не забываем что компонент надо добавить в массив components.
В любом контроллере, например SiteController, в методе actionIndex() добавим
/* @var $modules Dispatcher */
$modules = Yii::$app->dispatcher->modules(1);
return $this->render('index', compact('modules'));
Осталось только добавить в наше представление позиции для вывода модулей views/site/index.php:
<?php
/* @var $this yiiwebView */
$this->title = 'My Yii Application';
use appmodulesdispatcherModule;
?>
<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.
Автор: yu-hritsaiy