php原⽣渲染模板,通过PHP原⽣代码实现视图模板引擎的解析
和渲染
0、引⾔
上篇教程学院君给⼤家简单介绍了什么是 MVC 设计模式,并演⽰了如何基于原⽣ PHP 代码编写简单的 HTTP 控制器,控制器对应 MVC 模式中的 C(Controller),今天,我们⼀起来看下 MVC 模式中另⼀个模块 —— 视图(View,对应 MVC 模式中的 V),并且基于原⽣ PHP 代码实现简单的视图模板引擎。
在此之前,我们的视图渲染实现⽐较简单粗暴,就是直接通过 include 语句引⼊对应的 PHP 视图模板,然后在当前作⽤域内有效的变量会在引⼊的视图模板中⽣效,以博客应⽤⾸页为例,对应的视图引⼊代码是这样的(代码位于 HomeController.php 中):
public function index(){
$albums = $this->connection->table('albums')->selectAll();
$pageTitle = $siteName = $this->container->resolve('app.name');
$siteUrl = $this->container->resolve('app.url');
$siteDesc = $this->container->resolve('app.desc');
include __DIR__  . "/../../../views/home.php";
}
当前控制器⽅法中设置的变量在 home.php 视图模板中可以直接使⽤,因为 include 的本质就是把对应的 PHP 脚本导⼊到当前位置。
在 PHP 中,之所以可以直接这样渲染 HTML 视图,得益于 PHP 脚本和 HTML ⽂档可以混合编程,PHP 本⾝就看作是⼀种视图模板引擎,⽽不需要像其他语⾔那样(⽐如 Java、Go、Python),要引⼊额外的视图模板语⾔才能在 HTML ⽂档中动态引⼊变量进⾏渲染。
虽然 PHP ⽣态也提供了很多第三⽅扩展包作为独⽴的视图模板引擎,以便以⼯程化的⽅式构建更加复杂的应⽤,⽐如 Smarty、twig、Blade 等,不过这⾥为了简化系统,我们直接使⽤ PHP 本⾝作为 HTML 视图的模板语⾔。
不过为了让上述视图渲染实现代码更加优雅、便于维护和扩展,我们以⾯向对象风格的代码对其进⾏重构,并且将其调整为⽀持其他模板引擎。
1、编写 PHP 视图引擎实现代码
我们在 app ⽬录下新建⼀个 view ⼦⽬录,⽤于保存视图模板解析和渲染相关代码,然后在 view ⽬录下新建 engine ⼦⽬录,⽤来保存视图模板引擎代码。
在 engine ⽬录下新建⼀个 ViewEngine 接⼝作为所有 PHP 模板引擎实现的契约:
namespace AppViewEngine;
interface ViewEngine{
public function extract($path, $data): string;
}
接下来,在同级⽬录下新建⼀个实现了 ViewEngine 接⼝的 PhpEngine 类作为 PHP 原⽣视图模板引擎的实现:
namespace AppViewEngine;
class PhpEngine implements ViewEngine{
public function extract($path, $data): string{
ob_start();
extract($data, EXTR_SKIP);
try {
include $path;
} catch (Throwable $e) {
throw new Exception('解析视图模板出错:' . $e->getMessage());
}
return ltrim(ob_get_clean());
}
}
在 PhpEngine 的 extract 实现中,我们通过 PHP ⾃带的输出控制函数 ob_start 打开输出控制缓冲,
然后调⽤ extract 函数将从外部传⼊的数组变量导⼊当前符号表(即在当前作⽤域内以数组键名作为变量名,以对应键值作为变量值),接下来调⽤ include 引⼊指定路径的视图⽂件到缓冲区,这样,从外部传⼊的变量就可以在视图⽂件中⽣效了,如果引⼊⽂件或者变量解析出错,则抛出异常,最后,我们调⽤ob_get_clean 函数将当前缓冲区内执⾏过 PHP 脚本代码并完成变量渲染的视图⽂件内容(标准的 HTML ⽂档)以字符串形式返回,后续这部分内容将作为 HTTP 响应的响应实体返回给客户端。
2、编写视图管理器代码
以上只是最底层视图模板引擎解析 PHP 变量、返回 HTML 格式视图⽂件内容的实现代码,如果你想要基于第三⽅ PHP 引擎扩展包构建更复杂的⾃定义模板引擎解析实现,可以⾃⾏实现 ViewEngine 接⼝并完成相应的视图模板解析逻辑。
编程php语言
接下来,我们在 view ⽬录下编写上层的视图模板引擎管理器和相应的服务提供者。前者⽤来管理不同的模板引擎实现类,根据应⽤配置获取当前使⽤的模板引擎,并完成视图响应的渲染,后者⽤来将这个视图管理器实例注册到服务容器中,以便在应⽤代码中需要渲染视图模板的时候从服务容器获取并使⽤。
⾸先来编写视图管理器,在 view ⽬录下新建 View.php 并初始化代码如下:
namespace AppView;
use AppHttpResponse;
use AppViewEngineViewEngine;
class View{
/**
* @var ViewEngine
*/
protected $engine;
/**
* @var string
*/
protected $basePath;
public function __construct(ViewEngine $engine, $basePath){
$this->engine = $engine;
$this->basePath = $basePath;
}
public function render($path, $data){
$response = new Response();
try {
$content = $this->getContent($path, $data);
} catch (Throwable $e) {
$response->setStatusCode(500);
$response->setContent($e->getMessage());
$response->send();
return;
}
$response->setContent($content);
$response->setStatusCode(200);
$response->send();
}
protected function getContent($path, $data): string{
$path = $this->basePath . $path;
if (!file_exists($path)) {
throw new Exception('对应的视图⽂件不存在!');
}
return $this->engine->extract($path, $data);
}
}
在视图管理器 View 类中,定义了两个属性,$engine 表⽰模板引擎对象,basePath 则表⽰视图模板的根路径,这两个属性都是在实例化View 时从外部传⼊的,我们马上会看到实例化 View 的代码。
重点看下 render ⽅法,该⽅法⽤于被上层代码调⽤完成视图模板的解析和渲染,在这个⽅法中,我们通过 getContent ⽅法调⽤系统当前使⽤的模板引擎实例 $engine 的 extract ⽅法(⽐如当前使⽤的是 PhpEngine,则调⽤该对象的 extract ⽅法)完成视图模板的解析和PHP 变量替换,然后将其返回的字符串格式 HTML ⽂档作为 Response 对象的响应实体随着  $response->send() ⽅法⼀起发送给客户端,完成视图渲染的闭环,如果解析视图模板过程中出错(⽐如视图⽂件不存在,变量解析出错),则返回 500 响应。
3、编写视图服务提供者代码
接下来,在 view ⽬录下新建 ViewProvider.php,并编写服务提供者实现代码如下(其⽤途前⾯已经提及):
namespace AppView;
use AppCoreContainer;
use AppViewEnginePhpEngine;
use AppViewEngineViewEngine;
class ViewProvider{
/**
* @var Container
*/
protected $container;
public function __construct($container){
$this->container = $container;
}
public function register(){
$this->container->bind('view', function (){
$config = $this->container->resolve('ine');
$method = 'register' . ucfirst($config) . 'Engine';
if (!method_exists($this, $method)) {
throw new Exception('对应的视图模板引擎暂不⽀持!');
}
$engine = call_user_func([$this, $method]);
$basePath = $this->container->resolve('view.path');
return new View($engine, $basePath);
});
}
public function registerPhpEngine(){
return new PhpEngine();
}
}
我们在其 register ⽅法实现中将 View 对象实例绑定到全局服务容器中,在初始化 View 对象的时候,需要先初始化 ViewEngine 对象,这⾥,我们通过配置⽂件配置系统使⽤的模板引擎:
'ine' => 'php',  // 视图模板引擎
⽬前只有 PhpEngine ⼀个实现,所以我们将 ine 配置为 php,如果后续⽀持其他模板引擎,在实现了对应的引擎类 XxxEngine 后,还要在这⾥实现对应的注册⽅法 registerXxxEngine,最后在
配置⽂件中配置 ine 值为 xxx 才可以使其⽣效。
另外,我们还在 app/config/app.php 新增配置 view.path 作为视图模板的根路径:
'view.path' => __DIR__ . '/../../views/',  // 视图模板根路径
有了模板引擎实例和视图模板根路径后,就可以将它们传⼊视图管理器 View 的构造函数对其进⾏初始化了。
代码实现⽐较简单,不再逐⼀解释了。
最后,还要在 app/fig 的 providers 中注册视图提供者:
'providers' => [
AppStoreStoreProvider::class,
AppPrinterPrinterProvider::class,
AppViewViewProvider::class,
]
以便在应⽤启动时调⽤其 register ⽅法注册 View 实例。
另外,为了让新增的 ine 和 view.path 配置⽣效,需要在 app/bootstrap.php 的 initConfig ⽅法中新增这两个配置的注册:function initConfig(Container $container){
...
$container->bind('ine', $config['ine']);
$container->bind('view.path', $config['view.path']);
}
4、重构配置⽂件及注册逻辑
为了免于后续新增配置需要频繁修改这⾥的代码,还可以通过 foreach 循环来重构这段注册代码,为此,我们需要先调整
app/fig:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。