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 接口并完成相应的视图模板解析逻辑。

接下来,我们在 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('view.engine');

$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 对象,这里,我们通过配置文件配置系统使用的模板引擎:

'view.engine' => 'php',  // 视图模板引擎

目前只有 PhpEngine 一个实现,所以我们将 view.engine 配置为 php,如果后续支持其他模板引擎,在实现了对应的引擎类 XxxEngine 后,还要在这里实现对应的注册方法 registerXxxEngine,最后在配置文件中配置 view.engine 值为 xxx 才可以使其生效。

另外,我们还在 app/config/app.php 新增配置 view.path 作为视图模板的根路径:

'view.path' => __DIR__ . '/../../views/',  // 视图模板根路径

有了模板引擎实例和视图模板根路径后,就可以将它们传入视图管理器 View 的构造函数对其进行初始化了。

代码实现比较简单,不再逐一解释了。

最后,还要在 app/config/app.config 的 providers 中注册视图提供者:

'providers' => [

AppStoreStoreProvider::class,

AppPrinterPrinterProvider::class,

AppViewViewProvider::class,

]

以便在应用启动时调用其 register 方法注册 View 实例。

另外,为了让新增的 view.engine 和 view.path 配置生效,需要在 app/bootstrap.php 的 initConfig 方法中新增这两个配置的注册:

function initConfig(Container $container){

...

$container->bind('view.engine', $config['view.engine']);

$container->bind('view.path', $config['view.path']);

}

4、重构配置文件及注册逻辑

为了免于后续新增配置需要频繁修改这里的代码,还可以通过 foreach 循环来重构这段注册代码,为此,我们需要先调整 app/config/app.config:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值