Joomla的启动过程
当使用Joomla来生成一个页面时,有许多不同的步骤来到达最终的结果:发送HTML页面到浏览器。这些步骤被称为启动bootstrap
。注意不是那个前端框架Twitter Bootstrap。
启动Joomla——Initialise
Joomla启动CMS的第一步是在defines.php
中定义需要包含的PHP文件。之后,Joomla就可以载入各种类库了。configuration.php
同时被加载。这样Joomla才知道如何连接数据库。当所需的文件都被加载后,Joomla的应用类JApplication
被实例化,并调用execute()
方法。一旦application
实例化后,数据库开始连接,session开始,同时JFactory::getUser()
方法可以使用了。
此时,插件被加载。因为数据库连接上了,所有的启用插件从#__extensions
数据表中被读取。因为用户会话开始了,不符合ACL规则的插件被跳过。通过一个查询命令,所有的属于同一类型的插件可以被同时加载。插件记录在Joomla应用中。
下一步,系统插件通过JPluginHelper::importPlugin(′system′)
加载。此时,插件记录列表用来定位和包含每个系统插件文件。插件被实例化,并且存储在** event dispatcher**事件分发器的列表中。
之后,onAfterInitalise
事件调用,每个系统插件的onAfterInitialise()
方法被调用。
请求路由化
现在Joomla核心已经初始化并且运行了。Joomla需要解析URL,处理请求。JRouter
类被调用来解析当前的URL($_SERVER[′REQUEST_URI′]
。现在的URL通常是SEF URL。需要将其与Menu-item的别名进行匹配。大部分时候Menu-item指向一个组件。当请求被分发后,这个组件将会被调用。有时候请求的地址和Menu-item的别名一样,JRouter
类就会直接调用这个菜单的URL参数。
比如,Menu-item的别名是blog,就是完全匹配*/blog这个URL。但是,如果URL是/blog/23-example-article*,URL余下的部分23-example-article就需要继续被解析。此时JRouter
类会调用组件的router.php继续解析。解析出的会被插入到JApplication->input对象中
Itemid = 3
option = com_content
view = article
id = 23
一旦这些工作完成,SEF URL就有效的转变成了系统请求,然后事件onAfterRoute就会触发。
事件onAfterRoute之后,应用的*authorise() *会被调用,用来检测当前用户是否有权限进入这个页面。
将请求分发到组件
下一步是分发请求到组件。input变量包含了这个页面请求的类型:RSS,HTML还是其他。根据这个类型,JDocument的一个特定子类被实例化,比如JDocumentHtml用来实例化HTML页面。一些基本的信息,比如页面标题、描述、立即插入到文档中。但是,此时最重要的任务是调用组件继续解析。换句话说就是,将请求分发到组件。
通过调用JComponentHelper::renderComponent()
来生成组件输出。组件的入口文件被调用。以com_content为例,入口文件时components/com_content/content.php
。如何一个组件是com_example,那么它的入口文件是/com_example/example.php
。
下一步如何做完全取决于组件自身:可以通过入口文件直接输出内容,也可以使用MVC结构,用控制器调用视图来输出布局文件,也可以直接通过控制器重定向到其他的URL。
当组件生成输出时,不是直接发送给浏览器。而是,将输出存放在JDocument
对象的缓存中。之后,onAfterDispatch触发。Joomla启动的下一步是渲染整个页面。组件已经被渲染了。下一步需要渲染的是其他的扩展:模板和模块。
渲染页面
组件的输出已经缓存到JDocument缓存中了,但是模板和模块还没有。下一步就是调用模板,看看那个模块位置定义了,那些模块需要被实例化。
模板渲染之前会触发onBeforeRender事件。所有的预留位置会被实际的内容替换:
<jdoc:include type=″head″ />
<jdoc:include type=″modules″ name=″position-0″ />
<jdoc:include type=″message″ />
组件位置处替换成组件的输出:
<jdoc:include type=″component />
渲染过程完成后,onAfterRender事件触发。此时,渲染输出包含了所有的内容。
发送内容到浏览器
所有的输出会被发送到浏览器。之前,根据**Global Confguration **的参数,决定压不压缩页面。如果压缩,缓存的HTML会被压缩,然后onAfterCompress事件触发。之后所有的缓存输出,发送到浏览器。发送完毕后,onAfterRespond事件触发,此时可以让系统插件做一些清理工作。
启动过程的不同
启动过程会根据不同的情况有所不同。比如,如果当前页面是RSS,模板和模块的渲染过程会跳过。如果URL中带了参数*?tmpl=component*,组件会在模板中渲染,但是不包括任何模块。
同样,当组件条状到其他页面时,余下的所有过程会被跳过。onAfterRender,
onAfterCompress and onAfterRespond事件都不会触发。
如果一个插件或者组件决定停止应用,比如exit(),die() or JApplication::close()
,应用会停止,之后的所有Joomla代码都不会执行。
组件内部是如何工作的
JPlugin和JEvent的父类
每个插件的父类都是JPlugin。这个类提供了一些有用的功能:将JSON格式的参数转化成JRegistry对象,放在$this->params
变量中,$this->app
和$this->db
对象被定义,定义了一个loadLanguage()
方法,可以直接调用,或者使用$autoloadLanguage
标签。
JPlugin的父类都是JEvent, JEvent的父类都是JObject。JEvent使用了观察值模式,把自己变成被观察者。
引用插件
当Joomla触发事件是,代码可能如下:
JPluginHelper::importPlugin(′somegroup′);
$dispatcher = JEventDispatcher::getInstance();
$dispatcher->trigger(′onSomeEvent′);
一行一行的看,JPluginHelper载入某个类型的所有启用的插件。实例化每个插件类,因此会自动调用每个插件的构造函数。将插件载入内存,可以快速的调用插件,而不是用的时候再去请求数据库。保证了性能。
将事件绑定到分发器上
看一下插件的构造函数,总是要先调用父类的构造函数,否则就啥都做不了。
class PlgSystemExample extends JPlugin
{
public function __construct(&$subject, $confg = array())
{
parent::__construct($subject, $confg);
}
}
构造函数有两个参数: s u b j e c t ∗ 指 向 一 个 ∗ J E v e n t D i s p a t c h e r ∗ 对 象 , ∗ ’ subject*指向一个*JEventDispatcher*对象,*’ subject∗指向一个∗JEventDispatcher∗对象,∗’confg代表设置。
插件的构造函数一般不操作*$subject参数,其父类JEvent构造函数将插件绑定到dispatcher*分发器上,这样后面插件就和分发器联系上了。
这些文件的位置:
JPlugin:libraries/src/Plugin/CMSPlugin.php
;
JEvent:libraries/joomla/Event/event.php
;
JEventDispatcher:libraries/joomla/event/dispatcher.php
;
JPluginHelper:libraries/src/Plugin/PluginHelper.php
;
abstract class JEvent extends JObject
{
public function __construct(&$subject)
{
$subject->attach($this);
$this->_subject = &$subject;
}
}
当$subject->attach($this)
方法调用时,插件被添加到*$Subject*(是个dispatcher实例)的一个内部数组(名字是*$_observers*)。这样,分发器就知道哪些插件是可用的了。在attach()
方法内部会做许多检查,来确保插件能够这样工作而不是导致Joomla死掉。
另外,每个插件的方法会映射到dispather的一个内部数组*$_methods*。一个系统插件可能实现了一大堆事件方法,如果每次事件触发时,dispather都需要检查事件方法存不存在,
在插件中使用dispatcher
$Subject变量的一个用途是用来向触发事件的触发器(dispatcher)发送信息,告知插件执行用出现了错误。
public function onSomeEvent()
{
if($somethingGoesWrong)
{
$this->_subject->setError(′SOMETHING_WENT_WRONG′);
return false;
}
return true;
}
触发器的错误并不能像会话错误那样自动打印。调用的代码决定是否显示它。在组件里,可以用下面的代码来查看插件是否出现错误:
$results = $dispatcher->trigger(′onSomeEvent′, array(&$item));
if (in_array(false, $results))
{
throw new Exception($dispatcher->getError(), 500);
}
JEventDispatcher继承自JObject,这个基类可以使用getters和setters来获取属性值,而不需要真正的定义这个方法。这就意味着,在插件中,可以为*$Subject*变量插入任何值。
$this->_subject->setAnything(′foobar′);
在组件中,这个值可以通过dispatcher变量获取,并由会话消息使用。
$foobar = $dispatcher->getAnything();
JFactory::getApplication()->enqueueMessage($foobar);