php 是如何渲染view的,Phalcon View 渲染原理及过程

本文主要讨论 Phalcon 视图层的渲染。

原理

把 action 中传过来的数据定义成变量。

加载视图文件,此时视图里的变量已经有值了 (如果是volt的话,把volt语法编译成原生php,加载编译后的文件),它是一个 php 和 html 混写的文件,php 的代码会执行,最后只剩下 html。

把执行完后的内容返回出去。

控制器传值给视图

使用方法:

// 直接设置变量,会调用 __set() 的魔术方法

$this->view->products = $products;

// setParamToView()

$this->view->setParamToView('products', $products);

// setVar()

$this->view->setVar('products', $products);

// setVars()

$this->view->setVars(array('products' => $products));

下面我们分别看一下源码中这些方法是如何实现的

/**

* Magic method to pass variables to the views

*

* @param string key

* @param mixed value

*/

public function __set(string! key, value)

{

let this->_viewParams[key] = value;

}

/**

* Adds parameters to views (alias of setVar)

*

* @param string key

* @param mixed value

* @return \Phalcon\Mvc\View

*/

public function setParamToView(string! key, value) ->

{

let this->_viewParams[key] = value;

return this;

}

/**

* Set all the render params

*

* @param array params

* @param boolean merge

* @return \Phalcon\Mvc\View

*/

public function setVars(array! params, boolean merge = true) ->

{

var viewParams;

if merge {

let viewParams = this->_viewParams;

if typeof viewParams == "array" {

let this->_viewParams = array_merge(viewParams, params);

} else {

let this->_viewParams = params;

}

} else {

let this->_viewParams = params;

}

return this;

}

/**

* Set a single view param

*

* @param string key

* @param mixed value

* @return \Palcon\Mvc\View

*/

public function setVar(string! key, value) ->

{

let this->_viewParams[key] = value;

return this;

}

这几个方法都是把值设置给了 view 实例的一个属性 _viewParams ,在调用 _engineRender() 方法时,它取出 _viewParams 的值作为参数传给了具体的渲染引擎。

视图

在 index.php 中,实例化出一个 application 对象,执行 application->handle() 方法,在这个方法里,实例化了 view,调 view->render() 方法,我们追踪下去,就可以找出 view 层是如何处理渲染的。

视图渲染逻辑

/**

* Executes render process from dispatching data

*

* @param string controllerName

* @param string actionName

* @param array params

*/

public function render(string! controllerName, string! actionName, params = null) -> |boolean

{

}

下面我以主要的业务逻辑过一下源码

_disabled 检查视图是否禁用,如果禁用,输出内容, return false

layout 检查是否设置了layout

_loadTemplateEngines() 加载模板引擎

pickView 检查用户是否指定了一个不同于当前url对应的 controller/action 视图

_cacheLevel 检查缓存

create_symbol_table() 创建符号表

view:beforeRender 抛出事件

Inserts view related to action

Inserts templates before layout

Inserts controller layout

Inserts templates after layout

Inserts main view

Store the data in the cache

view:afterRender 抛出事件

在插入视图时,都是调用的这个方法this->_engineRender(engines, renderView, silence, mustClean, cache);

_engineRender

在 Phalcon 框架中,所有的模板引擎适配器都必须继承Engine类,这个类提供了引擎和视图组件的基本接口,每一个引擎都必须实现自己的render方法。

当插入视图调用 _engineRender() 方法时,Phalcon 又做了如下处理

注:本文仅是示例代码

/**

* Checks whether view exists on registered extensions and render it

*

* @param array engines

* @param string viewPath

* @param boolean silence

* @param boolean mustClean

* @param \Phalcon\Cache\BackendInterface $cache

*/

protected function _engineRender(engines, string viewPath, boolean silence, boolean mustClean, cache = null)

{

let viewsDir = this->_viewDir,

basePath = this->_basePath,

viewsDirPath = basePath . viewsDir . viewPath;

let viewParams = this->_viewParams;

for extension, engine in engines {

let viewEnginePath = viewsDirPath . extension;

if file_exists(viewEnginePath) {

if typeof eventsManager == "object" {

let this->_activeRenderPath = viewEnginePath;

if (eventsManager->fire("view:beforeRenderView", this, viewEnginePath) === false) {

continue;

}

}

engine->render(viewEnginePath, viewParams, mustClean);

let notExists = false;

if typeof eventsManager == "object" {

eventsManager->fire("view:afterRenderView", this);

}

break;

}

}

}

它在调用具体模板引擎渲染的前后分别抛出两个事件,便于开发者使用。

此时的 engine 可能是 php engine,也可以是 volt engine,或者其他任何你注册的渲染引擎。

下面是这些参数的含义。

viewEnginePath: 视图文件的路径。

viewParams: action 传给 view 的数据。

mustClean: 是否必须清除输出缓冲区。

这里有几个要点需要注意:

如果侦听 view:beforeRenderView 事件,处理后返回 false,将不进入渲染阶段。

engine->render() 方法调用后,任何输出都将忽略,原因参考下面模板引擎渲染的代码。

view:afterRenderView 事件不可中止。

php engine

以 php 默认渲染引擎为例,当上面调用engine->render()方法时,会执行以下几步

清空输出缓冲区。

把 action 传给 view 的数据,以变量的形式声明出来。

加载视图文件,这时候在视图中php代码会把变量的值输出。

使用 setContent() 方法把输出缓冲区的内容放入 view 对象 _content 属性中。这样在 视图文件里就可以使用 getContent() 方法取出。

/**

* Externally sets the view content

*

*

* $this->view->setContent("

hello

");

*

*/

public function setContent(string content) ->

{

let this->_content = content;

return this;

}

/**

* Returns cached output from another view stage

*/

public function getContent() -> string

{

return this->_content;

}

php engine render 源码分析

/**

* Adapter to use PHP itself as templating engine

*/

class Php Engine implements EngineInterface

{

/**

* Renders a view using the template engine

*/

public function render(string! path, var params, boolean mustClean = false)

{

var key, value;

if mustClean === true {

ob_clean();

}

/**

* Create the variables in local symbol table

*/

for key, value in params {

let {key} = value;

}

/**

* Require the file

*/

require path;

if mustClean == true {

$this->_view->setContent(ob_get_contents());

}

}

}

参数解析:

path:视图文件 .phtml 或 .volt 文件的路径

mustClean:是否必须清除输出缓冲区

params: array 类型,存储的是 action 传给 view 的变量。

如:在 action 中

$this->view->test = 123;

$this->view->haha = "hello world";

则 params

array (size=2)

'test' => int 123

'haha' => string 'hello world'

volt

volt 是 Phalcon 自带的模板渲染引擎。如果我们注册的是 volt 引擎,那么_engineRender调的是 volt 引擎,我们看一下它是如何处理的。

render

/**

* Renders a view using the template engine

*/

public function render(string! templatePath, var params, boolean mustClean = false)

{

var compiler, compiledTemplatePath, key, value;

if mustClean {

ob_clean();

}

/**

* The compilation process is done by Phalcon\Mvc\View\Engine\Volt\Compiler

*/

let compiler = this->getCompiler();

compiler->compile(templatePath);

let compiledTemplatePath = compiler->getCompiledTemplatePath();

/**

* Export the variables the current symbol table

*/

if typeof params == "array" {

for key, value in params {

let {key} = value;

}

}

require compiledTemplatePath;

if mustClean {

this->_view->setContent(ob_get_contents());

}

}

volt 和 php engine 处理差不多,只是中间多了一步编译,而编译所做的工作就是把 volt 语法转换成原生 php 语法。

我们可以看出,它首先得到一个 compiler 的实例,然后调用compiler的compile()方法来编译 volt 文件,最后是得到编译后文件的路径。

下面我们通过源代码验证我们的设想

首先看 getCompiler()

/**

* Returns the Volt's compiler

*/

public function getCompiler() ->

{

var compiler, dependencyInjector, options;

let compiler = this->_compiler;

if typeof compiler != "object" {

let compiler = new Compiler(this->_view);

/**

* Pass the IoC to the compiler only of it's an object

*/

let dependencyInjector = this->_dependencyInjector;

if typeof dependencyInjector == "object" {

compiler->setDi(dependencyInjector);

}

/**

* Pass the options to the compiler only if they're an array

*/

let options = this->_options;

if typeof options == "array" {

compiler->setOptions(options);

}

let this->_compiler = compiler;

}

return compiler;

}

然后看compile(),这个方法内容很多,我们忽略大部分细节,只看关键部分。

/**

* Compiles a template into a file applying the compiler options

* This method does not return the compiled path if the template was not compiled

*

*

* $compiler->compile('views/layouts/main.volt');

* require $compiler->getCompiledTemplatePath();

*

*/

public function compile(string! templatePath, boolean extendsMode = false)

{

let compilation = this->compileFile(templatePath, realCompiledPath, extendsMode);

let this->_compiledTemplatePath = realCompiledPath;

return compilation;

}

最后我们看getCompiledTemplatePath()

/**

* Returns the path to the last compiled template

*/

public function getCompiledTemplatePath() -> string

{

return this->_compiledTemplatePath;

}

compileFile

从 compile() ,我们就可以看出 compileFile() 方法是下一个突破口.

/**

* Compiles a template into a file forcing the destination path

*

*

* $compiler->compile('views/layouts/main.volt', 'views/layouts/main.volt.php');

*

*

* @param string path

* @param string compiledPath

* @param boolean extendsMode

* @return string|array

*/

public function compileFile(string! path, string! compiledPath, boolean extendsMode = false)

{

var viewCode, compilation, finalCompilation;

/**

* Always use file_get_contents instead of read the file directly, this respect the open_basedir directive

*/

let viewCode = file_get_contents(path);

let this->_currentPath = path;

let compilation = this->_compileSource(viewCode, extendsMode);

/**

* We store the file serialized if it's an array of blocks

*/

if typeof compilation == "array" {

let finalCompilation = serialize(compilation);

} else {

let finalCompilation = compilation;

}

/**

* Always use file_put_contents to write files instead of write the file directly, this respect the open_basedir directive

*/

if file_put_contents(compiledPath, finalCompilation) === false {

throw new Exception("Volt directory can't be written");

}

return compilation;

}

从这里我们又找到线索_compileSource(),继续前行。

_compileSource

/**

* Compiles a Volt source code returning a PHP plain version

*/

protected function _compileSource(string! viewCode, boolean extendsMode = false) -> string

{

let currentPath = this->_currentPath;

// Check for compilation options

// ......

let intermediate = phvolt_parse_view(viewCode, currentPath);

let compilation = this->_statementList(intermediate, extendsMode);

// Check if the template is extending another

// ......

return compilation;

}

看到方法注释的时候,我们以为终于找到正主,看完却发现并没有。

_statementList

/**

* Traverses a statement list compiling each of its nodes

*/

final protected function _statementList(array! statements, boolean extendsMode = false) -> string

{

// ......

}

这个方法有246行,我把关键部分截图了,方便查看。

bVsr5F

它通过 switch case,匹配不同表达式的类型,调用不同的方法具体编译某段代码。

我以 compileEcho() 和 compileIf() 为例给大家看一下.

bVsr5N

bVsr5O

进行到这里,我们终于找出了 volt 模板引擎的大致渲染思路,它比php engine 多了一步就是编译,把它自身语法翻译成原生php,然后就和 php engine 处理是一样的了。

volt 总结

volt 比 phtml 在处理渲染时多了一步编译,虽然它已编译成机器码在服务器端运行,但多了一步编译,肯定比 phtml 慢一些的;而且前期使用 volt 必然需要阅读官方文档,了解其规则,这些都是时间成本。

但是熟练使用它的话,在开发过程中能够简化代码,提高可读性和开发效率。

总结

我们在处理视图渲染时,先把控制器传过来的数据,通过变量声明出来,这样视图文件的变量就有值了,随着 php 代码的执行,最后只剩下一个 html 的内容返回出去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值