Сервис на Yii2: Организация переносимых модулей

В предыдущем рефакторинге мы начали перемещать переводы и конфигурацию в модули. Осталось сделать ещё одну вещь, незаметную снаружи, но очень важную для структуры приложения. А именно, довести разбивку на модули до логического завершения.

Когда мы начинали делать модуль user ничто не предвещало беды. Но потом нам захотелось сделать управление пользователями. Мы сгенерировали для этого модуль admin и поместили контроллер управления пользователями в него.

Предыдущие части | Исходники на GitHub

В итоге, в конфигурационном файле config/common.php образовался простой код подключения всех элементов:

return [
    ...
 
    'modules' => [
        'admin' => [
            'class' => 'app\modules\admin\Module',
        ],
        'main' => [
            'class' => 'app\modules\main\Module',
        ],
        'user' => [
            'class' => 'app\modules\user\Module',
        ],
    ],
 
    ...
];

А папка modules стала такой большой:

├── admin
│   ├── controllers
│   │   ├── DefaultController.php
│   │   └── UsersController.php
│   ├── messages
│   ├── models
│   │   ├── search
│   │   │   └── UserSearch.php
│   │   └── User.php
│   ├── views
│   │   ├── default
│   │   └── users
│   ├── Bootstrap.php
│   └── Module.php
│
├── main
│   ├── controllers
│   │   ├── DefaultController.php
│   │   └── ContactController.php
│   ├── messages
│   ├── forms
│   │   └── ContactForm.php
│   ├── views
│   │   ├── default
│   │   └── contact
│   ├── Bootstrap.php
│   └── Module.php
│
└── user
    ├── commands
    │   ├── CronController.php
    │   └── UsersController.php
    ├── controllers
    │   ├── DefaultController.php
    │   └── ProfileController.php
    ├── mails
    ├── messages
    ├── forms
    ├── models
    │   ├── query
    │   └── User.php
    ├── views
    │   └── profile
    ├── Bootstrap.php
    └── Module.php

Модули служат у нас сейчас в целях группировки моделей и прочего вспомогательного кода. Но если посмотреть на admin, то увидим что в нашей архитектуре управление пользователями вшито прямо в него:

admin
├── controllers
│   ├── DefaultController.php
│   └── UsersController.php
├── messages
├── models
│   ├── search
│   │   └── UserSearch.php
│   │   └── User.php
├── views
│   ├── default
│   └── users
├── Bootstrap.php
└── Module.php

Эти контроллеры и представления для администрирования пользователями у нас вынесены из user только для удобства маршрутизации. С такой структурой можно жить, но это накладывает некие ограничения. Разберёмся в них подробнее.

Во-первых, код стал разрозненным. Во-вторых, нам при такой структуре часто придётся лазить из одного модуля в другой для получения написанных в классе модуля user параметров. Из-за такой разрозненнности эти папки назвать модулями мы пока не можем.

Что такое настоящий модуль приложения? Цитируя документацию:

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

Сама идея изначально заключается в их самодостаточности. Если есть модуль блога или форума, то весь код должен быть в нём. Весьма странно было бы наблюдать форум, содержащий только отображение без админки. И какие бы мы готовые модули не искали, всё всегда находится в их папке и отдельно вручную не доустанавливается.

Группировка всего связанного кода в одном месте повышает удобство его использования и переиспользования. А именно:

  • Всё содержится в одной папке. Не нужно искать контроллеры по всему приложению.
  • При помещении контроллеров в сам модуль он всегда доступен через свойство $this->module текущего контроллера. Полностью пропадает необходимость ручного запроса через Yii::$app->getModule('user') для извлечения его параметров.
  • Полноценный модуль с контроллерами можно одной папкой перенести и подключить в другой проект (или даже опубликовать для свободного доступа).

Таким образом, наша текущая архитектура не очень-то соответствует данной логике. Согласен со статьёй и комментариями, что хватит это терпеть. Исправим эту ситуацию.

И да, кстати. В предыдущей части про автоудаление пользователей я отказался от внедрения параметра emailConfirmTokenExpire в UserQuery через DI-контейнер, так как UserQuery вечно создавался и дёргал параметр:

Yii::$app->getModule('user')->emailConfirmTokenExpire

при каждом запуске User::findIdentity в процессе аутентификации. Так что тот вариант я из статьи уже убрал.

Итак, приступим к реструктуризации нашего кода.

Группировка файлов

Сначала сделаем самое главное. А именно, соберём все контроллеры, модели и представления, относящиеся к пользователю, в одну папку modules/user. Для избежания смешивания разложим их внутри по поддиректориям frontend и backend. Консольные контроллеры из папки modules/user/commands поместим в console:

user
├── controllers
│   ├── backend
│   │   ├── DefaultController.php // прошлый admin/controllers/UsersController.php
│   │   └── UserController.php // содержит только actionLogin() и actionLogout()
│   ├── console
│   │   ├── CronController.php
│   │   └── UsersController.php
│   └── frontend
│       ├── DefaultController.php
│       └── ProfileController.php
│
├── forms
│   ├── backend
│   │   └── search
│   │       └── UserSearch.php
│   ├── frontend
│   │   ├── EmailConfirmForm.php
│   │   ├── PasswordChangeForm.php
│   │   ├── PasswordResetForm.php
│   │   ├── PasswordResetRequestForm.php
│   │   ├── ProfileUpdateForm.php
│   │   └── SignupForm.php
│   └── LoginForm.php
│
├── models
│   ├── backend
│   │   └── User.php
│   ├── query
│   │   └── UserQuery.php
│   └── User.php
│
├── views
│   ├── backend
│   │   ├── default
│   │   │   ├── _form.php
│   │   │   ├── create.php
│   │   │   ├── index.php
│   │   │   ├── update.php
│   │   │   └── view.php
│   │   └── user
│   │       └── login.php
│   └── frontend
│       ├── default
│       │   ├── login.php
│       │   ├── passwordReset.php
│       │   ├── passwordResetRequest.php
│       │   └── signup.php
│       └── profile
│           ├── index.php
│           ├── passwordChange.php
│           └── update.php
│
├── mails
├── messages
│
├── Bootstrap.php
└── Module.php

Не забудем про импорты классов при перемещении и переименовывании.

Для изменения всех пространств имён в use и namespace очень удобно пользоваться заменой в файлах или встроенным инструментом перемещения классов в IDE.

Далее в файлы в папке messages нужно переместить все переводы, которые были в admin/massages и которые касаются пользователя.

Теперь можно во всех представлениях внутри views/backend заменить класс модуля, используемый для вызова Module::t() с администраторского:

use app\modules\admin\Module;

на свой:

use app\modules\user\Module;

для полной самостоятельности.

И в каждом представлении убрать ссылку на панель управления из хлебных крошек, которую мы туда добавляли для навигации:

$this->params['breadcrumbs'][] = ['label' => Module::t('module', 'ADMIN'), 'url' => ['default/index']];

В итоге модуль admin должен оказаться практически пустым:

admin
├── controllers
│   └── DefaultController.php
├── views
│   └── default
│       └── index.php
├── messages
├── Bootstrap.php
└── Module.php

Ранее мы в классах Module делали автоматическое переключение на папку с консольными командами:

public $controllerNamespace = 'app\modules\user\controllers';
 
public function init()
{
    parent::init();
    if (Yii::$app instanceof ConsoleApplication) {
        $this->controllerNamespace = 'app\modules\user\commands';
    }
}

Теперь у нас таких папок commands нет и этот код стал бесполезным. Удалим его, оставив только параметры и метод t:

namespace app\modules\user;
 
use Yii;
 
class Module extends \yii\base\Module
{
    /**
     * @var int
     */
    public $emailConfirmTokenExpire = 259200; // 3 days
    /**
     * @var int
     */
    public $passwordResetTokenExpire = 3600;
 
    public static function t($category, $message, $params = [], $language = null)
    {
        return Yii::t('modules/user/' . $category, $message, $params, $language);
    }
}

Будем указывать controllerNamespace вручную при подключении.

Подключение модуля

Наш новый user принял структуру с поддиректориями backend и frontend. Это популярно при написании модулей под yii2-app-advanced. Нам нужно попробовать его как-то прикрутить к нашему приложению yii2-app-basic.

Нам нужно удалить подключение из общего файла config/common.php:

return [
    ...
    'modules' => [],
    ...
];

И отдельно подключить user для фронтенда config/web.php:

$config = [
    'id' => 'app',
    'modules' => [
        'admin' => [
            'class' => 'app\modules\admin\Module',
        ],
        'main' => [
            'class' => 'app\modules\main\Module',
        ],
        'user' => [
            'class' => 'app\modules\user\Module',
            'controllerNamespace' => 'app\modules\user\controllers\frontend',
            'viewPath' => '@app/modules/user/views/frontend',
        ],
    ],    
    ...
];

и для консольного приложения config/console.php:

return [
    'id' => 'app-console',
    'controllerNamespace' => 'app\commands',
    'modules' => [
        'user' => [
            'class' => 'app\modules\user\Module',
            'controllerNamespace' => 'app\modules\user\controllers\console',
        ],
    ],
];

Здесь мы как раз и указали разные пути к контроллерам и представлениям. А модули main и admin никаких консольных команд не содержат, так что в console.php они не нужны.

После таких переделок всё должно продолжить работать как и раньше, за исключением панели администрирования. Нам не хватает только управления пользователями. Для вывода панели можно подключить этот же модуль под другим именем users-admin, указав пути для backend:

'modules' => [
    'admin' => [
        'class' => 'app\modules\admin\Module',
    ],
    'main' => [
        'class' => 'app\modules\main\Module',
    ],
    'user-admin' => [
        'class' => 'app\modules\user\Module',
        'controllerNamespace' => 'app\modules\user\controllers\backend',
        'viewPath' => '@app/modules/user/views/backend',
    ],
    'user' => [
        'class' => 'app\modules\user\Module',
        'controllerNamespace' => 'app\modules\user\controllers\frontend',
        'viewPath' => '@app/modules/user/views/frontend',
    ],
],

Но это не очень удобно, так как при большом количестве модулей придётся каждый администраторский закрывать своим фильтром AccessControl. И если нам понадобится сделать отдельный шаблон для admin, то придётся указывать этот новый layout в каждом модуле. Да и в адресной строке будут постоянно показываться адреса вроде user-admin вместо admin/user. Конечно, это можно переопределить через правила маршрутизации в UrlManager, но всё равно это слишком несогласованно.

Можно избавиться от этой проблемы, если воспользоваться тем фактом, что класс Application в Yii2 наследуется от класса Module и, по сути, мы используем свойство modules именно из yii\base\Module. Это даёт нам возможность вкладывать модули друг в друга, то есть использовать их практически как мини-приложения. Соответственно, мы можем поместить все бэкенды внутрь admin:

// config/web.php
 
$config = [
    'id' => 'app',
    'modules' => [
        'admin' => [
            'class' => 'app\modules\admin\Module',
            'modules' => [
                'user' => [
                    'class' => 'app\modules\user\Module',
                    'controllerNamespace' => 'app\modules\user\controllers\backend',
                    'viewPath' => '@app/modules/user/views/backend',
                ],
            ]
        ],
        'main' => [
            'class' => 'app\modules\main\Module',
        ],
        'user' => [
            'class' => 'app\modules\user\Module',
            'controllerNamespace' => 'app\modules\user\controllers\frontend',
            'viewPath' => '@app/modules/user/views/frontend',
        ],
    ],
    ...
];

И любой фильтр вроде AccessControl или новый шаблон layout можно применить только к внешнему модулю admin.

Теперь все ссылки на вложенные модули в главном меню нужно переделать на «четырёхэтажные»:

!Yii::$app->user->isGuest ?
    ['label' => Yii::t('app', 'NAV_ADMIN'), 'items' => [
        ['label' => Yii::t('app', 'NAV_ADMIN'), 'url' => ['/admin/default/index']],
        ['label' => Yii::t('app', 'NAV_ADMIN_USERS'), 'url' => ['/admin/user/default/index']],
    ]] :
    false,

А внутри ничего заменять не нужно, так как в представлениях и контроллерах используется относительная адресация. Только в единственном представлении модуля admin поменяем перевод надписи и адрес ссылки:

<?php
 
use app\modules\admin\Module;
use app\modules\user\Module as UserModule;
use yii\helpers\Html;
 
/* @var $this yii\web\View */
/* @var $model \app\modules\user\models\backend\User */
 
$this->title = Module::t('module', 'ADMIN');
?>
<div class="admin-default-index">
    <h1><?= Html::encode($this->title) ?></h1>
 
    <p>
        <?= Html::a(UserModule::t('module', 'ADMIN_USERS'), ['user/default/index'], ['class' => 'btn btn-primary']) ?>
    </p>
</div>

Здесь мы не начинаем ссылку со слеша, так что она сработает вглубь модуля.

В результате модуль user стал абсолютно самостоятельным. Он содержит внутри себя все модели, контроллеры и представления, что позволяет его с лёгкостью перенести в любой другой проект на yii2-app-basic или yii2-app-advanced.

Если предполагается публиковать свой код в публичном репозитории, то следует перенести в него и миграции. Не забудьте тогда указать в инструкции по установке в своём файле README, что их нужно оттуда запустить:

php yii migrate --migrationPath=@vendor/my/module/migrations

Путь, соответственно, нужно будет указать именно свой. Но мы публиковать его не будем, так что оставим пока как есть.

Шаблон и хлебные крошки

Мы удалили из всех административных представлений ссылку на ['/admin/default/index']. Если раньше был адрес:

Главная / Панель управления / Пользователи

то теперь станет:

Главная / Пользователи

Поэтому в панели управления ориентироваться по ним стало сложно.

Этот пункт «Панель управления» вместо указания в представлениях можно добавлять в самом шаблоне. Помимо этого, можно выводить другие пункты в главном меню панели. Для этого можно целиком скопировать шаблон views/layouts/main.php и заменить пункты на свои. Но можно обойтись и без полного копирования.

Для этого разобъём основной шаблон на две части:

views
└── layouts
    ├── layout.php
    └── main.php

Основной код поместим в layout.php:

<?php
 
use yii\helpers\Html;
use app\assets\AppAsset;
 
/* @var $this \yii\web\View */
/* @var $content string */
 
AppAsset::register($this);
?>
<?php $this->beginPage() ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>">
<head>
    <meta charset="<?= Yii::$app->charset ?>"/>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <?= Html::csrfMetaTags() ?>
    <title><?= Html::encode($this->title) ?></title>
    <?php $this->head() ?>
</head>
<body>
 
<?php $this->beginBody() ?>
    <div class="wrap">
        <?= $content ?>
    </div>
 
    <footer class="footer">
        <div class="container">
            <p class="pull-left">&copy; <?= Yii::$app->name ?></p>
            <p class="pull-right"><?= date('Y') ?></p>
        </div>
    </footer>
 
<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>

А блок с меню и хлебными крошками вынесем именно в main.php:

<?php
 
use app\components\widgets\Alert;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs;
 
/* @var $this \yii\web\View */
/* @var $content string */
 
?>
<?php $this->beginContent('@app/views/layouts/layout.php'); ?>
 
<?php
NavBar::begin([
    'brandLabel' => Yii::$app->name,
    'brandUrl' => Yii::$app->homeUrl,
    'options' => [
        'class' => 'navbar-inverse navbar-fixed-top',
    ],
]);
echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'activateParents' => true,
    'items' => array_filter([
        ['label' => Yii::t('app', 'NAV_HOME'), 'url' => ['/main/default/index']],
        ['label' => Yii::t('app', 'NAV_CONTACT'), 'url' => ['/main/contact/index']],
        Yii::$app->user->isGuest ?
            ['label' => Yii::t('app', 'NAV_SIGNUP'), 'url' => ['/user/default/signup']] :
            false,
        Yii::$app->user->isGuest ?
            ['label' => Yii::t('app', 'NAV_LOGIN'), 'url' => ['/user/default/login']] :
            false,
        !Yii::$app->user->isGuest ?
            ['label' => Yii::t('app', 'NAV_ADMIN'), 'url' => ['/admin/default/index']] :
            false,
        !Yii::$app->user->isGuest ?
            ['label' => Yii::t('app', 'NAV_PROFILE'), 'items' => [
                ['label' => Yii::t('app', 'NAV_PROFILE'), 'url' => ['/user/profile/index']],
                ['label' => Yii::t('app', 'NAV_LOGOUT'),
                    'url' => ['/user/default/logout'],
                    'linkOptions' => ['data-method' => 'post']]
            ]] :
            false,
]),
]);
    NavBar::end();
?>
 
<div class="container">
    <?= Breadcrumbs::widget([
        'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
    ]) ?>
    <?= Alert::widget() ?>
    <?= $content ?>
</div>
 
<?php $this->endContent(); ?>

Этот подшаблон наследуется от основного с помощью конструкции:

<?php $this->beginContent('@app/views/layouts/layout.php'); ?>
...
<?php $this->endContent(); ?>

Так что ничего на сайте не сломается.

Теперь создадим ещё один похожий подшаблон views/layouts/admin.php для панели управления. Он тоже будет наследовать основной шаблон layout:

<?php
 
use app\components\widgets\Alert;
use app\modules\admin\Module;
use yii\helpers\ArrayHelper;
use yii\bootstrap\Nav;
use yii\bootstrap\NavBar;
use yii\widgets\Breadcrumbs;
 
/* @var $this \yii\web\View */
/* @var $content string */
 
/** @var \yii\web\Controller $context */
$context = $this->context;
 
if (isset($this->params['breadcrumbs'])) {
    $panelBreadcrumbs = [['label' => Module::t('module', 'ADMIN'), 'url' => ['/admin/default/index']]];
    $breadcrumbs = $this->params['breadcrumbs'];
} else {
    $panelBreadcrumbs = [Module::t('module', 'ADMIN')];
    $breadcrumbs = [];
}
?>
<?php $this->beginContent('@app/views/layouts/layout.php'); ?>
 
<?php
NavBar::begin([
    'brandLabel' => Yii::$app->name,
    'brandUrl' => Yii::$app->homeUrl,
    'options' => [
        'class' => 'navbar-inverse navbar-fixed-top',
    ],
]);
echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'activateParents' => true,
    'items' => array_filter([
        ['label' => Yii::t('app', 'NAV_ADMIN'), 'url' => ['/admin/default/index']],
        ['label' => Yii::t('app', 'NAV_ADMIN_USERS'), 'url' => ['/admin/user/default/index'], 'active' => $context->module->id == 'user'],
        ['label' => Yii::t('app', 'NAV_LOGOUT'), 'url' => ['/user/default/logout'], 'linkOptions' => ['data-method' => 'post']]
    ]),
]);
NavBar::end();
?>
 
<div class="container">
    <?= Breadcrumbs::widget([
        'links' => ArrayHelper::merge($panelBreadcrumbs, $breadcrumbs),
    ]) ?>
    <?= Alert::widget() ?>
    <?= $content ?>
</div>
 
<?php $this->endContent(); ?>

Мы его дополнили дописыванием пункта вначале хлебных крошек и изменили пункты меню. Также мы вручную указали active для пункта, чтобы он был активен при переходе в любой контроллер подмодуля user. Также мы убрали проверки на Yii::$app->user->isGuest, так как у нас весь модуль закрыт от неавторизованных пользователей с помощью общего AccessControl.

И подключаем шаблон к модулю полем $layout:

'modules' => [
    'admin' => [
        'class' => 'app\modules\admin\Module',
        'layout' => '@app/views/layouts/admin',
        'modules' => [
            'user' => [
                'class' => 'app\modules\user\Module',
                'controllerNamespace' => 'app\modules\user\controllers\backend',
                'viewPath' => '@app/modules/user/views/backend',
            ],
        ]
    ],
    ...
],

Единственное, что осталось сделать – это добавить правила маршрутизации для «четырёхэтажных» адресов. Можно целиком обернуть группу правил в GroupUrlRule, чтобы маршрутизатор лишний раз не искал по неподходящим строкам:

'urlManager' => [
    'class' => 'yii\web\UrlManager',
    'enablePrettyUrl' => true,
    'showScriptName' => false,
    'rules' => [
        [
            'class' => 'yii\web\GroupUrlRule',
            'prefix' => 'admin',
            'routePrefix' => 'admin',
            'rules' => [
                '' => 'default/index',
                '<_m:[\w\-]+>' => '<_m>/default/index',
                '<_m:[\w\-]+>/<id:\d+>' => '<_m>/default/view',
                '<_m:[\w\-]+>/<id:\d+>/<_a:[\w-]+>' => '<_m>/default/<_a>',
                '<_m:[\w\-]+>/<_c:[\w\-]+>/<id:\d+>' => '<_m>/<_c>/view',
                '<_m:[\w\-]+>/<_c:[\w\-]+>/<id:\d+>/<_a:[\w\-]+>' => '<_m>/<_c>/<_a>',
                '<_m:[\w\-]+>/<_c:[\w\-]+>' => '<_m>/<_c>/index',
            ],
        ],
 
        '' => 'main/default/index',
        'contact' => 'main/contact/index',
        '<_a:error>' => 'main/default/<_a>',
 
        '<_a:(login|logout|signup|email-confirm|password-reset-request|password-reset)>' => 'user/default/<_a>',
 
        '<_m:[\w\-]+>' => '<_m>/default/index',
        '<_m:[\w\-]+>/<_c:[\w\-]+>' => '<_m>/<_c>/index',
        '<_m:[\w\-]+>/<_c:[\w\-]+>/<_a:[\w-]+>' => '<_m>/<_c>/<_a>',
        '<_m:[\w\-]+>/<_c:[\w\-]+>/<id:\d+>' => '<_m>/<_c>/view',
        '<_m:[\w\-]+>/<_c:[\w\-]+>/<id:\d+>/<_a:[\w\-]+>' => '<_m>/<_c>/<_a>',
    ],
],

На этом всё. Снаружи на сайте ничего не изменилось, а внутри произошёл весьма важный процесс.

Наш псевдо-модуль admin фактически выполняет роль backend-приложения в yii2-app-advanced, подключая к себе бэкенды остальных модулей. Его можно как угодно настраивать и дополнять. При желании можно совершенно спокойно изменить его шаблон, не влезая в каждый модуль по отдельности.

Теперь не будет никакой путаницы с тем, куда какой класс поместить и как любой наш модуль к другому проекту привязать.

UPD. К статье появилось несколько комментариев с вопросом, как что сделать с модулями, если нужны связи. Ответил отдельной статьёй Связи независимых модулей.

В следующей части мы подключим контроль доступа:

Добавление RBAC для разграничения прав

Не забудьте подписаться на рассылку статей в сайдбаре или вместе с нами записаться и «позажигать» на бесплатных вебинарах. До встречи!

Другие статьи

Недавно мы переработали модульную структуру нашего сервиса. А нас уже есть модуль администрирования, который необходимо закрыть от посторонних глаз. Вдальнейшем мы будем добавлять новые ограничения для разных пользователей, поэтому пришла пора добавить контроль доступа на основе ролей.

К предыдущей философской статье об организации более-менее независимой структуры модулей появился комментарий вопросом о том, возможно ли оставить каждый модуль независимым, если между ними всё-таки нужно организовать какое-либо взаимодействие. Предположим, что у нас имеются несколько модулей с моделями, которые мы написали сами или подключили чужие.

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

На форуме и в личных сообщениях часто спрашивают о проектировании базы данных и о работе со связями в ActiveRecord во фреймворке. Про сохранение связанных моделей, про их вывод, про сортировку и поиск. Но и при этом многие не знают, откуда эти связи берутся, как организовываются и как за ними нужно следить. Поэтому отдельно поговорили о теории баз даных с практическим уклоном на нормализацию и внешние ключи.

Комментарии

 

xoma

Мы уже лет 5 используем похожий подход в Юпи =)

Ответить

 

Дмитрий Елисеев

Да, вижу там PageController и PageBackendController.

Даже этот мой сайт аналогично с PageAdminController написан. Так что тоже 5 лет :)

Ответить

 

xoma

Угу вместо каталогов мы используем префиксы в имени класса-контроллера, помнится на заре молодости в Уии были проблемы с вызовом контроллеров из вложенных директорий =)

Ответить

 

Андрей

Два вопроса:
1) Как получать информацию из одного модуля в другом. Например личном кабинете в модуля "User" вывести количество опубликованных постов из модуля "Blog" и количество комментариев из модуля "Comments"
2) Почему структура не:
user/Module.php
user/backend/controllers
user/backend/views
user/backend/models
user/frontend/controllers
user/frontend/views
user/frontend/models

Ответить

 

Дмитрий Елисеев

1. Если распространять модуль не собираетесь, то делайте обычные связи. Никто Вас в этом не ограничивает. В программировании нет ничего обязательного.

2. Куда в этом случае помещать общие файлы?

Ответить

 

lav45

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

В модули следует выносить функционал который никак не связан с другими модулями. А если в некоторых модулях нужна запрашивать данные например из модели User или Page тогда их стоит выносить на уровень приложения. Модули это самостоятельная часть вашего сайта с минимальным количеством зависимостей.

Ответить

 

Максим

Пишите сразу как нужно и толкуйте почему, а не как сейчас.
1 статья - написали код.
2 статья - добавили функционал и переписали кучу когда из первой статьи.
3 статья - еще что либо добавили и снова переписали кучу кода из прошлых статей.

А за статьи огромное спасибо.

Ответить

 

Дмитрий Елисеев

> Еще что либо добавили и снова переписали кучу кода...

Ну да, стандартный процесс разработки:

Установка > первый код > рефакторинг > статья > почесать левую пятку > код > анализ > новая идея > код > статья > нашлись косяки > рефакторинг > фигня > исправление фигни > новая фича > статья > придумал фишку > код > рефакторинг > статья > а можно и так > код > интересная идея в книге > код > рефакторинг -> ...

> Пишите сразу как нужно...

Я так не умею :)

Ответить

 

Максим

Дмитрий я полностью с вами согласен. Век живи век учись. Обратите внимание что вас не только новички читают. Сделайте баланс. Ещё раз спасибо за ваши статьи. Читаю с удовольствием.

Ответить

 

xfg

Это он пока абсолютно самостоятельный. Но по мере появления новых модулей между ними будут образовываться зависимости. И с этим ничего вроде нельзя сделать. Максимум на границах модулей можно внедрить интерфейсы и зависеть от этих интерфейсов, а не делать вызовы к другому модулю напрямую. Тогда хотя бы будет возможность подменять зависимости разными реализациями. В противном же случае модули будут неразлей вода, монолит.

Ответить

 

Макс

Разделил модули как написано в статье, и теперь хлебные крошки в аминистративной части вообще не отображаются. В чем может быть причина?
З.Ы. Спасибо за статью.

Ответить

 

Макс

Разобрался. Navbar закрыл хлебные крошки.

Ответить

 

ramik

Дмитрий, можно не использовать виджеты Yii ,а все написать при помощи html

Ответить

 

Дмитрий Елисеев

Можно. Фреймворк ничем пользоваться не заставляет :)

Ответить

 

Василий

Пришел сюда из Яндекс.Метрики. Решил посмотреть откуда льется бешенный трафик на мой блог, т.к. в основном там тихо и спокойно. Оказалось, что отсюда. =) Приятно.

Статью прочитал. Хорошо затронута тема по разделению `backend` от `frontend`, и пример работы с DI (DI сейчас особенно важная тема, потому что мало кто понимает, и знает о такой прекрасной возможности...хотя сам DI юзаю только в ZF2 :D).

Полтора года назад планировал нечто подобным образом разделить, но взял способ разделения на приложения, потому что как не крути, а модуль в Yii, если это конечно не сторонний модуль, имхо, не очень хорошо вписывается в философию фреймворка. =) Поэтому, после многочисленных экспериментов, и выводов, пишу в компонентном стиле и с разеделением на приложения проекты, и в модули выношу только сторонние расширения.

Буду рекомендовать вашу статью, как толковый пример реализации переносимых модулей, если вдруг что. ;)

Ответить

 

Олег
.grid-view td {
    white-space: nowrap;
}

.grid-view .filters input,
.grid-view .filters select {
    min-width: 50px;
}

/* align the logout "link" (button in form) of the navbar */
.nav > li > form {
    padding: 8px;
}

.nav > li > form > button:hover {
    text-decoration: none;
}
Ответить

 

Александр

Как сделать Pjax для списка пользователей? Не работает из-за использования двойного DatePicker'a.

Ответить

 

Дмитрий Елисеев

Не пробовал.

Ответить

 

Олег

Попробуйте, ибо тоже такая проблема.

Ответить

 

Evgeniy

Есть такая ситуация...
Имеем три модуля:
1. User
2. Post
3. Comments
Второй модуль зависит от первого, третий от первого и второго. Как в таком случаи организовать структуру модулей? Я вижу лишь в объедении в 1 модуль всех трех, но боюсь что этот один модуль будет расширяться дальше,т.к. можно добавить блог, магазин и еще что-нибудь.

Ответить

 

Дмитрий Елисеев

Ответил отдельной статьёй.

Ответить

 

Алексей

Пожалуйста научите загружать изображения. С виджетами и без, по одной и массово, с аяксом, с пользовательским кропом. В роде информация в интернете есть, но только основы, толку мало. Так как вы объяснять никто не умеет.

Ответить

 

stas

Все супер, но к сожалению очень редко статьи про сервис публикуются, уже второй год идет :(

Ответить

 

Дмитрий Елисеев

Их никто и не опубликует по Yii2, так как это особый фреймворк. Читайте документацию и статьи по фреймворкам Symfony, Laravel, Phalcon, Slim, языкам Java, C++, C# и книги по архитектуре за последние лет двадцать.

Ответить

 

Олег

Дмитрий, попробуйте Pjax, ибо тоже встретил такую проблему.

Ответить

 

Александр

Мы модуль user подключаем 2 раза. При этом если нас не устраивает значение параметра passwordResetTokenExpire по умолчанию, то нам в конфиге приложения нужно его установить в 2-х разных местах(если не разбираться что в административной части он не нужен)? Всё это решение какое-то кривое.

Ответить

 

Дмитрий Елисеев

В yii2-app-advanced также нужно прописывать в двух местах.

Ответить

 

dindilin

Дмитрий, добрый день.
Подскажите, а как вообще можно реализовать вывод модуля без прописывания его в конфиг файл?

Ответить

 

Дмитрий Елисеев

А зачем? Делаете CMS? Тогда в загрузочном BootstrapInterface-классе сканируете папку modules и заполняете Yii::$app->setModules(...).

Ответить

 

dindilin

Да не,не CMS, личный кабинет потребителя.

Ответить

 

Сергей

Добрый день, Дмитрий!

Подскажите пожалуйста, как организовать структуру. Возникла такая проблема, что профиль пользователя, с разными настройками, доступен как в админке Панели управления, так и самому пользователю, т.е. код практически идентичный, различается лишь некоторыми не доступными блоками для пользователя.

Т.е. как можно использовать один и тот же ProfileController, для backend и для frontend, что сделать в конфиге?

Ответить

 

Сергей

Не знаю на сколько это правильно, но в итоге сделал так и доделал маршруты:

'admin' => [
            'class' => 'app\modules\admin\Module',
            'layout' => '@app/views/layouts/admin',
            'modules' => [
                'users' => [
                    'class' => 'app\modules\user\Module',
                    'controllerNamespace' => 'app\modules\user\controllers\backend',
                    'viewPath' => '@app/modules/user/views/backend',
                    'modules' => [
                        'common' => [
                            'class' => 'app\modules\user\Module',
                            'controllerNamespace' => 'app\modules\user\controllers\common',
                            'viewPath' => '@app/modules/user/views/common',
                        ],
                    ],
                ],                            
            ]
        ],

Но мне все равно кажется решение не очень, может есть что-то лучше?

Ответить

 

Spirit Absolute

С ролями "урлменеджера" при таком разделении фиг разберешься...
Дмитрий, подскажите как избавиться от default?
/admin/user/default/create
/admin/user/default/1
/admin/user/default/1/update

Ответить

 

Spirit Absolute

Зачем нужен backend контроллер modules/user/controllers/backend/UserController.php ?

Ответить

 

Дмитрий Елисеев

В yii2-app-advanced для логина в backend.

Ответить

 

slo_nik

Добрый вечер.
Присоединяюсь к предыдущему автору комментария, как убрать из адреса "default"?
Как я понимаю, не срабатывают правила для кнопок управления(view, update and delete), при переходе по этим адресам добавляется default и в адресной строке не так "красиво"

admin/user/default/25
admin/user/default/25/update
admin/user/default/25/delete

Ещё удалил из правил

'<_m:[\w\-]+>/<_c:[\w\-]+>/<_a:[\w-]+>' => '<_m>/<_c>/<_a>',


если это правило присутствует, то при переходе на страницу профиля в адрес попадает всё тот же default

Перепроверил ещё раз, но не могу понять, как избавиться от default.
Подскажите, пожалуйста.

Ответить

 

Дмитрий Елисеев

Добавил такие правила:

'<_m:[\w\-]+>/<id:\d+>' => '<_m>/default/view',
'<_m:[\w\-]+>/<id:\d+>/<_a:[\w-]+>' => '<_m>/default/<_a>',
Ответить

 

slo_nik

Благодарю.
Вы меня опередили)))
После того как написал комментарий, посидел, подумал, пришёл к такому же решению.
В итоге получилось следующее:

        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
                [
                  'class' => 'yii\web\GroupUrlRule',
                  'prefix' => 'admin',
                  'routePrefix' => 'admin',
                  'rules' => [
                     //'' => 'default/index',
                     '<_m:[\w\-]+>/<id:\d+>' => '<_m>/default/view',
                     '<_m:[\w\-]+>/<id:\d+>/<_a:[\w\-]+>' => '<_m>/default/<_a>',
                     '<_m:[\w\-]+>' => '<_m>/default/index',
                     '<_m:[\w\-]+>/<_a:[\w\-]+>' => '<_m>/default/<_a>',
                     '<_m:[\w\-]+>/<_c:[\w\-]+>' => '<_m>/<_c>/index',
                  ],
                ],
                '' => 'main/default/index',
                'contact' => 'main/contact/index',
                '<_a:(error)>' => 'main/default/<_a>',
                '<_a:(login|logout|signup|password-rest-request|email-confirm|reset-password)>' => 'users/default/<_a>',

                //'<_m:[\w\-]+>/<_c:[\w\-]+>/<_a:[\w\-]+>' => '<_m>/<_c>/<_a>',
                '<_m:[\w\-]+>/<_c:[\w\-]+>/<id:\d+>' => '<_m>/<_c>/view',
                '<_m:[\w\-]+>/<_c:[\w\-]+>/<id:\d+>/<_a:[\w\-]+>' => '<_m>/<_c>/<_a>',
                '<_m:[\w\-]+>' => '<_m>/default/index',
                '<_m:[\w\-]+>/<_c:[\w\-]+>' => '<_m>/<_c>/index'
            ],
        ],

Вот только как будут работать эти правила для других модулей, которые будут добавляться? Ну как говорится - посмотрим)
И тут такой вопрос возник. А не проще ли для каждого модуля выносить правила в класс Bootstrap.php?

Ответить

 

Дмитрий Елисеев

Да, можно в Bootstrap.php вынести. Но учтите, чтобы при сборке нужный порядок сохранился.

Ответить

 

slo_nik

Вы имеете ввиду порядок подключения модулей?

И ещё вопрос. Зачем переместили все модули из common.php в web.php?

Ответить

 

Дмитрий Елисеев

Да, порядок добавления правил в Yii::$app->urlManager->addRules([...], $prepend);

Чтобы раздельно управлять модулями в console.php и web.php.

Ответить

 

slo_nik

Тогда получается, что в правилах для модуля rules оставить пустым или вообще не указывать?

'rules' => [
    [
        'class' => 'yii\web\GroupUrlRule',
        'prefix' => 'admin',
        'routePrefix' => 'admin',
        'rules' => [],
    ],
Ответить

 

Роман

Здравствуйте.

Выполнил всё до абзаца "Шаблон и хлебные крошки"

Валятся ошибки

Class 'app\components\UserIdentity' not found

Не помню, чтобы где-то создавали этот класс. Соответственно там его нет.
Но на Github е файл есть.

И в настройках config/web путь к нему прописан

'components' => [
        'user' => [
            'identityClass' => 'app\components\UserIdentity',
            'enableAutoLogin' => true,

Что я мог упустить?

Ответить

 

Дмитрий Елисеев

UserIdentity появится только в статье про RBAC на коммите Separated user identity. Откройте историю и нажмите <> справа от любого предыдущего коммита для обзора старого кода.

Ответить

 

Роман

Так и сделал. Видимо с Гита скопировал часть кода, относящуюся к следующей главе.
Спасибо.

Ответить

 

slo_nik

Доброй ночи.
Перечитываю в который раз статью, делаю модули)))
Но вот такой вопрос появился.
Есть модуль, при его подключении(backend) надо в шаблоне admin дописывать пункт меню руками и добавлять перевод этого пункта в messages admin-модуля.
Можно ли это как-то автоматизировать?

Ответить

 

Дмитрий Елисеев

Проходите по Yii::$app->getModules() циклом и собирайте меню.

Ответить

 

Денис

Здравствуйте, Дмитрий!
Вопрос следующий: тут у вас структура модулей такая, что например модуль user с бекендом и фронтендом. А как это разруть в урл менеджере? Пути я имею ввиду, контроллеры и там и там Default называются.

Заранее благодарю.

Ответить

 

Дмитрий Елисеев

/user/default/index
/admin/user/default/index

Ответить

 

Денис

Ладно, перефразирую. Я решил именно такой вариант с модулями использовать, как вы и преложили в статье. Сделал бекенд с независимыми модулями пока. Основное ядро составляет у меня модуль admin. Хочу сделать в этом модуле индекс фронтенда. А остальные модули как вы и описали соответственно в каждом модуле фронтенд будет по папкам frontend раскидан. И чет туплю - не понимаю как урл менеджер написать под них сейчас у меня он такой:

'' => 'admin/default/index',
'contact' => 'main/contact/index',
'<_a:error>' => 'main/default/<_a>',
'<_a:(login|logout)>' => 'admin/default/<_a>',
'admin/default/login' => 'admin/default/login',
'admin/login' => 'admin/default/login',
'admin/default/index' => 'admin/default/index',
'admin/<_m:[\w\-]+>' => '<_m>/default/index',
'admin/<_m:[\w\-]+>/<_c:[\w\-]+>/<_a:[\w\-]+>' => '<_m>/<_c>/<_a>',
'admin/<_m:[\w\-]+>/<_c:[\w\-]+>' => '<_m>/<_c>/index'
Ответить

 

Денис

Первые две строчки можно во внимание не брать. Именно с первой я пока не могу понять как быть - сейчас она тычется в индекс бекенда

Ответить

 

Дмитрий Елисеев
'' => 'main/default/index',
'admin' => 'admin/default/index',
...
Ответить

 

Сергей Ильичев

Я так понимаю это все на основе базового шаблона делалось?

А в расширенном шаблоне, без разницы где хранить модули? То есть я могу создать где-нибудь в common все модули со структурой:

-modul-name
--Controllers
---Frontend
---Backend
--Modeles
--Views
---Frontend
---backend
...

и работать так? Зачем тогда базовое разделение реализованное в расширенном шаблоне?

Я вижу, что по сути такой подход более привлекателен, так как при переносе модуля в другой проект, не нужно его собирать по частям из двух или вернее трех директорий. Но меня просто смущает то, что frontend и backend вообще по сути будут пустовать. Это, если я, конечно, правильно понял идею?

И на сколько, как вы думаете, такой подход актуален, без второй части с событиями, описанной в вашей другой статье - Связи независимых модулей? Тут мы как бы избавляемся от необходимости держать frontend и backend части в отдельных местах, но внешние зависимости из-за моделей остаются. И как вы писали их можно убрать создав отдельные модели для каждого модуля. То есть получается одно без другого в принципе не жизнеспособно)

И подскажите, как на ваш взгляд такая схема размещения:

Backend
-Modules
--User
---Controllers
----UserController.php
---Views

Common
- Models
--User.php

Frontend
-Modules
--User
---Controllers
----UserController.php
---Views

Совсем бед практик или все же имеет право на жизнь? Я просто не так давно изучаю yii, и предполагал, что это некий стандарт. Но последнее время все чаще стал задумываться о том, как уменьшить связь между модулями, так и набрел ан ваши статьи)

Еще дополнительно хочу попросить, если будет время напишите что-нибудь о создании и публикации своих расширений для yii2)

Спасибо за труд и интересные статьи)

Ответить

 

Дмитрий Елисеев

> Я так понимаю это все на основе базового шаблона делалось?

Да.

> А в расширенном шаблоне, без разницы где хранить модули?

Можете, например, создать папку modules в корне и прописать её в bootstrap.php:

Yii::setAlias('@modules', dirname(dirname(__DIR__)) . '/modules');

> Зачем тогда базовое разделение реализованное в расширенном шаблоне?

Если не использовать модули, то контролеры и модели удобно раскладывать по приложениям. Ну и чтобы работало на разных поддоменах с разными настройками.

> Но меня просто смущает то, что frontend и backend вообще по сути будут пустовать.

Да, из оригинального кода basic и advanced останутся только шаблоны и SiteController. Остальное уйдёт в модули.

> И подскажите, как на ваш взгляд такая схема размещения...

Как Вы и заметили про "при переносе модуля в другой проект... собирать по частям из трех директорий". Ещё у Вас возможна большая свалка в common/models. Кому как удобнее.

> И на сколько, как вы думаете, такой подход актуален, без второй части с событиями, описанной в вашей другой статье - Связи независимых модулей? Тут мы как бы избавляемся от необходимости держать frontend и backend части в отдельных местах, но внешние зависимости из-за моделей остаются.

Мне удобнее, когда модуль сделан одной папкой, а не тремя/четырьмя. Хоть со связями, хоть без.

> Я просто не так давно изучаю yii, и предполагал, что это некий стандарт.

У Yii девиз "Быстро и дёшево", а не "Приятно и качественно". Никаких стандартов в нём нет и не появится. Ни во фреймворке, ни в документации, ни в статьях. Поэтому хорошему от него не научитесь.

> Еще дополнительно хочу попросить, если будет время напишите что-нибудь о создании и публикации своих расширений для yii2

Уже написано про AuthManager и генератор фикстур.

Ответить

Оставить комментарий

Войти | Завести аккаунт


(никто не увидит)



Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>