OctoberCMS系统分析之模块加载过程

2018-12-04 548

前面文章OctoberCMS系统分析之启动过程中已经提过OctoberCMS模块是基于ServiceProvider实现的,OctoberCMS自带了2个系统模块,分别是modules目录里的cms前台模块和backend后台模块,本文以cms前台模块为例来详细分析一下模块的加载过程,大致包括路由注册、控制器加载和视图数据渲染三个方面。

路由Router

首先,模块的routes.php调用Application实例的before()方法,该方法体里注册路由规则,代码如下:

/**
 * 调用App实例的before方法
 */
App::before(function ($request) {
    /*
     * Extensibility
     */
    Event::fire('cms.beforeRoute'); //注册路由之前扩展事件
    /*
     * The CMS module intercepts all URLs that were not
     * handled by the back-end modules.
     */
    Route::any('{slug}', 'Cms\Classes\CmsController@run')->where('slug', '(.*)?')->middleware('web');  //注册路由
    /*
     * Extensibility
     */
    Event::fire('cms.route'); //注册路由之后扩展事件
});

vendor/october/rain/src/Foundation/Application.php类的registerBaseServiceProviders()方法重新注册了自定义路由类,代码如下:

/**
 * Register all of the base service providers.
 *
 * @return void
 */
protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));
    $this->register(new LogServiceProvider($this));
    $this->register(new RoutingServiceProvider($this)); //注册自定义路由
    $this->register(new MakerServiceProvider($this));
}

vendor/october/rain/src/Router/RoutingServiceProvider.php类重写registerRouter()方法绑定router实例对象CoreRouter,代码如下:

/**
 * Register the router instance.
 *
 * @return void
 */
protected function registerRouter()
{
    $this->app->singleton('router', function ($app) {
        return new CoreRouter($app['events'], $app); //实例化自定义的路由类
    });
}

vendor/october/rain/src/Router/CoreRouter.php类定义before($callback)方法并注册监听事件router.before,同时在dispatch()方法中触发router.before事件,进而执行$callback回调闭包函数,代码如下:

/**
 * Dispatch the request to the application.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
 */
public function dispatch(Request $request)
{
    $this->currentRequest = $request;
    $this->events->fire('router.before', [$request]); //触发调用事件回调闭包函数
    $response = $this->dispatchToRoute($request);
    $this->events->fire('router.after', [$request, $response]);
    return $response;
}

/**
 * Register a new "before" filter with the router.
 *
 * @param  string|callable  $callback
 * @return void
 */
public function before($callback)
{
    $this->events->listen('router.before', $callback);  //注册事件监听
}
控制器Controller

通过上面的路由分析中,我们已经知道OctoberCMS路由规则非常简单,即全部URL请求指向控制器CmsController的run方法,run方法App::make创建Controller控制器实例,Controller控制器构造函数会加载当前主题信息、初始化主题路由类Router和初始化组件容器类PartialStack,代码如下:

public function __construct($theme = null)
{
    $this->theme = $theme ? $theme : Theme::getActiveTheme();
    if (!$this->theme) {
        throw new CmsException(Lang::get('cms::lang.theme.active.not_found'));
    }

    $this->assetPath = Config::get('cms.themesPath', '/themes').'/'.$this->theme->getDirName();
    $this->router = new Router($this->theme); //实例化路由
    $this->partialStack = new PartialStack; //实例化组件容器
    $this->initTwigEnvironment(); //初始化Twig模板环境

    self::$instance = $this;
}

Controller的run方法,这个方法根据当前请求url经过Router的findByUrl方法获得\Cms\Classes\Page对象实例,然后调用runPage方法渲染模板视图文件,并得到响应结果,最后通过Response::make输出结果。

视图View

主题模板文件加载是在Router的loadUrlMap()方法中通过theme->listPages()处理的,并把模板信息赋值到Page对象实例,Controller的runPage方法读取当前页面Page模板信息,然后加载页面Layout和components组件、处理AJAX方法、解析twig模板内容,具体代码如下:

/**
 * Runs a page directly from its object and supplied parameters.
 * @param \Cms\Classes\Page $page Specifies the CMS page to run.
 * @return string
 */
public function runPage($page, $useAjax = true)
{
    $this->page = $page;

    /*
     * If the page doesn't refer any layout, create the fallback layout.
     * Otherwise load the layout specified in the page.
     */
    if (!$page->layout) {
        $layout = Layout::initFallback($this->theme);
    }
    elseif (($layout = Layout::loadCached($this->theme, $page->layout)) === null) {
        throw new CmsException(Lang::get('cms::lang.layout.not_found_name', ['name'=>$page->layout]));
    }

    $this->layout = $layout;

    /*
     * The 'this' variable is reserved for default variables.
     */
    $this->vars['this'] = [
        'page'        => $this->page,
        'layout'      => $this->layout,
        'theme'       => $this->theme,
        'param'       => $this->router->getParameters(),
        'controller'  => $this,
        'environment' => App::environment(),
        'session'     => App::make('session'),
    ];

    /*
     * Check for the presence of validation errors in the session.
     */
    $this->vars['errors'] = (Config::get('session.driver') && Session::has('errors'))
        ? Session::get('errors')
        : new \Illuminate\Support\ViewErrorBag;

    /*
     * Handle AJAX requests and execute the life cycle functions
     */
    $this->initCustomObjects();

    $this->initComponents();

    /*
     * Give the layout and page an opportunity to participate
     * after components are initialized and before AJAX is handled.
     */
    if ($this->layoutObj) {
        CmsException::mask($this->layout, 300);
        $this->layoutObj->onInit();
        CmsException::unmask();
    }

    CmsException::mask($this->page, 300);
    $this->pageObj->onInit();
    CmsException::unmask();

    /*
     * Extensibility
     */
    if ($event = $this->fireSystemEvent('cms.page.init', [$page])) {
        return $event;
    }

    /*
     * Execute AJAX event
     */
    if ($useAjax && $ajaxResponse = $this->execAjaxHandlers()) {
        return $ajaxResponse;
    }

    /*
     * Execute postback handler
     */
    if (
        $useAjax &&
        ($handler = post('_handler')) &&
        ($this->verifyCsrfToken()) &&
        ($handlerResponse = $this->runAjaxHandler($handler)) &&
        $handlerResponse !== true
    ) {
        return $handlerResponse;
    }

    /*
     * Execute page lifecycle
     */
    if ($cycleResponse = $this->execPageCycle()) {
        return $cycleResponse;
    }

    /*
     * Extensibility
     */
    if ($event = $this->fireSystemEvent('cms.page.beforeRenderPage', [$page])) {
        $this->pageContents = $event;
    }
    else {
        /*
         * Render the page
         */
        CmsException::mask($this->page, 400);
        $this->loader->setObject($this->page);
        $template = $this->twig->loadTemplate($this->page->getFilePath());
        $this->pageContents = $template->render($this->vars);
        CmsException::unmask();
    }

    /*
     * Render the layout
     */
    CmsException::mask($this->layout, 400);
    $this->loader->setObject($this->layout);
    $template = $this->twig->loadTemplate($this->layout->getFilePath());
    $result = $template->render($this->vars);
    CmsException::unmask();

    return $result;
}

本文只是粗略的分析了一下OctoberCMS的MVC执行过程,其中还有很多细节没有描述,大家可以按照这个指引慢慢撸撸OC的核心代码,会发现很多精髓。。

发表评论