模板引擎

渲染驱动

EasySwoole 引入模板渲染驱动的形式,把需要渲染的数据,通过协程客户端投递到自定义的同步进程中进行渲染并返回结果。为何要如此处理,原因在于,市面上的一些模板引擎在 Swoole 协程下存在变量安全问题。例如以下流程:

  • request A reached, static A assign requestA-data
  • compiled template
  • write compiled template (yield current coroutine)
  • request B reached,static A assign requestB-data
  • render static A data into complied template file

以上流程我们可以发现,A 请求的数据,被 B 请求给污染了。为了解决该问题,EasySwoole 引入模板渲染驱动模式。

组件要求

  • easyswoole/spl: ^1.0
  • easyswoole/component: ^2.0

安装方法

composer require easyswoole/template

仓库地址

easyswoole/template

基础实现原理讲解

实现渲染引擎

  1. <?php
  2. class R implements \EasySwoole\Template\RenderInterface
  3. {
  4. public function render(string $template, ?array $data = null, ?array $options = null): ?string
  5. {
  6. return 'todo some thing';
  7. }
  8. public function onException(\Throwable $throwable, $arg): string
  9. {
  10. return $throwable->getMessage();
  11. }
  12. }

旧版本 Template (1.1.0 之前版本) 实现渲染引擎如下:

  1. <?php
  2. class R implements \EasySwoole\Template\RenderInterface
  3. {
  4. public function render(string $template, ?array $data = [], ?array $options = []):?string
  5. {
  6. return 'todo some thing';
  7. }
  8. public function afterRender(?string $result, string $template, array $data = [], array $options = [])
  9. {
  10. // TODO: Implement afterRender() method.
  11. }
  12. public function onException(Throwable $throwable, $arg):string
  13. {
  14. return $throwable->getMessage();
  15. }
  16. }

在自定义 HTTP 服务中调用渲染引擎

  1. <?php
  2. require_once __DIR__ . '/vendor/autoload.php';
  3. class MyRender implements \EasySwoole\Template\RenderInterface
  4. {
  5. public function render(string $template, ?array $data = null, ?array $options = null): ?string
  6. {
  7. return "your template is {$template} and data is " . json_encode($data);
  8. }
  9. public function onException(\Throwable $throwable, $arg): string
  10. {
  11. return $throwable->getTraceAsString();
  12. }
  13. }
  14. $renderConfig = \EasySwoole\Template\Render::getInstance()->getConfig();
  15. /*
  16. * 可选配置
  17. $renderConfig->setTempDir(getcwd()); // 设置 渲染引擎驱动 Socket 存放目录,默认为 getcwd()
  18. $renderConfig->setTimeout(3); // 设置 超时时间,默认为 3s,不建议修改
  19. $renderConfig->setServerName('EasySwoole'); // 设置 渲染引擎驱动服务名称,不建议修改
  20. $renderConfig->setWorkerNum(3); // 设置 渲染引擎服务工作进程数,默认为 3,不建议修改
  21. */
  22. $renderConfig->setRender(new MyRender()); // 设置 渲染引擎
  23. $http = new swoole_http_server("0.0.0.0", 9501);
  24. $http->on("request", function ($request, $response) {
  25. $ret = \EasySwoole\Template\Render::getInstance()->render('index.html', ['easyswoole' => 'hello']);
  26. $response->end($ret);
  27. });
  28. // 调用渲染引擎
  29. \EasySwoole\Template\Render::getInstance()->attachServer($http);
  30. $http->start();

旧版本 Template 组件(1.1.0 之前)在自定义 HTTP 服务中调用渲染引擎时,实现渲染引擎接口的方法有些许不同,详细请看上文实现渲染引擎。

重启渲染引擎

由于某些模板引擎会缓存模板文件,导致可能出现以下情况:

  • 用户 A 请求 1.tpl 返回 ‘a’
  • 开发者修改了 1.tpl 的数据,改成了 ‘b’
  • 用户 B、C、D 在之后的请求中,可能会出现 ‘a’、’b’两种不同的值

那是因为模板引擎已经缓存了 A 所在进程的文件,导致后面的请求如果也分配到了 A 的进程,就会获取到缓存的值

解决方案如下:

  • 1: 重启 EasySwoole 服务,即可解决
  • 2: 模板渲染引擎实现了重启方法 restartWorker,直接调用即可
  1. Render::getInstance()->restartWorker();

用户可以根据自己的逻辑,自行调用 restartWorker 方法进行重启。

重启渲染引擎使用示例

例如:用户可以在控制器中新增 reload 方法重启渲染引擎:

1、实现自定义渲染引擎,新建 App\RenderDriver\MyRender.php 文件

  1. <?php
  2. namespace App\RenderDriver;
  3. class MyRender implements \EasySwoole\Template\RenderInterface
  4. {
  5. public function render(string $template, ?array $data = null, ?array $options = null): ?string
  6. {
  7. return "your template is {$template} and data is " . json_encode($data);
  8. }
  9. public function onException(\Throwable $throwable, $arg): string
  10. {
  11. return $throwable->getTraceAsString();
  12. }
  13. }

旧版本 Template 组件(1.1.0 之前)实现自定义渲染引擎接口的方法和最新稳定版本有些许不同,详细请看上文。

2、注册渲染引擎服务

  1. <?php
  2. namespace EasySwoole\EasySwoole;
  3. use EasySwoole\EasySwoole\AbstractInterface\Event;
  4. use EasySwoole\EasySwoole\Swoole\EventRegister;
  5. use EasySwoole\Template\Render;
  6. class EasySwooleEvent implements Event
  7. {
  8. public static function initialize()
  9. {
  10. date_default_timezone_set('Asia/Shanghai');
  11. }
  12. public static function mainServerCreate(EventRegister $register)
  13. {
  14. $renderConfig = \EasySwoole\Template\Render::getInstance()->getConfig();
  15. /*
  16. * 可选配置
  17. $renderConfig->setTempDir(getcwd()); // 设置 渲染引擎驱动 Socket 存放目录,默认为 getcwd()
  18. $renderConfig->setTimeout(3); // 设置 超时时间,默认为 3s,不建议修改
  19. $renderConfig->setServerName('EasySwoole'); // 设置 渲染引擎驱动服务名称,不建议修改
  20. $renderConfig->setWorkerNum(3); // 设置 渲染引擎服务工作进程数,默认为 3,不建议修改
  21. */
  22. $renderConfig->setRender(new \App\RenderDriver\MyRender());
  23. Render::getInstance()->attachServer(ServerManager::getInstance()->getSwooleServer());
  24. }
  25. }

3、在控制器中新增 reload 方法重启渲染引擎

  1. <?php
  2. namespace App\HttpController;
  3. use EasySwoole\Http\AbstractInterface\Controller;
  4. use EasySwoole\Template\Render;
  5. class Index extends Controller
  6. {
  7. public function index()
  8. {
  9. $this->response()->write(Render::getInstance()->render('index.tpl', [
  10. 'user' => 'easyswoole',
  11. 'time' => time()
  12. ]));
  13. }
  14. public function reload()
  15. {
  16. Render::getInstance()->restartWorker();
  17. $this->response()->write('restart worker success!');
  18. }
  19. }

运行结果:访问 http://127.0.0.1:9501/ (示例请求地址) 即可看到运行结果: your template is index.tpl and data is {"user":"easyswoole","time":1613659221},然后访问 http://127.0.0.1:9501/reload (示例请求地址) 即可重启渲染引擎,看到运行结果 restart worker success!

使用示例(在 EasySwoole 中使用)

使用 Smarty 渲染

引入Smarty

composer require smarty/smarty

实现渲染引擎

新建 \App\RenderDriver\Smarty.php,内容如下:

  1. <?php
  2. namespace App\RenderDriver;
  3. use EasySwoole\Template\RenderInterface;
  4. class Smarty implements RenderInterface
  5. {
  6. private $smarty;
  7. function __construct()
  8. {
  9. $temp = sys_get_temp_dir();
  10. $this->smarty = new \Smarty();
  11. $this->smarty->setTemplateDir(EASYSWOOLE_ROOT . '/App/View/');
  12. $this->smarty->setCacheDir("{$temp}/smarty/cache/");
  13. $this->smarty->setCompileDir("{$temp}/smarty/compile/");
  14. }
  15. public function render(string $template, ?array $data = null, ?array $options = null): ?string
  16. {
  17. foreach ($data as $key => $item) {
  18. $this->smarty->assign($key, $item);
  19. }
  20. return $this->smarty->fetch($template, $cache_id = null, $compile_id = null, $parent = null, $display = false,
  21. $merge_tpl_vars = true, $no_output_filter = false);
  22. }
  23. public function onException(\Throwable $throwable, $arg): string
  24. {
  25. $msg = "{$throwable->getMessage()} at file:{$throwable->getFile()} line:{$throwable->getLine()}";
  26. trigger_error($msg);
  27. return $msg;
  28. }
  29. }

旧版本 Template 组件(1.1.0 之前)实现渲染引擎接口的方法和最新稳定版本有些许不同,详细请看上文。Template 1.1.0 之前版本实现如下:

  1. <?php
  2. namespace App\RenderDriver;
  3. use EasySwoole\Template\RenderInterface;
  4. class Smarty implements RenderInterface
  5. {
  6. private $smarty;
  7. function __construct()
  8. {
  9. $temp = sys_get_temp_dir();
  10. $this->smarty = new \Smarty();
  11. $this->smarty->setTemplateDir(EASYSWOOLE_ROOT . '/App/View/');
  12. $this->smarty->setCacheDir("{$temp}/smarty/cache/");
  13. $this->smarty->setCompileDir("{$temp}/smarty/compile/");
  14. }
  15. public function render(string $template, ?array $data = [], ?array $options = []): ?string
  16. {
  17. foreach ($data as $key => $item) {
  18. $this->smarty->assign($key, $item);
  19. }
  20. return $this->smarty->fetch($template, $cache_id = null, $compile_id = null, $parent = null, $display = false,
  21. $merge_tpl_vars = true, $no_output_filter = false);
  22. }
  23. public function afterRender(?string $result, string $template, array $data = [], array $options = [])
  24. {
  25. }
  26. public function onException(\Throwable $throwable, $arg): string
  27. {
  28. $msg = "{$throwable->getMessage()} at file:{$throwable->getFile()} line:{$throwable->getLine()}";
  29. trigger_error($msg);
  30. return $msg;
  31. }
  32. }

在 EasySwoole 的 HTTP 服务中调用

首先在 EasySwoole 全局事件 EasySwooleEvent.phpmainServerCreate 事件中注册渲染引擎服务,注册示例代码如下:

  1. <?php
  2. namespace EasySwoole\EasySwoole;
  3. use EasySwoole\EasySwoole\AbstractInterface\Event;
  4. use EasySwoole\EasySwoole\Swoole\EventRegister;
  5. class EasySwooleEvent implements Event
  6. {
  7. public static function initialize()
  8. {
  9. date_default_timezone_set('Asia/Shanghai');
  10. }
  11. public static function mainServerCreate(EventRegister $register)
  12. {
  13. // 获取 Render 配置
  14. $renderConfig = \EasySwoole\Template\Render::getInstance()->getConfig();
  15. // [可选配置]
  16. /*
  17. $renderConfig->setTimeout(3); // 设置 超时时间,默认为 3s,不建议修改
  18. $renderConfig->setServerName('EasySwoole'); // 设置 渲染引擎驱动服务名称,不建议修改
  19. $renderConfig->setWorkerNum(3); // 设置 渲染引擎服务工作进程数,默认为 3,不建议修改
  20. */
  21. // 设置 渲染引擎模板驱动
  22. $renderConfig->setRender(new \App\RenderDriver\Smarty());
  23. // 设置 渲染引擎进程 Socket 存放目录,默认为 getcwd()
  24. $renderConfig->setTempDir(EASYSWOOLE_TEMP_DIR);
  25. // 注册进程到 EasySwoole 主服务
  26. \EasySwoole\Template\Render::getInstance()->attachServer(\EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer());
  27. }
  28. }

在控制器层响应(使用示例代码如下):

首先新建 App\View\custom.html,内容如下:

  1. {$name}

在控制器中进行调用:

  1. <?php
  2. namespace App\HttpController;
  3. use EasySwoole\Http\AbstractInterface\Controller;
  4. class Index extends Controller
  5. {
  6. public function index()
  7. {
  8. $this->response()->write(\EasySwoole\Template\Render::getInstance()->render('custom.html', ['name' => 'Welcome To Use EasySwoole ^_^!']));
  9. }
  10. }

运行结果:启动服务,访问 http://127.0.0.1:9501,即可看到运行结果:Welcome To Use EasySwoole ^_^!

支持常用的模板引擎

下面列举一些常用的模板引擎包方便引入使用:

smarty/smarty

Smarty 是一个使用 PHP 写出来的模板引擎,是目前业界最著名的 PHP 模板引擎之一。

引入方法

composer require smarty/smarty=~3.1

league/plates

使用原生 PHP 语法的非编译型模板引擎,更低的学习成本和更高的自由度。

引入方法

composer require league/plates=3.*

duncan3dc/blade

Laravel 框架使用的模板引擎

引入方法

composer require duncan3dc/blade=^4.5

topthink/think-template

ThinkPHP 框架使用的模板引擎

引入方法

composer require topthink/think-template

如果用户想要在 EasySwoole 框架中使用以上模板引擎,具体使用示例可以查看Template 使用示例 或者 Template 组件单元测试用例。上文中讲述了使用 Smarty 模板引擎的使用示例,其他模板引擎的使用方法大致类似。

常见问题

注册渲染引擎失败,出现 UnixSocket bind 失败

  • 报错结果类似如下:
  1. PHP Fatal error: Uncaught EasySwoole\Component\Process\Exception: EasySwoole\Template\RenderWorker bind /work/EasySwoole.Render.Worker.0.sock fail case Operation not permitted in /work/vendor/easyswoole/component/src/Process/Socket/AbstractUnixProcess.php:32
  • 失败原因:部分 vargrant 服务器或 Docker 服务器没有权限创建 UnixSocket,导致注册渲染引擎失败。
  • 解决方案:注册渲染引擎时,设置渲染引擎驱动进程 Socket 存放目录为 '/Tmp'。示例代码如下:
  1. <?php
  2. namespace EasySwoole\EasySwoole;
  3. use EasySwoole\EasySwoole\AbstractInterface\Event;
  4. use EasySwoole\EasySwoole\Swoole\EventRegister;
  5. class EasySwooleEvent implements Event
  6. {
  7. public static function initialize()
  8. {
  9. date_default_timezone_set('Asia/Shanghai');
  10. }
  11. public static function mainServerCreate(EventRegister $register)
  12. {
  13. // 获取 Render 配置
  14. $renderConfig = \EasySwoole\Template\Render::getInstance()->getConfig();
  15. // 设置 渲染引擎模板驱动
  16. $renderConfig->setRender(new \App\RenderDriver\Smarty());
  17. ### 设置 渲染引擎进程 Socket 存放目录为 '/Tmp' ###
  18. $renderConfig->setTempDir('/Tmp');
  19. // 注册进程到 EasySwoole 主服务
  20. \EasySwoole\Template\Render::getInstance()->attachServer(\EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer());
  21. }
  22. }