模板引擎
渲染驅動
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
倉庫地址
基礎實現原理講解
實現渲染引擎
<?php
class R implements \EasySwoole\Template\RenderInterface
{
public function render(string $template, ?array $data = null, ?array $options = null): ?string
{
return 'todo some thing';
}
public function onException(\Throwable $throwable, $arg): string
{
return $throwable->getMessage();
}
}
舊版本 Template (1.1.0 之前版本) 實現渲染引擎如下:
<?php
class R implements \EasySwoole\Template\RenderInterface
{
public function render(string $template, ?array $data = [], ?array $options = []):?string
{
return 'todo some thing';
}
public function afterRender(?string $result, string $template, array $data = [], array $options = [])
{
// TODO: Implement afterRender() method.
}
public function onException(Throwable $throwable, $arg):string
{
return $throwable->getMessage();
}
}
在自定義 HTTP 服務中調用渲染引擎
<?php
require_once __DIR__ . '/vendor/autoload.php';
class MyRender implements \EasySwoole\Template\RenderInterface
{
public function render(string $template, ?array $data = null, ?array $options = null): ?string
{
return "your template is {$template} and data is " . json_encode($data);
}
public function onException(\Throwable $throwable, $arg): string
{
return $throwable->getTraceAsString();
}
}
$renderConfig = \EasySwoole\Template\Render::getInstance()->getConfig();
/*
* 可選配置
$renderConfig->setTempDir(getcwd()); // 設置 渲染引擎驅動 Socket 存放目錄,默認為 getcwd()
$renderConfig->setTimeout(3); // 設置 超時時間,默認為 3s,不建議修改
$renderConfig->setServerName('EasySwoole'); // 設置 渲染引擎驅動服務名稱,不建議修改
$renderConfig->setWorkerNum(3); // 設置 渲染引擎服務工作進程數,默認為 3,不建議修改
*/
$renderConfig->setRender(new MyRender()); // 設置 渲染引擎
$http = new swoole_http_server("0.0.0.0", 9501);
$http->on("request", function ($request, $response) {
$ret = \EasySwoole\Template\Render::getInstance()->render('index.html', ['easyswoole' => 'hello']);
$response->end($ret);
});
// 調用渲染引擎
\EasySwoole\Template\Render::getInstance()->attachServer($http);
$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
,直接調用即可
Render::getInstance()->restartWorker();
用戶可以根據自己的邏輯,自行調用 restartWorker
方法進行重啟。
重啟渲染引擎使用示例
例如:用戶可以在控制器中新增 reload
方法重啟渲染引擎:
1、實現自定義渲染引擎,新建 App\RenderDriver\MyRender.php
文件
<?php
namespace App\RenderDriver;
class MyRender implements \EasySwoole\Template\RenderInterface
{
public function render(string $template, ?array $data = null, ?array $options = null): ?string
{
return "your template is {$template} and data is " . json_encode($data);
}
public function onException(\Throwable $throwable, $arg): string
{
return $throwable->getTraceAsString();
}
}
舊版本 Template 組件(1.1.0 之前)實現自定義渲染引擎接口的方法和最新穩定版本有些許不同,詳細請看上文。
2、注冊渲染引擎服務
<?php
namespace EasySwoole\EasySwoole;
use EasySwoole\EasySwoole\AbstractInterface\Event;
use EasySwoole\EasySwoole\Swoole\EventRegister;
use EasySwoole\Template\Render;
class EasySwooleEvent implements Event
{
public static function initialize()
{
date_default_timezone_set('Asia/Shanghai');
}
public static function mainServerCreate(EventRegister $register)
{
$renderConfig = \EasySwoole\Template\Render::getInstance()->getConfig();
/*
* 可選配置
$renderConfig->setTempDir(getcwd()); // 設置 渲染引擎驅動 Socket 存放目錄,默認為 getcwd()
$renderConfig->setTimeout(3); // 設置 超時時間,默認為 3s,不建議修改
$renderConfig->setServerName('EasySwoole'); // 設置 渲染引擎驅動服務名稱,不建議修改
$renderConfig->setWorkerNum(3); // 設置 渲染引擎服務工作進程數,默認為 3,不建議修改
*/
$renderConfig->setRender(new \App\RenderDriver\MyRender());
Render::getInstance()->attachServer(ServerManager::getInstance()->getSwooleServer());
}
}
3、在控制器中新增 reload
方法重啟渲染引擎
<?php
namespace App\HttpController;
use EasySwoole\Http\AbstractInterface\Controller;
use EasySwoole\Template\Render;
class Index extends Controller
{
public function index()
{
$this->response()->write(Render::getInstance()->render('index.tpl', [
'user' => 'easyswoole',
'time' => time()
]));
}
public function reload()
{
Render::getInstance()->restartWorker();
$this->response()->write('restart worker success!');
}
}
運行結果:訪問 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
,內容如下:
<?php
namespace App\RenderDriver;
use EasySwoole\Template\RenderInterface;
class Smarty implements RenderInterface
{
private $smarty;
function __construct()
{
$temp = sys_get_temp_dir();
$this->smarty = new \Smarty();
$this->smarty->setTemplateDir(EASYSWOOLE_ROOT . '/App/View/');
$this->smarty->setCacheDir("{$temp}/smarty/cache/");
$this->smarty->setCompileDir("{$temp}/smarty/compile/");
}
public function render(string $template, ?array $data = null, ?array $options = null): ?string
{
foreach ($data as $key => $item) {
$this->smarty->assign($key, $item);
}
return $this->smarty->fetch($template, $cache_id = null, $compile_id = null, $parent = null, $display = false,
$merge_tpl_vars = true, $no_output_filter = false);
}
public function onException(\Throwable $throwable, $arg): string
{
$msg = "{$throwable->getMessage()} at file:{$throwable->getFile()} line:{$throwable->getLine()}";
trigger_error($msg);
return $msg;
}
}
舊版本 Template 組件(1.1.0 之前)實現渲染引擎接口的方法和最新穩定版本有些許不同,詳細請看上文。Template 1.1.0 之前版本實現如下:
<?php
namespace App\RenderDriver;
use EasySwoole\Template\RenderInterface;
class Smarty implements RenderInterface
{
private $smarty;
function __construct()
{
$temp = sys_get_temp_dir();
$this->smarty = new \Smarty();
$this->smarty->setTemplateDir(EASYSWOOLE_ROOT . '/App/View/');
$this->smarty->setCacheDir("{$temp}/smarty/cache/");
$this->smarty->setCompileDir("{$temp}/smarty/compile/");
}
public function render(string $template, ?array $data = [], ?array $options = []): ?string
{
foreach ($data as $key => $item) {
$this->smarty->assign($key, $item);
}
return $this->smarty->fetch($template, $cache_id = null, $compile_id = null, $parent = null, $display = false,
$merge_tpl_vars = true, $no_output_filter = false);
}
public function afterRender(?string $result, string $template, array $data = [], array $options = [])
{
}
public function onException(\Throwable $throwable, $arg): string
{
$msg = "{$throwable->getMessage()} at file:{$throwable->getFile()} line:{$throwable->getLine()}";
trigger_error($msg);
return $msg;
}
}
在 EasySwoole 的 HTTP 服務中調用
首先在 EasySwoole
全局事件 EasySwooleEvent.php
的 mainServerCreate
事件中注冊渲染引擎服務,注冊示例代碼如下:
<?php
namespace EasySwoole\EasySwoole;
use EasySwoole\EasySwoole\AbstractInterface\Event;
use EasySwoole\EasySwoole\Swoole\EventRegister;
class EasySwooleEvent implements Event
{
public static function initialize()
{
date_default_timezone_set('Asia/Shanghai');
}
public static function mainServerCreate(EventRegister $register)
{
// 獲取 Render 配置
$renderConfig = \EasySwoole\Template\Render::getInstance()->getConfig();
// [可選配置]
/*
$renderConfig->setTimeout(3); // 設置 超時時間,默認為 3s,不建議修改
$renderConfig->setServerName('EasySwoole'); // 設置 渲染引擎驅動服務名稱,不建議修改
$renderConfig->setWorkerNum(3); // 設置 渲染引擎服務工作進程數,默認為 3,不建議修改
*/
// 設置 渲染引擎模板驅動
$renderConfig->setRender(new \App\RenderDriver\Smarty());
// 設置 渲染引擎進程 Socket 存放目錄,默認為 getcwd()
$renderConfig->setTempDir(EASYSWOOLE_TEMP_DIR);
// 注冊進程到 EasySwoole 主服務
\EasySwoole\Template\Render::getInstance()->attachServer(\EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer());
}
}
在控制器層響應(使用示例代碼如下):
首先新建 App\View\custom.html
,內容如下:
{$name}
在控制器中進行調用:
<?php
namespace App\HttpController;
use EasySwoole\Http\AbstractInterface\Controller;
class Index extends Controller
{
public function index()
{
$this->response()->write(\EasySwoole\Template\Render::getInstance()->render('custom.html', ['name' => 'Welcome To Use EasySwoole ^_^!']));
}
}
運行結果:啟動服務,訪問 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 失敗
- 報錯結果類似如下:
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'
。示例代碼如下:
<?php
namespace EasySwoole\EasySwoole;
use EasySwoole\EasySwoole\AbstractInterface\Event;
use EasySwoole\EasySwoole\Swoole\EventRegister;
class EasySwooleEvent implements Event
{
public static function initialize()
{
date_default_timezone_set('Asia/Shanghai');
}
public static function mainServerCreate(EventRegister $register)
{
// 獲取 Render 配置
$renderConfig = \EasySwoole\Template\Render::getInstance()->getConfig();
// 設置 渲染引擎模板驅動
$renderConfig->setRender(new \App\RenderDriver\Smarty());
### 設置 渲染引擎進程 Socket 存放目錄為 '/Tmp' ###
$renderConfig->setTempDir('/Tmp');
// 注冊進程到 EasySwoole 主服務
\EasySwoole\Template\Render::getInstance()->attachServer(\EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer());
}
}