luyadev/luya

View on GitHub
core/base/ModuleReflection.php

Summary

Maintainability
A
25 mins
Test Coverage
A
100%
<?php

namespace luya\base;

use luya\web\Controller;
use luya\web\Request;
use luya\web\UrlManager;
use Yii;
use yii\base\BaseObject;
use yii\base\InvalidConfigException;
use yii\web\NotFoundHttpException;

/**
 * Run any route inside the provided module.
 *
 * In order to run a module reflection route You have to instantiate the module via the Yii::createObject method
 *
 * ```php
 * $reflection = Yii::createObject(['class' => ModuleReflection::className(), 'module' => $module]);
 * ```
 *
 * Now you can pass optional parameters before the run process, for example to define what controller action you may run:
 *
 * ```php
 * $reflection->defaultRoute("my-controller", "the-action", ['param1' => 'foo']);
 * ```
 *
 * Now in order to return the content of the modules route execute the run method:
 *
 * ```php
 * $content = $reflection->run();
 * ```
 *
 * @property \luya\base\Module $module The module where the route should be run from.
 * @property string $suffix The suffix which should be used to attach to the url rules.
 *
 * @author Basil Suter <basil@nadar.io>
 * @since 1.0.0
 */
class ModuleReflection extends BaseObject
{
    /**
     * @var \luya\web\Request Request object from DI-Container.
     */
    public $request;

    /**
     * @var \luya\web\UrlManager UrlManager object from DI-Container.
     */
    public $urlManager;

    /**
     * @var \yii\base\Controller|null The controller paramter is null until the [[run()]] method has been applied.
     */
    public $controller;

    private $_defaultRoute;

    /**
     * Class constructor in order to consum from DI Container.
     *
     * @param Request $request
     * @param UrlManager $urlManager
     * @param array $config
     */
    public function __construct(Request $request, UrlManager $urlManager, array $config = [])
    {
        $this->request = $request;
        $this->urlManager = $urlManager;
        parent::__construct($config);
    }

    /**
     * @inheritdoc
     */
    public function init()
    {
        if ($this->module === null) {
            throw new InvalidConfigException('The module attribute is required and can not be null.');
        }

        // add the module specific url rules to the url manager
        $this->urlManager->addRules($this->module->urlRules, true);
    }

    private $_module;

    /**
     * Setter for the module property.
     *
     * @param Module $module
     */
    public function setModule(Module $module)
    {
        $this->_module = $module;
    }

    /**
     * Getter for the module property.
     *
     * @return \luya\base\Module
     */
    public function getModule()
    {
        return $this->_module;
    }

    private $_suffix;

    /**
     * Setter for the suffix property.
     *
     * @param string $suffix
     */
    public function setSuffix($suffix)
    {
        $this->_suffix = $suffix;
        $this->request->setPathInfo(implode('/', [$this->module->id, $suffix]));
    }

    /**
     * Getter for the suffix property.
     *
     * @return string
     */
    public function getSuffix()
    {
        return $this->_suffix;
    }

    private $_requestRoute;

    /**
     * Determine the default route based on current defaultRoutes or parsedRequested by the UrlManager.
     *
     * @return array An array with
     * + route: The path/route to the controller
     * + args: If the url has no params, it returns all params from get request.
     * + originalArgs: The arguments (params) parsed from the url trogh url manager
     *
     * @see Related problems and changes:
     * + https://github.com/luyadev/luya/issues/1885
     * + https://github.com/luyadev/luya/issues/1267
     * + https://github.com/luyadev/luya/issues/754
     */
    public function getRequestRoute()
    {
        if ($this->_requestRoute !== null) {
            return $this->_requestRoute;
        }

        if ($this->_defaultRoute !== null && empty($this->getSuffix())) {
            $array = $this->_defaultRoute;
        } else {
            // parse request against urlManager
            $route = $this->urlManager->parseRequest($this->request);
            // return human readable array
            $array = [
                'route' => $route[0],
                'args' => $route[1],
                'originalArgs' => $route[1],
            ];
        }
        // resolve the current route by the module
        $array['route'] = $this->module->resolveRoute($array['route']);

        // if there are no arguments, all get params are assigned. In order to use the original arguments from parse request use `originalArgs` instead of `args`.
        if (empty($array['args'])) {
            $array['args'] = $this->request->get();
        }

        // @see https://github.com/luyadev/luya/issues/1267
        if ($this->_defaultRoute !== null) {
            $array['args'] = array_merge($this->_defaultRoute['args'], $array['args']);
        }

        $this->_requestRoute = $array;

        return $array;
    }

    /**
     * Setter method for the requested route
     * @param string $route
     * @param array $args
     */
    public function setRequestRoute($route, array $args = [])
    {
        $this->_requestRoute = ['route' => $route, 'args' => $args, 'originalArgs' => $args];
    }

    /**
     * Inject a defaultRoute.
     *
     * @param string $controller
     * @param string $action
     * @param array $args
     */
    public function defaultRoute($controller, $action = null, array $args = [])
    {
        $this->_defaultRoute = [
            'route' => implode('/', [$this->module->id, $controller, (empty($action)) ? 'index' : $action]),
            'args' => $args,
            'originalArgs' => $args,
        ];
    }

    /**
     * Returns the url rule parameters which are taken from the requested route.
     *
     * @return array
     */
    public function getUrlRule()
    {
        $request = $this->getRequestRoute();

        return [
            'module' => $this->module->id,
            'route' => $this->module->id . '/' . $request['route'],
            'params' => $request['originalArgs'],
        ];
    }

    /**
     * Run the route based on the values.
     *
     * @return string|\yii\web\Response The response of the action, can be either a string or an object from response.
     * @throws \yii\web\NotFoundHttpException
     */
    public function run()
    {
        $requestRoute = $this->getRequestRoute();
        // create controller object
        $controller = $this->module->createController($requestRoute['route']);
        // throw error if the requests request does not returns a valid controller object
        if (!$controller || !isset($controller[0]) || !is_object($controller[0])) {
            throw new NotFoundHttpException(sprintf("Unable to create controller '%s' for module '%s'.", $requestRoute['route'], $this->module->id));
        }

        Yii::debug('LUYA module run module "'.$this->module->id.'" route ' . $requestRoute['route'], __METHOD__);

        $this->controller = $controller[0];
        $originalController = Yii::$app->controller;

        /**
         * Override the current application controller in order to ensure current() url handling which is used
         * for relativ urls/rules.
         *
         * @see https://github.com/luyadev/luya/issues/1730
         */
        $this->controller->on(Controller::EVENT_BEFORE_ACTION, function ($event) {
            Yii::$app->controller = $this->controller;
        });
        /**
         * Restore the original controller instance after rendering.
         *
         * @see https://github.com/luyadev/luya/issues/1768
         */
        $this->controller->on(Controller::EVENT_AFTER_ACTION, function ($event) use ($originalController) {
            Yii::$app->controller = $originalController;
        });

        // run the action on the provided controller object
        return $this->controller->runAction($controller[1], $requestRoute['args']);
    }
}