eee
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Blade;
|
||||
|
||||
/**
|
||||
* Blade 模板编译器
|
||||
* 将 Blade 语法编译为 PHP 代码
|
||||
*/
|
||||
class BladeCompiler
|
||||
{
|
||||
/**
|
||||
* Blade 指令映射
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $directives = [];
|
||||
|
||||
/**
|
||||
* 自定义指令处理器
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
protected array $customDirectives = [];
|
||||
|
||||
/**
|
||||
* 编译后的模板缓存目录
|
||||
*/
|
||||
protected string $cachePath;
|
||||
|
||||
/**
|
||||
* 视图文件目录
|
||||
*/
|
||||
protected string $viewPath;
|
||||
|
||||
/**
|
||||
* @param string $viewPath 视图文件路径
|
||||
* @param string $cachePath 编译缓存路径
|
||||
*/
|
||||
public function __construct(string $viewPath, string $cachePath)
|
||||
{
|
||||
$this->viewPath = rtrim($viewPath, '/\\');
|
||||
$this->cachePath = rtrim($cachePath, '/\\');
|
||||
$this->registerDefaultDirectives();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册默认指令
|
||||
*/
|
||||
protected function registerDefaultDirectives(): void
|
||||
{
|
||||
$this->directives = [
|
||||
'if' => 'if',
|
||||
'elseif' => 'elseif',
|
||||
'else' => 'else',
|
||||
'endif' => 'endif',
|
||||
'foreach' => 'foreach',
|
||||
'endforeach' => 'endforeach',
|
||||
'for' => 'for',
|
||||
'endfor' => 'endfor',
|
||||
'while' => 'while',
|
||||
'endwhile' => 'endwhile',
|
||||
'break' => 'break',
|
||||
'continue' => 'continue',
|
||||
'switch' => 'switch',
|
||||
'case' => 'case',
|
||||
'default' => 'default',
|
||||
'endswitch' => 'endswitch',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译模板文件
|
||||
*
|
||||
* @param string $view 视图名称
|
||||
* @return string 编译后的 PHP 文件路径
|
||||
*/
|
||||
public function compile(string $view): string
|
||||
{
|
||||
$viewFile = $this->viewPath . '/' . str_replace('.', '/', $view) . '.blade.php';
|
||||
|
||||
if (!file_exists($viewFile)) {
|
||||
throw new \RuntimeException("视图文件不存在: {$viewFile}");
|
||||
}
|
||||
|
||||
$compiledPath = $this->getCompiledPath($view);
|
||||
$compiledDir = dirname($compiledPath);
|
||||
|
||||
if (!is_dir($compiledDir)) {
|
||||
mkdir($compiledDir, 0755, true);
|
||||
}
|
||||
|
||||
// 如果源文件未修改,直接返回缓存的编译文件
|
||||
if (file_exists($compiledPath) && filemtime($viewFile) <= filemtime($compiledPath)) {
|
||||
return $compiledPath;
|
||||
}
|
||||
|
||||
$content = file_get_contents($viewFile);
|
||||
$compiled = $this->compileString($content);
|
||||
|
||||
file_put_contents($compiledPath, $compiled);
|
||||
|
||||
return $compiledPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译模板字符串
|
||||
*
|
||||
* @param string $content Blade 模板内容
|
||||
* @param bool $skipLayouts 是否跳过布局编译(避免递归)
|
||||
* @return string 编译后的 PHP 代码
|
||||
*/
|
||||
public function compileString(string $content, bool $skipLayouts = false): string
|
||||
{
|
||||
// 移除注释
|
||||
$content = $this->compileComments($content);
|
||||
|
||||
// 编译 Echo 语句
|
||||
$content = $this->compileEchos($content);
|
||||
|
||||
// 编译指令
|
||||
$content = $this->compileDirectives($content);
|
||||
|
||||
// 编译布局和继承(如果未跳过)
|
||||
if (!$skipLayouts) {
|
||||
$content = $this->compileLayouts($content);
|
||||
}
|
||||
|
||||
// 编译包含
|
||||
$content = $this->compileIncludes($content);
|
||||
|
||||
// 编译原始 PHP
|
||||
$content = $this->compilePhp($content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译注释 {{-- ... --}}
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileComments(string $content): string
|
||||
{
|
||||
return preg_replace('/\{\{--\s*(.*?)\s*--\}\}/s', '', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译 Echo 语句
|
||||
* {{ $var }} => <?php echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8'); ?>
|
||||
* {!! $var !!} => <?php echo $var; ?>
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileEchos(string $content): string
|
||||
{
|
||||
// 编译转义的 Echo {{ }}
|
||||
$content = preg_replace_callback('/\{\{\s*(.+?)\s*\}\}/', function ($matches) {
|
||||
$expression = trim($matches[1]);
|
||||
return "<?php echo htmlspecialchars({$expression}, ENT_QUOTES, 'UTF-8'); ?>";
|
||||
}, $content);
|
||||
|
||||
// 编译原始 Echo {!! !!}
|
||||
$content = preg_replace_callback('/\{!!\s*(.+?)\s*!!\}/', function ($matches) {
|
||||
$expression = trim($matches[1]);
|
||||
return "<?php echo {$expression}; ?>";
|
||||
}, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译指令 @if, @foreach 等
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileDirectives(string $content): string
|
||||
{
|
||||
foreach ($this->directives as $directive => $phpDirective) {
|
||||
$pattern = '/@' . $directive . '\s*\((.+?)\)/';
|
||||
$replacement = "<?php {$phpDirective} ($1): ?>";
|
||||
|
||||
$content = preg_replace($pattern, $replacement, $content);
|
||||
|
||||
// 处理结束指令
|
||||
$endPattern = '/@end' . $directive . '/';
|
||||
$endReplacement = "<?php end{$phpDirective}; ?>";
|
||||
$content = preg_replace($endPattern, $endReplacement, $content);
|
||||
}
|
||||
|
||||
// 处理自定义指令
|
||||
foreach ($this->customDirectives as $directive => $handler) {
|
||||
$pattern = '/@' . $directive . '\s*(?:\((.+?)\))?/';
|
||||
$content = preg_replace_callback($pattern, function ($matches) use ($handler) {
|
||||
$expression = $matches[1] ?? '';
|
||||
return $handler($expression);
|
||||
}, $content);
|
||||
}
|
||||
|
||||
// 处理 @else
|
||||
$content = preg_replace('/@else\b/', '<?php else: ?>', $content);
|
||||
|
||||
// 处理 @elseif
|
||||
$content = preg_replace('/@elseif\s*\((.+?)\)/', '<?php elseif ($1): ?>', $content);
|
||||
|
||||
// 处理 @case
|
||||
$content = preg_replace('/@case\s*\((.+?)\)/', '<?php case $1: ?>', $content);
|
||||
|
||||
// 处理 @default
|
||||
$content = preg_replace('/@default\b/', '<?php default: ?>', $content);
|
||||
|
||||
// 处理 @break 和 @continue
|
||||
$content = preg_replace('/@break\b/', '<?php break; ?>', $content);
|
||||
$content = preg_replace('/@continue\b/', '<?php continue; ?>', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译布局和继承
|
||||
* @extends('layout')
|
||||
* @section('content') ... @endsection
|
||||
* @yield('content')
|
||||
* @parent
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileLayouts(string $content): string
|
||||
{
|
||||
// 处理 @extends
|
||||
if (preg_match('/@extends\s*\([\'"](.+?)[\'"]\)/', $content, $matches)) {
|
||||
$layout = $matches[1];
|
||||
$content = preg_replace('/@extends\s*\([\'"](.+?)[\'"]\)/', '', $content);
|
||||
|
||||
// 提取所有 @section
|
||||
$sections = [];
|
||||
$content = preg_replace_callback('/@section\s*\([\'"](.+?)[\'"]\)\s*(.*?)\s*@endsection/s', function ($matches) use (&$sections) {
|
||||
$name = $matches[1];
|
||||
$sectionContent = trim($matches[2]);
|
||||
$sections[$name] = $sectionContent;
|
||||
return '';
|
||||
}, $content);
|
||||
|
||||
// 获取布局内容
|
||||
$layoutContent = $this->getLayoutContent($layout);
|
||||
|
||||
// 先编译 section 内容
|
||||
$compiledSections = [];
|
||||
foreach ($sections as $name => $sectionContent) {
|
||||
$compiledSections[$name] = $this->compileString($sectionContent, true);
|
||||
}
|
||||
|
||||
// 编译布局内容(跳过布局处理以避免递归)
|
||||
$layoutContent = $this->compileString($layoutContent, true);
|
||||
|
||||
// 替换 @yield 为对应的 section 内容
|
||||
foreach ($compiledSections as $name => $sectionContent) {
|
||||
$layoutContent = preg_replace('/@yield\s*\([\'"](?:' . preg_quote($name, '/') . ')[\'"]\)/', $sectionContent, $layoutContent);
|
||||
}
|
||||
|
||||
// 替换剩余的 @yield 为空
|
||||
$layoutContent = preg_replace('/@yield\s*\([\'"](.+?)[\'"]\)/', '', $layoutContent);
|
||||
|
||||
return $layoutContent;
|
||||
}
|
||||
|
||||
// 处理 @section ... @endsection (非继承模式,用于组件等)
|
||||
$content = preg_replace_callback('/@section\s*\([\'"](.+?)[\'"]\)\s*(.*?)\s*@endsection/s', function ($matches) {
|
||||
$name = $matches[1];
|
||||
$sectionContent = trim($matches[2]);
|
||||
// 处理 @parent
|
||||
$sectionContent = preg_replace('/@parent/', '', $sectionContent);
|
||||
return "<?php \$__sections['{$name}'] = function() { ?>{$sectionContent}<?php }; ?>";
|
||||
}, $content);
|
||||
|
||||
// 处理 @yield(非继承模式)
|
||||
$content = preg_replace_callback('/@yield\s*\([\'"](.+?)[\'"]\s*(?:,\s*[\'"](.+?)[\'"]\s*)?\)/', function ($matches) {
|
||||
$name = $matches[1];
|
||||
$default = $matches[2] ?? '';
|
||||
if ($default) {
|
||||
return "<?php echo isset(\$__sections['{$name}']) ? call_user_func(\$__sections['{$name}']) : '{$default}'; ?>";
|
||||
}
|
||||
return "<?php echo isset(\$__sections['{$name}']) ? call_user_func(\$__sections['{$name}']) : ''; ?>";
|
||||
}, $content);
|
||||
|
||||
// 处理 @parent(在 section 中使用)
|
||||
$content = preg_replace('/@parent/', '', $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译包含 @include('view')
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileIncludes(string $content): string
|
||||
{
|
||||
return preg_replace_callback('/@include\s*\([\'"](.+?)[\'"]\s*(?:,\s*\[(.+?)\])?\)/', function ($matches) {
|
||||
$view = $matches[1];
|
||||
$dataStr = $matches[2] ?? '';
|
||||
|
||||
// 解析数据数组
|
||||
if ($dataStr) {
|
||||
// 尝试解析数组字符串,如果失败则使用空数组
|
||||
try {
|
||||
$data = eval("return [{$dataStr}];");
|
||||
$dataCode = var_export($data, true);
|
||||
} catch (\Throwable $e) {
|
||||
$dataCode = '[]';
|
||||
}
|
||||
} else {
|
||||
$dataCode = '[]';
|
||||
}
|
||||
|
||||
// 使用静态方法调用,因为我们需要在运行时获取 BladeFactory 实例
|
||||
return "<?php echo \Kiri\Router\Blade\BladeHelper::include('{$view}', {$dataCode}); ?>";
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译原始 PHP @php ... @endphp
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compilePhp(string $content): string
|
||||
{
|
||||
return preg_replace('/@php\s*(.*?)\s*@endphp/s', '<?php $1 ?>', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取布局文件内容
|
||||
*
|
||||
* @param string $layout
|
||||
* @return string
|
||||
*/
|
||||
protected function getLayoutContent(string $layout): string
|
||||
{
|
||||
$layoutFile = $this->viewPath . '/' . str_replace('.', '/', $layout) . '.blade.php';
|
||||
|
||||
if (!file_exists($layoutFile)) {
|
||||
throw new \RuntimeException("布局文件不存在: {$layoutFile}");
|
||||
}
|
||||
|
||||
return file_get_contents($layoutFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编译后的文件路径
|
||||
*
|
||||
* @param string $view
|
||||
* @return string
|
||||
*/
|
||||
protected function getCompiledPath(string $view): string
|
||||
{
|
||||
$hash = md5($view);
|
||||
return $this->cachePath . '/' . substr($hash, 0, 2) . '/' . substr($hash, 2, 2) . '/' . $hash . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册自定义指令
|
||||
*
|
||||
* @param string $name 指令名称
|
||||
* @param callable $handler 处理函数
|
||||
*/
|
||||
public function directive(string $name, callable $handler): void
|
||||
{
|
||||
$this->customDirectives[$name] = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除编译缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
if (is_dir($this->cachePath)) {
|
||||
$this->deleteDirectory($this->cachePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归删除目录
|
||||
*
|
||||
* @param string $dir
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteDirectory(string $dir): void
|
||||
{
|
||||
if (!is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$path = $dir . '/' . $file;
|
||||
is_dir($path) ? $this->deleteDirectory($path) : unlink($path);
|
||||
}
|
||||
|
||||
rmdir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Blade;
|
||||
|
||||
/**
|
||||
* Blade 视图工厂
|
||||
* 管理视图路径、缓存和编译器
|
||||
*/
|
||||
class BladeFactory
|
||||
{
|
||||
/**
|
||||
* 视图文件路径
|
||||
*/
|
||||
protected string $viewPath;
|
||||
|
||||
/**
|
||||
* 编译缓存路径
|
||||
*/
|
||||
protected string $cachePath;
|
||||
|
||||
/**
|
||||
* Blade 编译器实例
|
||||
*/
|
||||
protected ?BladeCompiler $compiler = null;
|
||||
|
||||
/**
|
||||
* 共享数据(所有视图可用)
|
||||
*/
|
||||
protected array $shared = [];
|
||||
|
||||
/**
|
||||
* @param string $viewPath 视图文件路径
|
||||
* @param string $cachePath 编译缓存路径
|
||||
*/
|
||||
public function __construct(string $viewPath, string $cachePath)
|
||||
{
|
||||
$this->viewPath = rtrim($viewPath, '/\\');
|
||||
$this->cachePath = rtrim($cachePath, '/\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编译器实例
|
||||
*
|
||||
* @return BladeCompiler
|
||||
*/
|
||||
public function getCompiler(): BladeCompiler
|
||||
{
|
||||
if ($this->compiler === null) {
|
||||
$this->compiler = new BladeCompiler($this->viewPath, $this->cachePath);
|
||||
}
|
||||
|
||||
return $this->compiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建视图实例
|
||||
*
|
||||
* @param string $view 视图名称
|
||||
* @param array $data 视图数据
|
||||
* @return BladeView
|
||||
*/
|
||||
public function make(string $view, array $data = []): BladeView
|
||||
{
|
||||
// 合并共享数据
|
||||
$data = array_merge($this->shared, $data);
|
||||
|
||||
return new BladeView($view, $data, $this->getCompiler());
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染视图
|
||||
*
|
||||
* @param string $view 视图名称
|
||||
* @param array $data 视图数据
|
||||
* @return string
|
||||
*/
|
||||
public function render(string $view, array $data = []): string
|
||||
{
|
||||
return $this->make($view, $data)->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* 共享数据到所有视图
|
||||
*
|
||||
* @param string|array $key 键名或数据数组
|
||||
* @param mixed $value 值(当 $key 为数组时忽略)
|
||||
* @return $this
|
||||
*/
|
||||
public function share(string|array $key, mixed $value = null): self
|
||||
{
|
||||
if (is_array($key)) {
|
||||
$this->shared = array_merge($this->shared, $key);
|
||||
} else {
|
||||
$this->shared[$key] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册自定义指令
|
||||
*
|
||||
* @param string $name 指令名称
|
||||
* @param callable $handler 处理函数
|
||||
* @return $this
|
||||
*/
|
||||
public function directive(string $name, callable $handler): self
|
||||
{
|
||||
$this->getCompiler()->directive($name, $handler);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除编译缓存
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
$this->getCompiler()->clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视图路径
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getViewPath(): string
|
||||
{
|
||||
return $this->viewPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存路径
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCachePath(): string
|
||||
{
|
||||
return $this->cachePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Blade;
|
||||
|
||||
/**
|
||||
* Blade 辅助函数
|
||||
*/
|
||||
class BladeHelper
|
||||
{
|
||||
/**
|
||||
* 全局 BladeFactory 实例
|
||||
*/
|
||||
protected static ?BladeFactory $factory = null;
|
||||
|
||||
/**
|
||||
* 设置全局 BladeFactory 实例
|
||||
*
|
||||
* @param BladeFactory $factory
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setFactory(BladeFactory $factory): void
|
||||
{
|
||||
self::$factory = $factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局 BladeFactory 实例
|
||||
*
|
||||
* @return BladeFactory
|
||||
*/
|
||||
public static function getFactory(): BladeFactory
|
||||
{
|
||||
if (self::$factory === null) {
|
||||
$viewPath = APP_PATH . 'resources/view';
|
||||
$cachePath = storage(null, 'view/cache');
|
||||
self::$factory = new BladeFactory($viewPath, $cachePath);
|
||||
}
|
||||
|
||||
return self::$factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包含视图
|
||||
*
|
||||
* @param string $view
|
||||
* @param array $data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function include(string $view, array $data = []): string
|
||||
{
|
||||
return self::getFactory()
|
||||
->render($view, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Blade;
|
||||
|
||||
/**
|
||||
* Blade 视图类
|
||||
* 管理视图的渲染和数据传递
|
||||
*/
|
||||
class BladeView
|
||||
{
|
||||
/**
|
||||
* 视图名称
|
||||
*/
|
||||
protected string $view;
|
||||
|
||||
/**
|
||||
* 视图数据
|
||||
*/
|
||||
protected array $data;
|
||||
|
||||
/**
|
||||
* Blade 编译器实例
|
||||
*/
|
||||
protected BladeCompiler $compiler;
|
||||
|
||||
/**
|
||||
* @param string $view 视图名称
|
||||
* @param array $data 视图数据
|
||||
* @param BladeCompiler $compiler 编译器实例
|
||||
*/
|
||||
public function __construct(string $view, array $data, BladeCompiler $compiler)
|
||||
{
|
||||
$this->view = $view;
|
||||
$this->data = $data;
|
||||
$this->compiler = $compiler;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染视图
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render(): string
|
||||
{
|
||||
// 编译视图
|
||||
$compiledPath = $this->compiler->compile($this->view);
|
||||
|
||||
// 提取数据
|
||||
extract($this->data, EXTR_SKIP);
|
||||
|
||||
// 开始输出缓冲
|
||||
ob_start();
|
||||
|
||||
try {
|
||||
// 包含编译后的 PHP 文件
|
||||
require $compiledPath;
|
||||
} catch (\Throwable $e) {
|
||||
ob_end_clean();
|
||||
throw new \RuntimeException("视图渲染失败: {$this->view}", 0, $e);
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视图名称
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getView(): string
|
||||
{
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视图数据
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
# Kiri Blade 模板引擎
|
||||
|
||||
这是一个类似 Laravel Blade 的模板引擎实现,用于 kiri-router 项目。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 变量输出:`{{ $variable }}` 和 `{!! $variable !!}`
|
||||
- ✅ 控制结构:`@if`, `@elseif`, `@else`, `@endif`
|
||||
- ✅ 循环:`@foreach`, `@endforeach`, `@for`, `@endfor`, `@while`, `@endwhile`
|
||||
- ✅ 布局继承:`@extends`, `@section`, `@yield`, `@endsection`
|
||||
- ✅ 包含视图:`@include`
|
||||
- ✅ 注释:`{{-- comment --}}`
|
||||
- ✅ 原始 PHP:`@php ... @endphp`
|
||||
- ✅ 编译缓存:自动缓存编译后的模板以提高性能
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```php
|
||||
use function Kiri\Router\View;
|
||||
|
||||
// 在控制器中渲染视图
|
||||
return View('user.profile', [
|
||||
'name' => 'John Doe',
|
||||
'email' => 'john@example.com'
|
||||
]);
|
||||
```
|
||||
|
||||
### 视图文件结构
|
||||
|
||||
视图文件应放在 `resources/view` 目录下,使用 `.blade.php` 扩展名:
|
||||
|
||||
```
|
||||
resources/
|
||||
view/
|
||||
layouts/
|
||||
app.blade.php
|
||||
user/
|
||||
profile.blade.php
|
||||
components/
|
||||
header.blade.php
|
||||
```
|
||||
|
||||
### 模板语法示例
|
||||
|
||||
#### 1. 变量输出
|
||||
|
||||
```blade
|
||||
<!-- 转义输出 -->
|
||||
<h1>{{ $title }}</h1>
|
||||
<p>{{ $description }}</p>
|
||||
|
||||
<!-- 原始输出(不转义) -->
|
||||
<div>{!! $htmlContent !!}</div>
|
||||
```
|
||||
|
||||
#### 2. 条件语句
|
||||
|
||||
```blade
|
||||
@if($user->isAdmin())
|
||||
<p>管理员</p>
|
||||
@elseif($user->isModerator())
|
||||
<p>版主</p>
|
||||
@else
|
||||
<p>普通用户</p>
|
||||
@endif
|
||||
```
|
||||
|
||||
#### 3. 循环
|
||||
|
||||
```blade
|
||||
@foreach($users as $user)
|
||||
<div>
|
||||
<h3>{{ $user->name }}</h3>
|
||||
<p>{{ $user->email }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@for($i = 0; $i < 10; $i++)
|
||||
<p>Item {{ $i }}</p>
|
||||
@endfor
|
||||
```
|
||||
|
||||
#### 4. 布局继承
|
||||
|
||||
**layouts/app.blade.php:**
|
||||
```blade
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>@yield('title', '默认标题')</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@include('components.header')
|
||||
</header>
|
||||
|
||||
<main>
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2024</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
**user/profile.blade.php:**
|
||||
```blade
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title')
|
||||
用户资料
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<h1>用户资料</h1>
|
||||
<p>姓名:{{ $name }}</p>
|
||||
<p>邮箱:{{ $email }}</p>
|
||||
@endsection
|
||||
```
|
||||
|
||||
#### 5. 包含视图
|
||||
|
||||
```blade
|
||||
@include('components.header')
|
||||
|
||||
@include('components.footer', ['year' => 2024])
|
||||
```
|
||||
|
||||
#### 6. 注释
|
||||
|
||||
```blade
|
||||
{{-- 这是注释,不会输出到 HTML --}}
|
||||
```
|
||||
|
||||
#### 7. 原始 PHP
|
||||
|
||||
```blade
|
||||
@php
|
||||
$count = count($items);
|
||||
$total = array_sum($prices);
|
||||
@endphp
|
||||
|
||||
<p>总数:{{ $count }}</p>
|
||||
```
|
||||
|
||||
## 高级用法
|
||||
|
||||
### 自定义指令
|
||||
|
||||
```php
|
||||
use Kiri\Router\Blade\BladeHelper;
|
||||
|
||||
$factory = BladeHelper::getFactory();
|
||||
|
||||
// 注册自定义指令
|
||||
$factory->directive('datetime', function ($expression) {
|
||||
return "<?php echo date('Y-m-d H:i:s', {$expression}); ?>";
|
||||
});
|
||||
```
|
||||
|
||||
使用自定义指令:
|
||||
|
||||
```blade
|
||||
@datetime(time())
|
||||
```
|
||||
|
||||
### 共享数据
|
||||
|
||||
```php
|
||||
use Kiri\Router\Blade\BladeHelper;
|
||||
|
||||
$factory = BladeHelper::getFactory();
|
||||
|
||||
// 共享数据到所有视图
|
||||
$factory->share('siteName', 'My Website');
|
||||
$factory->share([
|
||||
'version' => '1.0.0',
|
||||
'author' => 'Kiri Team'
|
||||
]);
|
||||
```
|
||||
|
||||
### 清除缓存
|
||||
|
||||
```php
|
||||
use Kiri\Router\Blade\BladeHelper;
|
||||
|
||||
$factory = BladeHelper::getFactory();
|
||||
$factory->clearCache();
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
视图路径和缓存路径可以通过常量配置:
|
||||
|
||||
```php
|
||||
// 视图路径
|
||||
define('APP_PATH', __DIR__ . '/');
|
||||
|
||||
// 缓存路径(可选)
|
||||
define('RUNTIME_PATH', __DIR__ . '/runtime');
|
||||
```
|
||||
|
||||
默认情况下:
|
||||
- 视图路径:`APP_PATH . 'resources/view'`
|
||||
- 缓存路径:`RUNTIME_PATH . '/blade'` 或系统临时目录
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 模板文件必须使用 `.blade.php` 扩展名
|
||||
2. 视图路径使用点号分隔,如 `user.profile` 对应 `user/profile.blade.php`
|
||||
3. 编译后的模板会自动缓存,修改源文件后会自动重新编译
|
||||
4. 建议在生产环境中定期清理缓存目录
|
||||
|
||||
+23
-14
@@ -4,13 +4,17 @@ declare(strict_types=1);
|
||||
namespace Kiri\Router;
|
||||
|
||||
use Kiri\Di\Interface\ResponseEmitterInterface;
|
||||
use Kiri\Router\Blade\BladeFactory;
|
||||
use Kiri\Router\Blade\BladeHelper;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param array $data
|
||||
* 渲染 Blade 视图
|
||||
*
|
||||
* @param string $path 视图路径(支持 . 分隔,如 'user.profile')
|
||||
* @param array $data 视图数据
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
@@ -18,21 +22,26 @@ function View(string $path, array $data = []): ResponseInterface
|
||||
{
|
||||
$response = \response();
|
||||
$response->withAddedHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
|
||||
try {
|
||||
ob_start();
|
||||
$__path = $path;
|
||||
$__data = $data;
|
||||
|
||||
extract($__data, EXTR_SKIP);
|
||||
|
||||
require APP_PATH . 'resources/view/' . $path . '.php';
|
||||
|
||||
$content = ltrim(ob_get_clean());
|
||||
// 获取视图路径和缓存路径
|
||||
$viewPath = APP_PATH . 'resources/view';
|
||||
$cachePath = storage(null, 'view/cache');
|
||||
|
||||
// 创建或获取 BladeFactory 实例
|
||||
$factory = BladeHelper::getFactory();
|
||||
if ($factory->getViewPath() !== $viewPath) {
|
||||
$factory = new BladeFactory($viewPath, $cachePath);
|
||||
BladeHelper::setFactory($factory);
|
||||
}
|
||||
|
||||
// 渲染视图
|
||||
$content = $factory->render($path, $data);
|
||||
} catch (\Exception $e) {
|
||||
$content = throwable($e);
|
||||
} finally {
|
||||
return $response->html($content);
|
||||
$content = function_exists('throwable') ? throwable($e) : $e->getMessage();
|
||||
}
|
||||
|
||||
return $response->html($content);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user