学习yii2.0框架阅读代码(十一)

vendor/yiisoft/yii2/base/Module. php(续)

    /**
     * 该方法解析指定的路线和创建相应的子模块(s),控制器和行动
     * This method parses the specified route and creates the corresponding child module(s), controller and action
     * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
     * 如果路径为空,该方法将使用[[defaultRoute]]
     * @param string $route the route that specifies the action.
     * @param array $params the parameters to be passed to the action
     * @return mixed the result of the action.
     * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
     */
    public function runAction($route, $params = [])
    {
        // 创建controller,获取controller的实例和action的ID
        $parts = $this->createController($route);
        if (is_array($parts)) {
            /* @var $controller Controller */
            list($controller, $actionID) = $parts;
            // 保留下app中绑定的controller
            $oldController = Yii::$app->controller;
            // 将当前controller绑定到app上
            Yii::$app->controller = $controller;
            $result = $controller->runAction($actionID, $params);
            // 还原会app之前的绑定的controller
            Yii::$app->controller = $oldController;

            return $result;
        } else {
            $id = $this->getUniqueId();
            throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
        }
    }

    /**
     * 新建一个控制器实例基于给定的路线.
     *
     * 路线应该是相对于这个模块。该方法实现了以下算法
     * to resolve the given route:
     *
     * 1. If the route is empty, use [[defaultRoute]];
     * 2. If the first segment of the route is a valid module ID as declared in [[modules]],
     *    call the module's `createController()` with the rest part of the route;
     * 3. If the first segment of the route is found in [[controllerMap]], create a controller
     *    based on the corresponding configuration found in [[controllerMap]];
     * 4. The given route is in the format of `abc/def/xyz`. Try either `abcDefController`
     *    or `abcdefXyzController` class within the [[controllerNamespace|controller namespace]].
     *
     * If any of the above steps resolves into a controller, it is returned together with the rest
     * part of the route which will be treated as the action ID. Otherwise, false will be returned.
     *
     * @param string $route the route consisting of module, controller and action IDs.
     * @return array|boolean If the controller is created successfully, it will be returned together
     * with the requested action ID. Otherwise false will be returned.
     * @throws InvalidConfigException if the controller class and its file do not match.
     */
    public function createController($route)
    {
        if ($route === '') {
            $route = $this->defaultRoute;
        }

        // double slashes or leading/ending slashes may cause substr problem
        // 如果有双斜线或者结束的斜线,可能会引起问题
        $route = trim($route, '/');
        if (strpos($route, '//') !== false) {
            return false;
        }

        if (strpos($route, '/') !== false) {
            // 如果存在斜线,就根据斜线分割route,并限制最多分割成两个
            list ($id, $route) = explode('/', $route, 2);
        } else {
            // 不存在,就直接赋值给$id
            $id = $route;
            $route = '';
        }

        // module and controller map take precedence
        if (isset($this->controllerMap[$id])) {
            // 如果controllerMap中存在相应的$id,就使用Yii::createObject去创建该controller的实例
            $controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
            return [$controller, $route];
        }
        // 根据id获取module
        $module = $this->getModule($id);
        if ($module !== null) {
            // 获取到module,就递归调用相应module的createController去创建controller的实例
            return $module->createController($route);
        }

        // 如果取到了module,就需要继续去取controller的ID
        // 再次根据斜线分割
        if (($pos = strrpos($route, '/')) !== false) {
            // 如果有斜线,就将module的ID和后面截取出的内容一起拼成ID
            $id .= '/' . substr($route, 0, $pos);
            // 剩余的赋给route
            $route = substr($route, $pos + 1);
        }

        // 然后根据ID去创建controller的实例
        $controller = $this->createControllerByID($id);
        // 未创建成功,并且route不为空
        if ($controller === null && $route !== '') {
            // 将route拼接到ID中,再次去创建controller出的实例
            $controller = $this->createControllerByID($id . '/' . $route);
            $route = '';
        }

        return $controller === null ? false : [$controller, $route];
    }

    /**
     * 创建一个控制器基于给定控制器ID.
     *
     * The controller ID is relative to this module. The controller class
     * should be namespaced under [[controllerNamespace]].
     *
     * Note that this method does not check [[modules]] or [[controllerMap]].
     *
     * @param string $id the controller ID
     * @return Controller the newly created controller instance, or null if the controller ID is invalid.
     * @throws InvalidConfigException if the controller class and its file name do not match.
     * This exception is only thrown when in debug mode.
     */
    public function createControllerByID($id)
    {
        // $id是controller的ID,但它前面可能带有module的ID,而且可能是多层module ID
        // 取到最右边的斜线位置,可以分离出controller的名字
        $pos = strrpos($id, '/');
        if ($pos === false) {
            // 没有斜线,$id就是controller的名字
            $prefix = '';
            $className = $id;
        } else {
            // 有斜线,斜线前的是module的ID,斜线后是controller的名字
            $prefix = substr($id, 0, $pos + 1);
            $className = substr($id, $pos + 1);
        }

        if (!preg_match('%^[a-z][a-z0-9\-_]*$%', $className)) {
            // 如果controller的名字不符合命名的规范,就返回null
            // 以小写字母开头,可以包含数字/小写字母/右斜线/中划线/下划线
            return null;
        }
        if ($prefix !== '' && !preg_match('%^[a-z0-9_/]+$%i', $prefix)) {
            // 如果module的ID不符合module的命名规范,就返回null
            // 可以包含数字/小写字母/左斜线/下划线
            return null;
        }

        // 先将-替换为空格,再将每个单词的首字母大写,再将空格替换为空字符串,最后拼接上'Controller',就拿到了类名(不带namespace)
        $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $className))) . 'Controller';
        // 根据application和module中定义的controllerNamespace,拼出controller的类名,含namespace
        $className = ltrim($this->controllerNamespace . '\' . str_replace('/', '\', $prefix)  . $className, '\');
        if (strpos($className, '-') !== false || !class_exists($className)) {
            // 如果存在中划线或者类不存在,即返回null
            return null;
        }

        // is_subclass_of — 如果此对象是该类的子类,则返回 TRUE
        // 判断是不是'yiiaseController'的子类,是的话,就创建相应类的实例
        if (is_subclass_of($className, 'yiiaseController')) {
            return Yii::createObject($className, [$id, $this]);
        } elseif (YII_DEBUG) {
            throw new InvalidConfigException("Controller class must extend from \yii\base\Controller.");
        } else {
            return null;
        }
    }
原文地址:https://www.cnblogs.com/xwzj/p/5415113.html