前面文章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的核心代码,会发现很多精髓。。