Yii2: Робимо модуль для керування модулями

Вітаю всіх! На поточному проекті ми використовуємо Yii2 і в процесі розробки знадобилася якась сутність як модуль.

У Yii2 вже реалізована модульна система, але є один мінус у тому, що модуль не дозволяє виводити один модуль в іншому модулі, а використання віджетів теж не підходить, оскільки це частина виду і не вміє обробляти дії, наприклад входить POST-запит (хоча один час ми використовували віджети так з якимись милицями).

Ми створимо модуль, який буде містити вкладені модулі, які можна буде використовувати в будь-якому контроллері.

В результаті ми отримаємо:

  • Модулі виводяться в будь-якому контроллері
  • Управління через БД станом модуля (включений/виключений) і його позицією
  • Обробка вхідних запитів
  • Висновок тільки на певних сторінках потрібні модулі
За ідею взято реалізація модулів CMS OpenCart.

Почнемо з створення модуля пошук через gii і підключимо його в конфіги web.php

'пошук' => [
'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 path by
* @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 class definition
*/
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');

В корені нашого модуля пошук додамо директорію 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);
}
}


Докорінно модуля пошук додамо директорію catalog — це батьківська директорія для наших модулів.

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

Module.php
<?php
namespace app\modules\dispatcher\catalog\test;

/**
* test module class definition
*/
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 view index 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>


Майже все готово, залишилося тільки зробити виклик наших модулів. Для цього створимо компонент Пошук 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 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

'пошук' => [
'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.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.