Корректно ли подменять UrlManager после того, как сработал Request->resolve()

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Аватара пользователя
delvin
Сообщения: 85
Зарегистрирован: 2009.11.13, 15:29

Корректно ли подменять UrlManager после того, как сработал Request->resolve()

Сообщение delvin »

У меня появилась довольно извращенная задача.
В приложении одна точка входа - index.php
При этом нужно три (пока три UrlManager): UrlManagerFrontend, UrlManagerBackend, UrlManagerApi
Которые соответсвуют префиксам в роутах '' (без префикса), 'backend' и 'api' соответственно.
Т.е., например роуты:

Код: Выделить всё

'site/main/index' - UrlManagerFrontend

Код: Выделить всё

'backend/site/main/index' - UrlManagerBackend

Код: Выделить всё

'api/site/main/index' - UrlManagerFrontend
Что бы всегда можно было сделать

Код: Выделить всё

Yii::$app->urlManager->getManager('backend')->createUrl('site/main/index'); // backend/site/main/index
Yii::$app->urlManager->getManager('api')->createUrl('site/main/index'); // api/site/main/index
Yii::$app->urlManager->getManager('frontend')->createUrl('site/main/index'); // site/main/index
Естественно, запросы при этом должны корректно парсится.
А вызов Yii::$app->urlManager->createUrl('site/main/index') будет использовать актуальный UrlManager в зависимости от префикса (или его отсутствия).

Собственно, на коленях быстро накидал вот такую реализацию:
Абстрактный класс, от которого наследуются UrlManagerFrontend, UrlManagerBackend, UrlManagerApi

Код: Выделить всё

abstract class BaseUrlManager extends \yii\web\UrlManager
{
    /**
     * @var \yii\web\UrlManager[]
     */
    public $managers = [];
    public $prefix;

    /**
     * @param string $name
     * @return BaseUrlManager|\yii\web\UrlManager
     */
    public function getManager($name)
    {
        if (!isset($this->managers[$name])) {
            throw new InvalidParamException(Yii::t('app', '"{name}" url manager is absent.', ['name' => $name]));
        }

        if (is_object($this->managers[$name])) {
            $manager = $this->managers[$name];
        } else if ($this->managers[$name]['class'] == get_called_class()) {
            $manager = $this;
        } else {
            $manager = Yii::createObject($this->managers[$name]);
            $this->managers[$name] = $manager;
        }

        return $manager;
    }


    /**
     * @inheritdoc
     */
    public function createUrl($params)
    {
        if ($this->prefix) {
            $route = trim($params[0], '/');
            $route = sprintf('%s/%s', $this->prefix, $route);
            $params[0] = $route;
        }

        return parent::createUrl($params);
    }
}
И UrlManager, с которым стартует приложение, и после первого выполнения parseRequest он подменяет себя на соответствующий класс

Код: Выделить всё

class UrlManager extends \yii\web\UrlManager
{
    public $managers = [];

    protected $managersDefaults = [
        'frontend' => [
            'class' => 'app\extensions\web\urlManager\UrlManagerFrontend',
            'prefix' => '',
        ],
        'backend' => [
            'class' => 'app\extensions\web\urlManager\UrlManagerBackend',
            'prefix' => 'backend',
        ],
        'api' => [
            'class' => 'app\extensions\web\urlManager\UrlManagerApi',
            'prefix' => 'api',
        ],
    ];


    /**
     * @inheritdoc
     */
    public function __construct(array $config = [])
    {
        if (isset($config['managers'])) {
            $this->managers = $config['managers'];
            unset($config['managers']);
        }

        $this->configureManagers($config);

        parent::__construct($config);
    }


    /**
     * @inheritdoc
     */
    public function parseRequest($request)
    {
        $result = parent::parseRequest($request);
        list($route, $params) = $result;

        $config = $this->managers['frontend'];

        foreach ($this->managers as $name => $manager) {
            $prefix = $manager['prefix'];

            if ($prefix) {
                $matched = (strpos($route, $prefix) === 0);
                if ($matched) {
                    $config = $this->managers[$name];
                    $route = str_replace($name, '', $route);
                    $route = trim($route, '/');
                    $result = [$route, $params];
                }
            }
        }
        $config['managers'] = $this->managers;

        \Yii::$app->set('urlManager', $config);


        return $result;
    }

    /**
     * @param array $config
     */
    private function configureManagers($config)
    {
        $this->managers = ArrayHelper::merge($this->managersDefaults, $this->managers);

        foreach ($this->managers as $name => $managerConfig) {
            $this->managers[$name] = ArrayHelper::merge($config, $managerConfig);
            $this->managers[$name]['rules'] = $this->managers[$name]['rules'] ?? [];
            $this->managers[$name]['rules'] = array_filter($this->managers[$name]['rules'], function ($value) {
                return !empty($value);
            });
//            if ($name == ApplicationHelper::APP_TYPE_FRONTEND) {
//                $this->managers[$name]['prefix'] = '';
//            } else {
//                $this->managers[$name]['prefix'] = $name;
//            }
        }
    }
}
Как видно из кода, у стартового UrlManager есть свои, глобальные настройки, которые объединяются с кастомными настройками каждого менеджера.

На оформление кода внимание не обращайте, мне главное - нужно было проверить работоспособность идеи.
Такая реализация работает.
Но меня тревожит, на сколько она корректна, не не вылезут ли в процессе работы какие-то косяки?
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Корректно ли подменять UrlManager после того, как сработал Request->resolve()

Сообщение samdark »

Не должны вылезти. Технически с этим всё нормально.
Ответить