Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d098b293a4 | |||
| f2f2ee408c | |||
| 76d2d2c64e | |||
| 8778cd220f | |||
| 6929bed90a | |||
| 2f08fad1ef | |||
| 91e0fe8940 | |||
| 34051feb87 | |||
| d32fb67650 | |||
| b28b45b15e | |||
| 371e11baa9 | |||
| 5358205fbe | |||
| f4430e2dd0 | |||
| 3a41eaa95c | |||
| 6cb95f4749 | |||
| 299255ce7a | |||
| 51587768cf | |||
| 8165b61f72 | |||
| 0c17482b20 | |||
| 4c9f33a6dc | |||
| 42f945c195 | |||
| a11e6a3eee | |||
| dfa50c9448 | |||
| ad56abe39d | |||
| c148c6135a | |||
| 24cb6e2b5b | |||
| 443df009e1 | |||
| 7b9475a1c9 | |||
| 67bd83933e | |||
| 1f4916f661 | |||
| 44f8bc6535 | |||
| 30cc98c00c | |||
| 9968ea778e | |||
| 870e9b530e | |||
| 669b8ed49f | |||
| d626d5bed7 | |||
| ea3eacdb84 | |||
| 01ad7d7416 | |||
| 58cb9b53db | |||
| c243348e78 | |||
| 05d3110522 | |||
| 1b08ae2be3 | |||
| 45ed584435 | |||
| 5ba0f45528 | |||
| 791267c26f | |||
| 8d53079ba4 | |||
| 2d42ed7090 | |||
| 99af7df9fb | |||
| 6990ef5bcc | |||
| 705e916bda | |||
| 92dd57429d | |||
| 975e7a3cd0 | |||
| 15d54e8ffe | |||
| 7b1767ab5f | |||
| 804b9bd67f | |||
| 153667f0b4 | |||
| 89ac36c227 | |||
| 79ce7142d9 | |||
| 90ee572092 | |||
| 926cbea0b9 | |||
| 3251045a5b | |||
| 5b8163b79e | |||
| be99f4dcbb | |||
| cd79309db3 | |||
| 0c2462feee | |||
| 4c4a21dd7a | |||
| 8623a036ed | |||
| f38942f4f3 | |||
| dc561cec9b | |||
| daa02a6408 | |||
| 34ab8f145c | |||
| ae20755bd7 | |||
| 011e95a3f2 | |||
| 5bda66b40d | |||
| e82fad2fcb | |||
| 16eb6b11c5 | |||
| 227b6fa512 | |||
| c06ab29054 | |||
| 3ccf08fdfb | |||
| 292ccc84de | |||
| edc7371d9b | |||
| 8440104053 | |||
| 3bc65b8776 | |||
| 7aee7e158f | |||
| e9989e36d9 | |||
| 814d6c6054 | |||
| 13a2d037c3 | |||
| 8811f2bcd6 | |||
| 080cd6ab92 | |||
| c435af1156 | |||
| 337c52c744 | |||
| 47d864bbf7 | |||
| 4197b16132 | |||
| dcf6940062 | |||
| 2baf2847ab | |||
| 3b5a4adbc9 | |||
| e307accb7c | |||
| e59b78d2b1 | |||
| ac4315bcb9 | |||
| 1efb583194 | |||
| dfeb79aaea | |||
| 2058849059 | |||
| abb69cff20 | |||
| b149fbf693 | |||
| b2e641c159 | |||
| be1badfa5c | |||
| 1795b59b88 | |||
| 6efd221585 | |||
| ebc39a6938 | |||
| 26d32fbe7f |
@@ -1 +1,81 @@
|
||||
# kiri-router
|
||||
|
||||
## Session 使用说明
|
||||
|
||||
### 基本用法
|
||||
|
||||
```php
|
||||
use Kiri\Router\Session;
|
||||
|
||||
// 存储 Session 数据
|
||||
Session::put('user_id', 123);
|
||||
Session::put('username', 'admin');
|
||||
|
||||
// 或者批量设置
|
||||
Session::put(['user_id' => 123, 'username' => 'admin']);
|
||||
|
||||
// 获取 Session 数据
|
||||
$userId = Session::get('user_id');
|
||||
$username = Session::get('username', 'guest'); // 带默认值
|
||||
|
||||
// 获取所有 Session 数据
|
||||
$all = Session::get(); // 或 Session::all()
|
||||
|
||||
// 检查 Session 中是否存在指定键
|
||||
if (Session::has('user_id')) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 删除 Session 数据
|
||||
Session::forget('user_id');
|
||||
|
||||
// 清空所有 Session 数据
|
||||
Session::flush();
|
||||
|
||||
// 销毁 Session(包括文件)
|
||||
Session::destroy();
|
||||
|
||||
// 重新生成 Session ID
|
||||
Session::regenerate();
|
||||
```
|
||||
|
||||
### 使用中间件自动管理 Session
|
||||
|
||||
在配置文件中添加 SessionMiddleware 到中间件列表:
|
||||
|
||||
```php
|
||||
// config/request.php
|
||||
return [
|
||||
'middlewares' => [
|
||||
\Kiri\Router\Base\SessionMiddleware::class,
|
||||
// 其他中间件...
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
或者使用注解在控制器方法上:
|
||||
|
||||
```php
|
||||
use Kiri\Router\Annotate\Middleware;
|
||||
use Kiri\Router\Base\SessionMiddleware;
|
||||
|
||||
class UserController
|
||||
{
|
||||
#[Middleware(SessionMiddleware::class)]
|
||||
public function login()
|
||||
{
|
||||
Session::put('user_id', 123);
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 配置 Session
|
||||
|
||||
```php
|
||||
// 初始化 Session(可选,有默认值)
|
||||
Session::init(
|
||||
savePath: storage(null, 'session'), // Session 存储路径
|
||||
lifetime: 7200 // Session 生命周期(秒),默认2小时
|
||||
);
|
||||
```
|
||||
+5
-4
@@ -9,7 +9,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"php": ">=8.4",
|
||||
"composer-runtime-api": "^2.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"psr/http-message": "^1.0"
|
||||
@@ -17,8 +17,9 @@
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Kiri\\Router\\": "./src"
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
},
|
||||
"files": [
|
||||
"./src/function.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<nav style="background: #f0f0f0; padding: 10px;">
|
||||
<ul style="list-style: none; display: flex; gap: 20px; margin: 0; padding: 0;">
|
||||
<li><a href="/">首页</a></li>
|
||||
<li><a href="/about">关于</a></li>
|
||||
<li><a href="/contact">联系</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>@yield('title', 'Kiri Blade 示例')</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
header {
|
||||
background: #333;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
footer {
|
||||
background: #f5f5f5;
|
||||
padding: 1rem;
|
||||
margin-top: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>@yield('header', 'Kiri Blade 模板引擎')</h1>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© {{ date('Y') }} Kiri Framework. 使用 Blade 模板引擎构建。</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Blade 模板引擎测试示例
|
||||
*
|
||||
* 使用方法:
|
||||
* php examples/test-blade.php
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Kiri\Router\Blade\BladeFactory;
|
||||
use Kiri\Router\Blade\BladeHelper;
|
||||
|
||||
// 设置视图路径和缓存路径
|
||||
$viewPath = __DIR__ . '/';
|
||||
$cachePath = __DIR__ . '/cache';
|
||||
|
||||
// 创建 BladeFactory 实例
|
||||
$factory = new BladeFactory($viewPath, $cachePath);
|
||||
BladeHelper::setFactory($factory);
|
||||
|
||||
// 准备测试数据
|
||||
$data = [
|
||||
'name' => '张三',
|
||||
'email' => 'zhangsan@example.com',
|
||||
'age' => 28,
|
||||
'skills' => ['PHP', 'JavaScript', 'MySQL', 'NoSql'],
|
||||
'posts' => [
|
||||
[
|
||||
'title' => 'Blade 模板引擎介绍',
|
||||
'content' => '这是一个类似 Laravel Blade 的模板引擎实现。',
|
||||
'date' => '2024-01-15'
|
||||
],
|
||||
[
|
||||
'title' => '如何使用 Blade',
|
||||
'content' => 'Blade 提供了简洁优雅的模板语法。',
|
||||
'date' => '2024-01-20'
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
// 渲染视图
|
||||
try {
|
||||
echo "开始渲染视图...\n\n";
|
||||
$html = $factory->render('user.profile', $data);
|
||||
echo $html;
|
||||
echo "\n\n渲染完成!\n";
|
||||
} catch (\Exception $e) {
|
||||
echo "错误: " . $e->getMessage() . "\n";
|
||||
echo "文件: " . $e->getFile() . "\n";
|
||||
echo "行号: " . $e->getLine() . "\n";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title')
|
||||
用户资料 - Kiri Blade
|
||||
@endsection
|
||||
|
||||
@section('header')
|
||||
用户资料页面
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<h2>用户信息</h2>
|
||||
|
||||
@if(isset($name))
|
||||
<p><strong>姓名:</strong>{{ $name }}</p>
|
||||
@else
|
||||
<p>姓名未设置</p>
|
||||
@endif
|
||||
|
||||
@if(isset($email))
|
||||
<p><strong>邮箱:</strong>{{ $email }}</p>
|
||||
@endif
|
||||
|
||||
@if(isset($age))
|
||||
<p><strong>年龄:</strong>{{ $age }} 岁</p>
|
||||
@endif
|
||||
|
||||
<h3>技能列表</h3>
|
||||
@if(isset($skills) && is_array($skills))
|
||||
<ul>
|
||||
@foreach($skills as $skill)
|
||||
<li>{{ $skill }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@else
|
||||
<p>暂无技能</p>
|
||||
@endif
|
||||
|
||||
<h3>文章列表</h3>
|
||||
@if(isset($posts) && is_array($posts))
|
||||
<div>
|
||||
@foreach($posts as $post)
|
||||
<div style="border: 1px solid #ddd; padding: 10px; margin: 10px 0;">
|
||||
<h4>{{ $post['title'] ?? '无标题' }}</h4>
|
||||
<p>{{ $post['content'] ?? '无内容' }}</p>
|
||||
<small>发布时间:{{ $post['date'] ?? '未知' }}</small>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<p>暂无文章</p>
|
||||
@endif
|
||||
@endsection
|
||||
|
||||
@@ -28,14 +28,12 @@ class Delete extends AbstractRequestMethod implements InjectMethodInterface
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
$controller = \Kiri::getDi()->get($class);
|
||||
|
||||
// TODO: Implement dispatch() method.
|
||||
$path = '/' . ltrim($this->path, '/');
|
||||
if (!empty($this->version)) {
|
||||
$path = '/' . trim($this->version) . $path;
|
||||
}
|
||||
Router::addRoute(RequestMethod::REQUEST_DELETE, $path, [$controller, $method]);
|
||||
Router::addRoute(RequestMethod::REQUEST_DELETE, $path, [$class, $method]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,14 +28,12 @@ class Get extends AbstractRequestMethod implements InjectMethodInterface
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
$controller = \Kiri::getDi()->get($class);
|
||||
|
||||
// TODO: Implement dispatch() method.
|
||||
$path = '/' . ltrim($this->path, '/');
|
||||
if (!empty($this->version)) {
|
||||
$path = '/' . trim($this->version) . $path;
|
||||
}
|
||||
Router::addRoute(RequestMethod::REQUEST_GET, $path, [$controller, $method]);
|
||||
Router::addRoute(RequestMethod::REQUEST_GET, $path, [$class, $method]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,13 +29,12 @@ class Head extends AbstractRequestMethod implements InjectMethodInterface
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
$controller = Kiri::getDi()->get($class);
|
||||
// TODO: Implement dispatch() method.
|
||||
$path = '/' . ltrim($this->path, '/');
|
||||
if (!empty($this->version)) {
|
||||
$path = '/' . trim($this->version) . $path;
|
||||
}
|
||||
Router::addRoute(RequestMethod::REQUEST_HEAD, $path, [$controller, $method]);
|
||||
Router::addRoute(RequestMethod::REQUEST_HEAD, $path, [$class, $method]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class Middleware implements InjectMethodInterface
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
MiddlewareManager::set($class, $method, $this->middleware);
|
||||
// MiddlewareManager::set($class, $method, $this->middleware);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -30,14 +30,12 @@ class Options extends AbstractRequestMethod implements InjectMethodInterface
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
$controller = Kiri::getDi()->get($class);
|
||||
|
||||
// TODO: Implement dispatch() method.
|
||||
$path = '/' . ltrim($this->path, '/');
|
||||
if (!empty($this->version)) {
|
||||
$path = '/' . trim($this->version) . $path;
|
||||
}
|
||||
Router::addRoute(RequestMethod::REQUEST_OPTIONS, $path, [$controller, $method]);
|
||||
Router::addRoute(RequestMethod::REQUEST_OPTIONS, $path, [$class, $method]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -30,13 +30,11 @@ class Post extends AbstractRequestMethod implements InjectMethodInterface
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
// TODO: Implement dispatch() method.
|
||||
$controller = Kiri::getDi()->get($class);
|
||||
|
||||
$path = '/' . ltrim($this->path, '/');
|
||||
if (!empty($this->version)) {
|
||||
$path = '/' . trim($this->version) . $path;
|
||||
}
|
||||
Router::addRoute(RequestMethod::REQUEST_POST, $path, [$controller, $method]);
|
||||
Router::addRoute(RequestMethod::REQUEST_POST, $path, [$class, $method]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,13 +32,12 @@ class Put extends AbstractRequestMethod implements InjectMethodInterface
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
$controller = Kiri::getDi()->get($class);
|
||||
// TODO: Implement dispatch() method.
|
||||
$path = '/' . ltrim($this->path, '/');
|
||||
if (!empty($this->version)) {
|
||||
$path = '/' . trim($this->version) . $path;
|
||||
}
|
||||
Router::addRoute(RequestMethod::REQUEST_PUT, $path, [$controller, $method]);
|
||||
Router::addRoute(RequestMethod::REQUEST_PUT, $path, [$class, $method]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,11 +28,10 @@ class Route extends AbstractRequestMethod implements InjectMethodInterface
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
$controller = \Kiri::getDi()->get($class);
|
||||
$path = '/' . ltrim($this->path, '/');
|
||||
if (!empty($this->version)) {
|
||||
$path = '/' . trim($this->version) . $path;
|
||||
}
|
||||
Router::addRoute([$this->method], $path, [$controller, $method]);
|
||||
Router::addRoute([$this->method], $path, [$class, $method]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
namespace Kiri\Router\Base;
|
||||
|
||||
use Kiri\Router\Handler;
|
||||
use Kiri\Router\Validator\ValidatorMiddleware;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
@@ -12,39 +13,50 @@ abstract class AbstractHandler
|
||||
{
|
||||
|
||||
|
||||
public int $offset = 0;
|
||||
|
||||
public array $middlewares = [];
|
||||
public int $offset = 0;
|
||||
|
||||
|
||||
/**
|
||||
* @param array $middlewares
|
||||
* @param Handler $handler
|
||||
* @throws
|
||||
*/
|
||||
public function __construct(array $middlewares, public Handler $handler)
|
||||
{
|
||||
foreach ($middlewares as $middleware) {
|
||||
$this->middlewares[] = di($middleware);
|
||||
}
|
||||
}
|
||||
private ?ValidatorMiddleware $middleware = null;
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @return ResponseInterface
|
||||
* @throws
|
||||
*/
|
||||
public function execute(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!isset($this->middlewares[$this->offset])) {
|
||||
return $this->handler->handle($request);
|
||||
}
|
||||
|
||||
/** @var MiddlewareInterface $middleware */
|
||||
$middleware = $this->middlewares[$this->offset];
|
||||
$this->offset += 1;
|
||||
/**
|
||||
* @param array $middlewares
|
||||
* @param Handler $handler
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public function __construct(public array $middlewares, public Handler $handler)
|
||||
{
|
||||
}
|
||||
|
||||
return $middleware->process($request, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws
|
||||
*/
|
||||
public function execute(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if (!isset($this->middlewares[$this->offset])) {
|
||||
return $this->handler->handle($request);
|
||||
}
|
||||
|
||||
$middleware = $this->middlewares[$this->offset];
|
||||
$this->offset += 1;
|
||||
|
||||
return $middleware->process($request, $this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param ValidatorMiddleware $middleware
|
||||
* @return void
|
||||
*/
|
||||
public function withValidatorMiddleware(ValidatorMiddleware $middleware): void
|
||||
{
|
||||
$this->middlewares[] = $middleware;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-21
@@ -5,36 +5,43 @@ namespace Kiri\Router\Base;
|
||||
|
||||
|
||||
use Kiri;
|
||||
use Kiri\Router\Response;
|
||||
use Kiri\Router\Request;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Kiri\Di\Inject\Container;
|
||||
use Kiri\Error\StdoutLogger;
|
||||
|
||||
/**
|
||||
* Class WebController
|
||||
* @package Kiri\Web
|
||||
*/
|
||||
abstract class Controller
|
||||
class Controller extends Kiri\Abstracts\Component
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var Request
|
||||
* @var RequestInterface
|
||||
*/
|
||||
#[Container(RequestInterface::class)]
|
||||
public RequestInterface $request;
|
||||
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
* @var ResponseInterface
|
||||
*/
|
||||
#[Container(ResponseInterface::class)]
|
||||
public ResponseInterface $response;
|
||||
|
||||
|
||||
/**
|
||||
* @var LoggerInterface|StdoutLogger
|
||||
*/
|
||||
#[Container(LoggerInterface::class)]
|
||||
public LoggerInterface|StdoutLogger $logger;
|
||||
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
@@ -42,20 +49,4 @@ abstract class Controller
|
||||
public ContainerInterface $container;
|
||||
|
||||
|
||||
/**
|
||||
* @var Kiri\Error\StdoutLogger
|
||||
*/
|
||||
#[Container(LoggerInterface::class)]
|
||||
public Kiri\Error\StdoutLogger $logger;
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return true
|
||||
*/
|
||||
public function beforeAction(RequestInterface $request): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Base;
|
||||
|
||||
use Kiri\Di\Inject\Container;
|
||||
use Kiri\Router\Constrict\Stream;
|
||||
use Kiri\Router\Request;
|
||||
use Kiri\Router\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
@@ -18,14 +17,6 @@ use Psr\Http\Server\RequestHandlerInterface;
|
||||
class CoreMiddleware implements MiddlewareInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
#[Container(ResponseInterface::class)]
|
||||
public ResponseInterface $response;
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
@@ -34,7 +25,12 @@ class CoreMiddleware implements MiddlewareInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$this->response->withHeaders(['Access-Control-Allow-Headers' => '*', 'Access-Control-Request-Method' => '*', 'Access-Control-Allow-Origin' => '*']);
|
||||
$response = response();
|
||||
$response->withHeaders([
|
||||
'Access-Control-Allow-Headers' => $request->getHeaderLine('access-control-request-headers'),
|
||||
'Access-Control-Request-Method' => $request->getHeaderLine('access-control-request-method'),
|
||||
'Access-Control-Allow-Origin' => $request->getHeaderLine('origin')
|
||||
]);
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class ExceptionHandlerDispatcher implements ExceptionHandlerInterface
|
||||
*/
|
||||
public function emit(Throwable $exception, object $response): ResponseInterface
|
||||
{
|
||||
error($exception);
|
||||
\Kiri::getLogger()->json_log($exception);
|
||||
$response->withContentType(ContentType::HTML)->withBody(new Stream(throwable($exception)));
|
||||
if ($exception->getCode() == 404) {
|
||||
return $response->withStatus(404);
|
||||
|
||||
+30
-19
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
||||
namespace Kiri\Router\Base;
|
||||
|
||||
use Kiri;
|
||||
use Kiri\Router\Validator\Validator;
|
||||
use Kiri\Router\Validator\ValidatorMiddleware;
|
||||
|
||||
class Middleware
|
||||
{
|
||||
@@ -12,36 +14,25 @@ class Middleware
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static array $manager = [];
|
||||
|
||||
|
||||
protected static array $mapping = [];
|
||||
|
||||
|
||||
protected static array $validators = [];
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param string $method
|
||||
* @param string $middleware
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public static function set(string $className, string $method, string|object $middleware): void
|
||||
public static function set(string $className, string $method, string $middleware): void
|
||||
{
|
||||
$path = $className . '::' . $method;
|
||||
if (!isset(static::$manager[$path])) {
|
||||
static::$manager[$path] = static::$mapping[$path] = [];
|
||||
if (!isset(static::$mapping[$path])) {
|
||||
static::$mapping[$path] = [];
|
||||
}
|
||||
|
||||
if (is_object($middleware)) {
|
||||
if (!in_array($middleware::class, static::$mapping[$path])) {
|
||||
static::$manager[$path][] = $middleware;
|
||||
static::$mapping[$path][] = $middleware::class;
|
||||
}
|
||||
} else {
|
||||
if (!in_array($middleware, static::$mapping[$path])) {
|
||||
static::$manager[$path][] = Kiri::getDi()->get($middleware);
|
||||
static::$mapping[$path][] = $middleware;
|
||||
}
|
||||
if (!in_array($middleware, static::$mapping[$path])) {
|
||||
static::$mapping[$path][] = $middleware;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +44,28 @@ class Middleware
|
||||
*/
|
||||
public static function get(string $className, string $method): array
|
||||
{
|
||||
return static::$manager[$className . '::' . $method] ?? [];
|
||||
return static::$mapping[$className . '::' . $method] ?? [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @param Validator $validators
|
||||
*/
|
||||
public static function setValidator(string $class, string $method, Validator $validators): void
|
||||
{
|
||||
self::$validators[$class . '::' . $method] = $validators;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return Validator|null
|
||||
*/
|
||||
public static function getValidator(string $class, string $method): ?Validator
|
||||
{
|
||||
return static::$validators[$class . '::' . $method] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class NotFoundController extends Controller
|
||||
public function fail(): ResponseInterface
|
||||
{
|
||||
if ($this->request->getMethod() == 'OPTIONS') {
|
||||
return $this->response->withStatus(200, "");
|
||||
return $this->response->json([], 200);
|
||||
} else {
|
||||
return $this->response->withStatus(404, "not found page.");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Base;
|
||||
|
||||
use Kiri\Router\Session;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* Session 中间件
|
||||
* 自动启动和保存 Session
|
||||
*/
|
||||
class SessionMiddleware implements MiddlewareInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
* @param RequestHandlerInterface $handler
|
||||
* @return ResponseInterface
|
||||
* @throws
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
// 启动 Session
|
||||
Session::start($request);
|
||||
|
||||
// 处理请求
|
||||
$response = $handler->handle($request);
|
||||
|
||||
// 保存 Session
|
||||
Session::save();
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,671 @@
|
||||
<?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 语句(包括 @json)
|
||||
$content = $this->compileEchos($content);
|
||||
|
||||
// 编译指令
|
||||
$content = $this->compileDirectives($content);
|
||||
|
||||
// 编译条件判断语法糖
|
||||
$content = $this->compileConditionalDirectives($content);
|
||||
|
||||
// 编译布局和继承(如果未跳过)
|
||||
if (!$skipLayouts) {
|
||||
$content = $this->compileLayouts($content);
|
||||
}
|
||||
|
||||
// 编译包含和组件
|
||||
$content = $this->compileIncludes($content);
|
||||
|
||||
// 编译 @each 指令
|
||||
$content = $this->compileEach($content);
|
||||
|
||||
// 编译栈和推送
|
||||
$content = $this->compileStacks($content);
|
||||
|
||||
// 编译表单辅助
|
||||
$content = $this->compileFormHelpers($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; ?>
|
||||
* @json($var) => JSON 编码输出
|
||||
*
|
||||
* @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);
|
||||
|
||||
// 编译 @json 指令
|
||||
$content = preg_replace_callback('/@json\s*\((.+?)\)/', function ($matches) {
|
||||
$expression = trim($matches[1]);
|
||||
return "<?php echo json_encode({$expression}, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>";
|
||||
}, $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);
|
||||
|
||||
// 处理 @lang 指令(语言翻译)
|
||||
$content = preg_replace_callback('/@lang\s*\([\'"](.+?)[\'"]\s*(?:,\s*\[(.+?)\])?\)/', function ($matches) {
|
||||
$key = $matches[1];
|
||||
$replace = $matches[2] ?? '[]';
|
||||
return "<?php echo function_exists('__') ? __('{$key}', {$replace}) : '{$key}'; ?>";
|
||||
}, $content);
|
||||
|
||||
// 处理 @class 指令(条件类名)
|
||||
$content = preg_replace_callback('/@class\s*\((.+?)\)/', function ($matches) {
|
||||
$conditions = trim($matches[1]);
|
||||
return "<?php echo is_array({$conditions}) ? implode(' ', array_keys(array_filter({$conditions}))) : {$conditions}; ?>";
|
||||
}, $content);
|
||||
|
||||
// 处理 @style 指令(条件样式)
|
||||
$content = preg_replace_callback('/@style\s*\((.+?)\)/', function ($matches) {
|
||||
$styles = trim($matches[1]);
|
||||
return "<?php if (is_array({$styles})): echo 'style=\"' . implode('; ', array_map(function(\$k, \$v) { return \$k . ':' . \$v; }, array_keys({$styles}), {$styles})) . '\"'; else: echo 'style=\"' . {$styles} . '\"'; endif; ?>";
|
||||
}, $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 为空
|
||||
return preg_replace('/@yield\s*\([\'"](.+?)[\'"]\)/', '', $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 中使用)
|
||||
return preg_replace('/@parent/', '', $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) {
|
||||
\Kiri::getLogger()->json_log($e);
|
||||
|
||||
$dataCode = '[]';
|
||||
}
|
||||
} else {
|
||||
$dataCode = '[]';
|
||||
}
|
||||
|
||||
// 使用静态方法调用,因为我们需要在运行时获取 BladeFactory 实例
|
||||
return "<?php echo \Kiri\Router\Blade\BladeHelper::include('{$view}', {$dataCode}); ?>";
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译 @each 指令
|
||||
* @each('view', $items, 'item', 'empty')
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileEach(string $content): string
|
||||
{
|
||||
return preg_replace_callback('/@each\s*\([\'"](.+?)[\'"]\s*,\s*(.+?)\s*(?:,\s*[\'"](.+?)[\'"]\s*(?:,\s*[\'"](.+?)[\'"])?)?\)/', function ($matches) {
|
||||
$view = $matches[1];
|
||||
$items = trim($matches[2]);
|
||||
$itemVar = $matches[3] ?? "'item'";
|
||||
$emptyView = $matches[4] ?? null;
|
||||
|
||||
$itemVar = trim($itemVar, "'\"");
|
||||
|
||||
if ($emptyView) {
|
||||
return "<?php if (count({$items}) > 0): foreach ({$items} as \${$itemVar}): echo \Kiri\Router\Blade\BladeHelper::include('{$view}', ['{$itemVar}' => \${$itemVar}]); endforeach; else: echo \Kiri\Router\Blade\BladeHelper::include('{$emptyView}', []); endif; ?>";
|
||||
}
|
||||
|
||||
return "<?php foreach ({$items} as \${$itemVar}): echo \Kiri\Router\Blade\BladeHelper::include('{$view}', ['{$itemVar}' => \${$itemVar}]); endforeach; ?>";
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译条件判断语法糖
|
||||
* @isset, @empty, @auth, @guest, @hasSection, @unless 等
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileConditionalDirectives(string $content): string
|
||||
{
|
||||
// @isset($var) ... @endisset
|
||||
$content = preg_replace_callback('/@isset\s*\((.+?)\)\s*(.*?)\s*@endisset/s', function ($matches) {
|
||||
$var = trim($matches[1]);
|
||||
$body = $matches[2];
|
||||
return "<?php if (isset({$var})): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @empty($var) ... @endempty
|
||||
$content = preg_replace_callback('/@empty\s*\((.+?)\)\s*(.*?)\s*@endempty/s', function ($matches) {
|
||||
$var = trim($matches[1]);
|
||||
$body = $matches[2];
|
||||
return "<?php if (empty({$var})): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @auth ... @endauth
|
||||
$content = preg_replace_callback('/@auth\s*(?:\((.+?)\))?\s*(.*?)\s*@endauth/s', function ($matches) {
|
||||
$guard = $matches[1] ?? '';
|
||||
$body = $matches[2];
|
||||
if ($guard) {
|
||||
return "<?php if (auth('{$guard}')->check()): ?>{$body}<?php endif; ?>";
|
||||
}
|
||||
return "<?php if (function_exists('auth') && auth()->check()): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @guest ... @endguest
|
||||
$content = preg_replace_callback('/@guest\s*(?:\((.+?)\))?\s*(.*?)\s*@endguest/s', function ($matches) {
|
||||
$guard = $matches[1] ?? '';
|
||||
$body = $matches[2];
|
||||
if ($guard) {
|
||||
return "<?php if (!auth('{$guard}')->check()): ?>{$body}<?php endif; ?>";
|
||||
}
|
||||
return "<?php if (!function_exists('auth') || !auth()->check()): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @hasSection('name') ... @endhasSection
|
||||
$content = preg_replace_callback('/@hasSection\s*\([\'"](.+?)[\'"]\)\s*(.*?)\s*@endhasSection/s', function ($matches) {
|
||||
$name = $matches[1];
|
||||
$body = $matches[2];
|
||||
return "<?php if (isset(\$__sections['{$name}'])): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @sectionMissing('name') ... @endsectionMissing
|
||||
$content = preg_replace_callback('/@sectionMissing\s*\([\'"](.+?)[\'"]\)\s*(.*?)\s*@endsectionMissing/s', function ($matches) {
|
||||
$name = $matches[1];
|
||||
$body = $matches[2];
|
||||
return "<?php if (!isset(\$__sections['{$name}'])): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @unless($condition) ... @endunless
|
||||
$content = preg_replace_callback('/@unless\s*\((.+?)\)\s*(.*?)\s*@endunless/s', function ($matches) {
|
||||
$condition = trim($matches[1]);
|
||||
$body = $matches[2];
|
||||
return "<?php if (!({$condition})): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @can('permission') ... @endcan
|
||||
$content = preg_replace_callback('/@can\s*\([\'"](.+?)[\'"]\s*(?:,\s*(.+?))?\)\s*(.*?)\s*@endcan/s', function ($matches) {
|
||||
$permission = $matches[1];
|
||||
$model = $matches[2] ?? '';
|
||||
$body = $matches[3];
|
||||
if ($model) {
|
||||
return "<?php if (function_exists('can') && can('{$permission}', {$model})): ?>{$body}<?php endif; ?>";
|
||||
}
|
||||
return "<?php if (function_exists('can') && can('{$permission}')): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @cannot('permission') ... @endcannot
|
||||
$content = preg_replace_callback('/@cannot\s*\([\'"](.+?)[\'"]\s*(?:,\s*(.+?))?\)\s*(.*?)\s*@endcannot/s', function ($matches) {
|
||||
$permission = $matches[1];
|
||||
$model = $matches[2] ?? '';
|
||||
$body = $matches[3];
|
||||
if ($model) {
|
||||
return "<?php if (!function_exists('can') || !can('{$permission}', {$model})): ?>{$body}<?php endif; ?>";
|
||||
}
|
||||
return "<?php if (!function_exists('can') || !can('{$permission}')): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @forelse($items as $item) ... @empty ... @endforelse
|
||||
$content = preg_replace_callback('/@forelse\s*\((.+?)\)\s*(.*?)\s*@empty\s*(.*?)\s*@endforelse/s', function ($matches) {
|
||||
$loop = trim($matches[1]);
|
||||
$body = $matches[2];
|
||||
$empty = $matches[3];
|
||||
return "<?php if (count({$loop}) > 0): ?><?php foreach ({$loop}): ?>{$body}<?php endforeach; ?><?php else: ?>{$empty}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @once ... @endonce (只执行一次)
|
||||
$content = preg_replace_callback('/@once\s*(.*?)\s*@endonce/s', function ($matches) {
|
||||
static $onceCounter = 0;
|
||||
$onceCounter++;
|
||||
$hash = md5($matches[0] . $onceCounter);
|
||||
$body = $matches[1];
|
||||
return "<?php if (!isset(\$__once_{$hash})): \$__once_{$hash} = true; ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
// @error('field') ... @enderror
|
||||
$content = preg_replace_callback('/@error\s*\([\'"](.+?)[\'"]\)\s*(.*?)\s*@enderror/s', function ($matches) {
|
||||
$field = $matches[1];
|
||||
$body = $matches[2];
|
||||
return "<?php if (function_exists('errors') && errors()->has('{$field}')): ?>{$body}<?php endif; ?>";
|
||||
}, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译栈和推送 @push, @stack, @prepend
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileStacks(string $content): string
|
||||
{
|
||||
// @push('name') ... @endpush
|
||||
$content = preg_replace_callback('/@push\s*\([\'"](.+?)[\'"]\)\s*(.*?)\s*@endpush/s', function ($matches) {
|
||||
$name = $matches[1];
|
||||
$body = $matches[2];
|
||||
return "<?php \$__stacks['{$name}'][] = function() { ?>{$body}<?php }; ?>";
|
||||
}, $content);
|
||||
|
||||
// @prepend('name') ... @endprepend
|
||||
$content = preg_replace_callback('/@prepend\s*\([\'"](.+?)[\'"]\)\s*(.*?)\s*@endprepend/s', function ($matches) {
|
||||
$name = $matches[1];
|
||||
$body = $matches[2];
|
||||
return "<?php array_unshift(\$__stacks['{$name}'] ?? [], function() { ?>{$body}<?php }); ?>";
|
||||
}, $content);
|
||||
|
||||
// @stack('name')
|
||||
$content = preg_replace_callback('/@stack\s*\([\'"](.+?)[\'"]\)/', function ($matches) {
|
||||
$name = $matches[1];
|
||||
return "<?php if (isset(\$__stacks['{$name}'])): foreach (\$__stacks['{$name}'] as \$__stack): call_user_func(\$__stack); endforeach; endif; ?>";
|
||||
}, $content);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译表单辅助函数
|
||||
* @csrf, @method, @old, @checked, @selected, @disabled
|
||||
*
|
||||
* @param string $content
|
||||
* @return string
|
||||
*/
|
||||
protected function compileFormHelpers(string $content): string
|
||||
{
|
||||
// @csrf - CSRF token
|
||||
$content = preg_replace('/@csrf/', '<?php echo function_exists("csrf_token") ? csrf_token() : ""; ?>', $content);
|
||||
|
||||
// @method('PUT') - HTTP method spoofing
|
||||
$content = preg_replace_callback('/@method\s*\([\'"](.+?)[\'"]\)/', function ($matches) {
|
||||
$method = strtoupper($matches[1]);
|
||||
return "<input type=\"hidden\" name=\"_method\" value=\"{$method}\">";
|
||||
}, $content);
|
||||
|
||||
// @old('field', 'default')
|
||||
$content = preg_replace_callback('/@old\s*\([\'"](.+?)[\'"]\s*(?:,\s*(.+?))?\)/', function ($matches) {
|
||||
$field = $matches[1];
|
||||
$default = $matches[2] ?? "''";
|
||||
return "<?php echo function_exists('old') ? old('{$field}', {$default}) : {$default}; ?>";
|
||||
}, $content);
|
||||
|
||||
// @checked($condition)
|
||||
$content = preg_replace_callback('/@checked\s*\((.+?)\)/', function ($matches) {
|
||||
$condition = trim($matches[1]);
|
||||
return "<?php echo ({$condition}) ? 'checked' : ''; ?>";
|
||||
}, $content);
|
||||
|
||||
// @selected($condition)
|
||||
$content = preg_replace_callback('/@selected\s*\((.+?)\)/', function ($matches) {
|
||||
$condition = trim($matches[1]);
|
||||
return "<?php echo ({$condition}) ? 'selected' : ''; ?>";
|
||||
}, $content);
|
||||
|
||||
// @disabled($condition)
|
||||
$content = preg_replace_callback('/@disabled\s*\((.+?)\)/', function ($matches) {
|
||||
$condition = trim($matches[1]);
|
||||
return "<?php echo ({$condition}) ? 'disabled' : ''; ?>";
|
||||
}, $content);
|
||||
|
||||
// @readonly($condition)
|
||||
$content = preg_replace_callback('/@readonly\s*\((.+?)\)/', function ($matches) {
|
||||
$condition = trim($matches[1]);
|
||||
return "<?php echo ({$condition}) ? 'readonly' : ''; ?>";
|
||||
}, $content);
|
||||
|
||||
// @required($condition)
|
||||
$content = preg_replace_callback('/@required\s*\((.+?)\)/', function ($matches) {
|
||||
$condition = trim($matches[1]);
|
||||
return "<?php echo ({$condition}) ? 'required' : ''; ?>";
|
||||
}, $content);
|
||||
|
||||
return $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,93 @@
|
||||
<?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);
|
||||
|
||||
// 初始化栈和 section 数组
|
||||
$__stacks = [];
|
||||
$__sections = [];
|
||||
|
||||
// 开始输出缓冲
|
||||
ob_start();
|
||||
|
||||
try {
|
||||
// 包含编译后的 PHP 文件
|
||||
require $compiledPath;
|
||||
} catch (\Throwable $e) {
|
||||
ob_end_clean();
|
||||
|
||||
\Kiri::getLogger()->json_log($throwable);
|
||||
|
||||
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,252 @@
|
||||
# Kiri Blade 模板引擎
|
||||
|
||||
这是一个类似 Laravel Blade 的模板引擎实现,用于 kiri-router 项目。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 基础功能
|
||||
- ✅ 变量输出:`{{ $variable }}` 和 `{!! $variable !!}`
|
||||
- ✅ JSON 输出:`@json($data)`
|
||||
- ✅ 控制结构:`@if`, `@elseif`, `@else`, `@endif`, `@unless`
|
||||
- ✅ 循环:`@foreach`, `@for`, `@while`, `@forelse` 及其结束指令
|
||||
- ✅ Switch:`@switch`, `@case`, `@default`, `@endswitch`
|
||||
- ✅ 布局继承:`@extends`, `@section`, `@yield`, `@endsection`, `@parent`
|
||||
- ✅ 包含视图:`@include`, `@each`
|
||||
- ✅ 注释:`{{-- comment --}}`
|
||||
- ✅ 原始 PHP:`@php ... @endphp`
|
||||
- ✅ 编译缓存:自动缓存编译后的模板以提高性能
|
||||
|
||||
### 条件判断语法糖
|
||||
- ✅ `@isset` / `@endisset` - 检查变量是否存在
|
||||
- ✅ `@empty` / `@endempty` - 检查变量是否为空
|
||||
- ✅ `@auth` / `@endauth` - 检查用户是否已认证
|
||||
- ✅ `@guest` / `@endguest` - 检查用户是否未认证
|
||||
- ✅ `@hasSection` / `@endhasSection` - 检查 section 是否存在
|
||||
- ✅ `@sectionMissing` / `@endsectionMissing` - 检查 section 是否缺失
|
||||
- ✅ `@can` / `@endcan` - 权限检查
|
||||
- ✅ `@cannot` / `@endcannot` - 反向权限检查
|
||||
|
||||
### 表单辅助
|
||||
- ✅ `@csrf` - CSRF token
|
||||
- ✅ `@method` - HTTP 方法伪装
|
||||
- ✅ `@old` - 旧输入值
|
||||
- ✅ `@checked` - 复选框选中状态
|
||||
- ✅ `@selected` - 下拉框选中状态
|
||||
- ✅ `@disabled` - 禁用状态
|
||||
- ✅ `@readonly` - 只读状态
|
||||
- ✅ `@required` - 必填状态
|
||||
- ✅ `@error` / `@enderror` - 错误信息显示
|
||||
|
||||
### 栈和推送
|
||||
- ✅ `@push` / `@endpush` - 推送到栈
|
||||
- ✅ `@prepend` / `@endprepend` - 前置推送到栈
|
||||
- ✅ `@stack` - 输出栈内容
|
||||
|
||||
### 其他语法糖
|
||||
- ✅ `@once` / `@endonce` - 只执行一次
|
||||
- ✅ `@lang` - 语言翻译
|
||||
- ✅ `@class` - 条件类名
|
||||
- ✅ `@style` - 条件样式
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本用法
|
||||
|
||||
```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. 建议在生产环境中定期清理缓存目录
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
# Blade 语法糖参考手册
|
||||
|
||||
本文档列出了所有可用的 Blade 语法糖和指令。
|
||||
|
||||
## 变量输出
|
||||
|
||||
### 转义输出
|
||||
```blade
|
||||
{{ $variable }}
|
||||
{{ $user->name }}
|
||||
{{ $array['key'] }}
|
||||
```
|
||||
自动转义 HTML 特殊字符,防止 XSS 攻击。
|
||||
|
||||
### 原始输出
|
||||
```blade
|
||||
{!! $htmlContent !!}
|
||||
```
|
||||
不转义,直接输出原始 HTML(谨慎使用)。
|
||||
|
||||
### JSON 输出
|
||||
```blade
|
||||
@json($data)
|
||||
```
|
||||
将数据编码为 JSON 字符串输出。
|
||||
|
||||
## 控制结构
|
||||
|
||||
### 条件判断
|
||||
|
||||
#### @if / @elseif / @else / @endif
|
||||
```blade
|
||||
@if($user->isAdmin())
|
||||
<p>管理员</p>
|
||||
@elseif($user->isModerator())
|
||||
<p>版主</p>
|
||||
@else
|
||||
<p>普通用户</p>
|
||||
@endif
|
||||
```
|
||||
|
||||
#### @unless / @endunless
|
||||
```blade
|
||||
@unless($user->isBanned())
|
||||
<p>用户正常</p>
|
||||
@endunless
|
||||
```
|
||||
等同于 `@if(!condition)`
|
||||
|
||||
#### @isset / @endisset
|
||||
```blade
|
||||
@isset($variable)
|
||||
<p>{{ $variable }}</p>
|
||||
@endisset
|
||||
```
|
||||
|
||||
#### @empty / @endempty
|
||||
```blade
|
||||
@empty($items)
|
||||
<p>列表为空</p>
|
||||
@endempty
|
||||
```
|
||||
|
||||
### 循环
|
||||
|
||||
#### @foreach / @endforeach
|
||||
```blade
|
||||
@foreach($users as $user)
|
||||
<p>{{ $user->name }}</p>
|
||||
@endforeach
|
||||
```
|
||||
|
||||
#### @for / @endfor
|
||||
```blade
|
||||
@for($i = 0; $i < 10; $i++)
|
||||
<p>Item {{ $i }}</p>
|
||||
@endfor
|
||||
```
|
||||
|
||||
#### @while / @endwhile
|
||||
```blade
|
||||
@while($condition)
|
||||
<p>循环内容</p>
|
||||
@endwhile
|
||||
```
|
||||
|
||||
#### @forelse / @empty / @endforelse
|
||||
```blade
|
||||
@forelse($items as $item)
|
||||
<p>{{ $item->name }}</p>
|
||||
@empty
|
||||
<p>没有项目</p>
|
||||
@endforelse
|
||||
```
|
||||
|
||||
#### @break 和 @continue
|
||||
```blade
|
||||
@foreach($items as $item)
|
||||
@if($item->hidden)
|
||||
@continue
|
||||
@endif
|
||||
<p>{{ $item->name }}</p>
|
||||
@if($item->id > 100)
|
||||
@break
|
||||
@endif
|
||||
@endforeach
|
||||
```
|
||||
|
||||
### Switch 语句
|
||||
|
||||
```blade
|
||||
@switch($status)
|
||||
@case(1)
|
||||
<p>待处理</p>
|
||||
@break
|
||||
@case(2)
|
||||
<p>处理中</p>
|
||||
@break
|
||||
@default
|
||||
<p>未知状态</p>
|
||||
@endswitch
|
||||
```
|
||||
|
||||
## 布局和继承
|
||||
|
||||
### @extends
|
||||
```blade
|
||||
@extends('layouts.app')
|
||||
```
|
||||
|
||||
### @section / @endsection
|
||||
```blade
|
||||
@section('content')
|
||||
<h1>页面内容</h1>
|
||||
@endsection
|
||||
```
|
||||
|
||||
### @yield
|
||||
```blade
|
||||
@yield('content')
|
||||
@yield('title', '默认标题')
|
||||
```
|
||||
|
||||
### @parent
|
||||
在子视图的 section 中使用,输出父布局中对应 section 的内容。
|
||||
|
||||
### @hasSection / @endhasSection
|
||||
```blade
|
||||
@hasSection('sidebar')
|
||||
@yield('sidebar')
|
||||
@endhasSection
|
||||
```
|
||||
|
||||
### @sectionMissing / @endsectionMissing
|
||||
```blade
|
||||
@sectionMissing('sidebar')
|
||||
<p>没有侧边栏</p>
|
||||
@endsectionMissing
|
||||
```
|
||||
|
||||
## 包含和组件
|
||||
|
||||
### @include
|
||||
```blade
|
||||
@include('components.header')
|
||||
@include('components.footer', ['year' => 2024])
|
||||
```
|
||||
|
||||
### @each
|
||||
```blade
|
||||
@each('components.item', $items, 'item')
|
||||
@each('components.item', $items, 'item', 'components.empty')
|
||||
```
|
||||
|
||||
## 栈和推送
|
||||
|
||||
### @push / @endpush
|
||||
```blade
|
||||
@push('scripts')
|
||||
<script src="/js/custom.js"></script>
|
||||
@endpush
|
||||
```
|
||||
|
||||
### @prepend / @endprepend
|
||||
```blade
|
||||
@prepend('scripts')
|
||||
<script src="/js/jquery.js"></script>
|
||||
@endprepend
|
||||
```
|
||||
|
||||
### @stack
|
||||
```blade
|
||||
@stack('scripts')
|
||||
```
|
||||
|
||||
## 表单辅助
|
||||
|
||||
### @csrf
|
||||
```blade
|
||||
<form method="POST">
|
||||
@csrf
|
||||
...
|
||||
</form>
|
||||
```
|
||||
|
||||
### @method
|
||||
```blade
|
||||
<form method="POST">
|
||||
@method('PUT')
|
||||
...
|
||||
</form>
|
||||
```
|
||||
|
||||
### @old
|
||||
```blade
|
||||
<input type="text" name="name" value="@old('name', '默认值')">
|
||||
```
|
||||
|
||||
### @checked
|
||||
```blade
|
||||
<input type="checkbox" name="active" @checked($user->isActive())>
|
||||
```
|
||||
|
||||
### @selected
|
||||
```blade
|
||||
<select name="status">
|
||||
<option value="1" @selected($status == 1)>启用</option>
|
||||
<option value="0" @selected($status == 0)>禁用</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
### @disabled
|
||||
```blade
|
||||
<input type="text" @disabled($readonly)>
|
||||
```
|
||||
|
||||
### @readonly
|
||||
```blade
|
||||
<input type="text" @readonly($readonly)>
|
||||
```
|
||||
|
||||
### @required
|
||||
```blade
|
||||
<input type="text" @required($isRequired)>
|
||||
```
|
||||
|
||||
## 权限和认证
|
||||
|
||||
### @auth / @endauth
|
||||
```blade
|
||||
@auth
|
||||
<p>欢迎,{{ auth()->user()->name }}</p>
|
||||
@endauth
|
||||
|
||||
@auth('admin')
|
||||
<p>管理员面板</p>
|
||||
@endauth
|
||||
```
|
||||
|
||||
### @guest / @endguest
|
||||
```blade
|
||||
@guest
|
||||
<a href="/login">登录</a>
|
||||
@endguest
|
||||
```
|
||||
|
||||
### @can / @endcan
|
||||
```blade
|
||||
@can('edit', $post)
|
||||
<a href="/posts/{{ $post->id }}/edit">编辑</a>
|
||||
@endcan
|
||||
```
|
||||
|
||||
### @cannot / @endcannot
|
||||
```blade
|
||||
@cannot('edit', $post)
|
||||
<p>您没有编辑权限</p>
|
||||
@endcannot
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### @error / @enderror
|
||||
```blade
|
||||
@error('email')
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
```
|
||||
|
||||
## 其他语法糖
|
||||
|
||||
### @once / @endonce
|
||||
```blade
|
||||
@once
|
||||
<script src="/js/unique.js"></script>
|
||||
@endonce
|
||||
```
|
||||
确保内容只输出一次。
|
||||
|
||||
### @lang
|
||||
```blade
|
||||
@lang('messages.welcome')
|
||||
@lang('messages.greeting', ['name' => $user->name])
|
||||
```
|
||||
|
||||
### @class
|
||||
```blade
|
||||
<div @class(['active' => $isActive, 'disabled' => $isDisabled])>
|
||||
```
|
||||
|
||||
### @style
|
||||
```blade
|
||||
<div @style(['color' => 'red', 'font-size' => '14px'])>
|
||||
```
|
||||
|
||||
### @php / @endphp
|
||||
```blade
|
||||
@php
|
||||
$count = count($items);
|
||||
$total = array_sum($prices);
|
||||
@endphp
|
||||
```
|
||||
|
||||
### 注释
|
||||
```blade
|
||||
{{-- 这是注释,不会输出到 HTML --}}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 完整的页面模板
|
||||
```blade
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', '用户资料')
|
||||
|
||||
@push('styles')
|
||||
<link rel="stylesheet" href="/css/profile.css">
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
@auth
|
||||
<h1>欢迎,{{ auth()->user()->name }}</h1>
|
||||
|
||||
@if($user->isAdmin())
|
||||
<p class="badge">管理员</p>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="/profile">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<input type="text" name="name" value="@old('name', $user->name)">
|
||||
@error('name')
|
||||
<p class="error">{{ $message }}</p>
|
||||
@enderror
|
||||
|
||||
<button type="submit" @disabled($readonly)>保存</button>
|
||||
</form>
|
||||
|
||||
<h2>文章列表</h2>
|
||||
@forelse($posts as $post)
|
||||
<article>
|
||||
<h3>{{ $post->title }}</h3>
|
||||
<p>{{ $post->content }}</p>
|
||||
</article>
|
||||
@empty
|
||||
<p>暂无文章</p>
|
||||
@endforelse
|
||||
@else
|
||||
<p>请先登录</p>
|
||||
@endauth
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script src="/js/profile.js"></script>
|
||||
@endpush
|
||||
```
|
||||
|
||||
### 布局文件
|
||||
```blade
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>@yield('title', '默认标题')</title>
|
||||
@stack('styles')
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@include('components.header')
|
||||
</header>
|
||||
|
||||
<main>
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
@include('components.footer')
|
||||
</footer>
|
||||
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ use Kiri\Router\ContentType;
|
||||
use Kiri\Router\StreamResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
|
||||
class ConstrictResponse extends Message implements ResponseInterface
|
||||
{
|
||||
|
||||
@@ -30,6 +31,25 @@ class ConstrictResponse extends Message implements ResponseInterface
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param array $params
|
||||
* @param int $statusCode
|
||||
* @return static
|
||||
*/
|
||||
public function redirectTo(string $url, array $params = [], int $statusCode = 302): static
|
||||
{
|
||||
if (!empty($params)) {
|
||||
$url .= '?' . http_build_query($params);
|
||||
}
|
||||
|
||||
$this->withHeader('Location', $url);
|
||||
$this->withStatus($statusCode);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ContentType $contentType
|
||||
* @return $this
|
||||
|
||||
+644
-643
File diff suppressed because it is too large
Load Diff
@@ -5,22 +5,21 @@ namespace Kiri\Router;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kiri;
|
||||
use Kiri\Router\Format\ArrayFormat;
|
||||
use Kiri\Router\Format\MixedFormat;
|
||||
use Kiri\Router\Format\OtherFormat;
|
||||
use Kiri\Router\Format\ResponseFormat;
|
||||
use Kiri\Router\Format\VoidFormat;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
class ControllerInterpreter
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(public ContainerInterface $container)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param object $class
|
||||
* @param string|ReflectionMethod $method
|
||||
@@ -31,7 +30,7 @@ class ControllerInterpreter
|
||||
public function addRouteByString(object $class, string|ReflectionMethod $method, ?ReflectionClass $reflection = null): Handler
|
||||
{
|
||||
if (is_null($reflection)) {
|
||||
$reflection = $this->container->getReflectionClass($class::class);
|
||||
$reflection = Kiri::getDi()->getReflectionClass($class::class);
|
||||
}
|
||||
return $this->resolveMethod($class, $method, $reflection);
|
||||
}
|
||||
@@ -44,9 +43,13 @@ class ControllerInterpreter
|
||||
*/
|
||||
public function addRouteByClosure(Closure $method): Handler
|
||||
{
|
||||
$reflection = new \ReflectionFunction($method);
|
||||
$reflection = new ReflectionFunction($method);
|
||||
|
||||
return new Handler($method, $reflection);
|
||||
$parameters = Kiri::getDi()->getFunctionParams($method);
|
||||
|
||||
$returnType = $this->getReturnType($reflection);
|
||||
|
||||
return new Handler($method, $parameters, $returnType);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +63,7 @@ class ControllerInterpreter
|
||||
public function addRouteByObject(object $class, string|ReflectionMethod $method, ?ReflectionClass $reflection = null): Handler
|
||||
{
|
||||
if (is_null($reflection)) {
|
||||
$reflection = $this->container->getReflectionClass($class::class);
|
||||
$reflection = Kiri::getDi()->getReflectionClass($class::class);
|
||||
}
|
||||
return $this->resolveMethod($class, $method, $reflection);
|
||||
}
|
||||
@@ -79,7 +82,26 @@ class ControllerInterpreter
|
||||
$reflectionMethod = $reflectionClass->getMethod($reflectionMethod);
|
||||
}
|
||||
|
||||
return new Handler([$class, $reflectionMethod->getName()], $reflectionMethod);
|
||||
$parameters = Kiri::getDi()->getMethodParams($reflectionMethod);
|
||||
$returnType = $this->getReturnType($reflectionMethod);
|
||||
|
||||
return new Handler([$class::class, $reflectionMethod->getName()], $parameters, $returnType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ReflectionMethod|ReflectionFunction $reflectionMethod
|
||||
* @return string
|
||||
*/
|
||||
protected function getReturnType(ReflectionMethod|ReflectionFunction $reflectionMethod): string
|
||||
{
|
||||
return match ($reflectionMethod->getReturnType()?->getName()) {
|
||||
'array' => ArrayFormat::class,
|
||||
'mixed', 'object' => MixedFormat::class,
|
||||
'int', 'string', 'bool' => OtherFormat::class,
|
||||
'void' => VoidFormat::class,
|
||||
default => ResponseFormat::class
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
-13
@@ -10,21 +10,23 @@ class ArrayFormat implements IFormat
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var ResponseInterface
|
||||
*/
|
||||
#[Container(ResponseInterface::class)]
|
||||
public ResponseInterface $response;
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function __construct(public ResponseInterface $response)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $result
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function call($result): ResponseInterface
|
||||
{
|
||||
return $this->response->withBody(new Stream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
||||
}
|
||||
/**
|
||||
* @param $result
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function call($result): ResponseInterface
|
||||
{
|
||||
return $this->response->withBody(new Stream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -2,23 +2,23 @@
|
||||
|
||||
namespace Kiri\Router\Format;
|
||||
|
||||
use Kiri\Di\Inject\Container;
|
||||
use Kiri;
|
||||
use Kiri\Router\Constrict\Stream;
|
||||
use Kiri\Router\ContentType;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class MixedFormat implements IFormat
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var ResponseInterface
|
||||
*/
|
||||
#[Container(ResponseInterface::class)]
|
||||
public ResponseInterface $response;
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function __construct(public ResponseInterface $response)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param mixed $result
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
|
||||
+30
-29
@@ -2,42 +2,43 @@
|
||||
|
||||
namespace Kiri\Router\Format;
|
||||
|
||||
use Kiri\Di\Inject\Container;
|
||||
use Kiri;
|
||||
use Kiri\Router\Constrict\Stream;
|
||||
use Kiri\Router\ContentType;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class NoBody implements IFormat
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var ResponseInterface
|
||||
*/
|
||||
#[Container(ResponseInterface::class)]
|
||||
public ResponseInterface $response;
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function __construct(public ResponseInterface $response)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $result
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function call($result): ResponseInterface
|
||||
{
|
||||
// TODO: Implement call() method.
|
||||
if (request()->getMethod() === 'HEAD') {
|
||||
return $this->response->withBody(new Stream());
|
||||
}
|
||||
if ($result instanceof ResponseInterface) {
|
||||
return $result;
|
||||
}
|
||||
if (is_object($result)) {
|
||||
return $this->response->withBody(new Stream('[object]'));
|
||||
}
|
||||
if (is_array($result)) {
|
||||
return $this->response->withBody(new Stream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
||||
} else {
|
||||
return $this->response->withBody(new Stream((string)$result));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param $result
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function call($result): ResponseInterface
|
||||
{
|
||||
// TODO: Implement call() method.
|
||||
if (request()->getMethod() === 'HEAD') {
|
||||
return $this->response->withBody(new Stream());
|
||||
}
|
||||
if ($result instanceof ResponseInterface) {
|
||||
return $result;
|
||||
}
|
||||
if (is_object($result)) {
|
||||
return $this->response->withBody(new Stream('[object]'));
|
||||
}
|
||||
if (is_array($result)) {
|
||||
return $this->response->withBody(new Stream(json_encode($result, JSON_UNESCAPED_UNICODE)));
|
||||
} else {
|
||||
return $this->response->withBody(new Stream((string)$result));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,14 +10,11 @@ class OtherFormat implements IFormat
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var ResponseInterface
|
||||
*/
|
||||
#[Container(ResponseInterface::class)]
|
||||
public ResponseInterface $response;
|
||||
public function __construct(public ResponseInterface $response)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param mixed $result
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
|
||||
+16
-16
@@ -9,22 +9,22 @@ class VoidFormat implements IFormat
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var ResponseInterface
|
||||
*/
|
||||
#[Container(ResponseInterface::class)]
|
||||
public ResponseInterface $response;
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
public function __construct(public ResponseInterface $response)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @param $result
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function call($result): ResponseInterface
|
||||
{
|
||||
// TODO: Implement call() method.
|
||||
return $this->response;
|
||||
}
|
||||
/**
|
||||
* @param $result
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function call($result): ResponseInterface
|
||||
{
|
||||
// TODO: Implement call() method.
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
}
|
||||
+41
-48
@@ -4,21 +4,13 @@ declare(strict_types=1);
|
||||
namespace Kiri\Router;
|
||||
|
||||
use Closure;
|
||||
use Kiri\Router\Format\ArrayFormat;
|
||||
use Kiri;
|
||||
use Kiri\Router\Format\IFormat;
|
||||
use Kiri\Router\Format\MixedFormat;
|
||||
use Kiri\Router\Format\NoBody;
|
||||
use Kiri\Router\Format\OtherFormat;
|
||||
use Kiri\Router\Format\ResponseFormat;
|
||||
use Kiri\Router\Format\VoidFormat;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use ReflectionFunction;
|
||||
use ReflectionMethod;
|
||||
|
||||
class Handler implements RequestHandlerInterface
|
||||
{
|
||||
@@ -29,48 +21,29 @@ class Handler implements RequestHandlerInterface
|
||||
protected mixed $format;
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $methods = [];
|
||||
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
protected ContainerInterface $container;
|
||||
|
||||
protected array $parameters;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $middlewares = [];
|
||||
|
||||
/**
|
||||
* @param array|Closure $handler
|
||||
* @param ReflectionMethod|ReflectionFunction $parameter
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @param array $parameters
|
||||
* @param string|null $parameter
|
||||
*/
|
||||
public function __construct(public array|Closure $handler, public ReflectionMethod|ReflectionFunction $parameter)
|
||||
public function __construct(public array|Closure $handler, public array $parameters, ?string $parameter)
|
||||
{
|
||||
$this->container = \Kiri::getDi();
|
||||
if ($this->parameter->getReturnType() != null) {
|
||||
$this->format = $this->container->get($this->returnType($parameter));
|
||||
if ($parameter !== null) {
|
||||
$this->format = Kiri::getDi()->get($parameter);
|
||||
} else {
|
||||
$this->format = $this->container->get(MixedFormat::class);
|
||||
$this->format = Kiri::getDi()->get(MixedFormat::class);
|
||||
}
|
||||
$this->parameters = $this->container->getMethodParams($this->parameter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $reflectionType
|
||||
* @return string
|
||||
*/
|
||||
protected function returnType(ReflectionMethod $reflectionType): string
|
||||
{
|
||||
return match ($reflectionType->getReturnType()->getName()) {
|
||||
'array' => ArrayFormat::class,
|
||||
'mixed', 'object' => MixedFormat::class,
|
||||
'int', 'string', 'bool' => OtherFormat::class,
|
||||
'void' => VoidFormat::class,
|
||||
default => ResponseFormat::class
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -81,8 +54,8 @@ class Handler implements RequestHandlerInterface
|
||||
*/
|
||||
public function setRequestMethod(string $method): void
|
||||
{
|
||||
if ($method == 'HEAD') {
|
||||
$this->format = $this->container->get(NoBody::class);
|
||||
if ($method == 'HEAD' || $method == 'OPTIONS') {
|
||||
$this->format = Kiri::getDi()->get(NoBody::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +75,7 @@ class Handler implements RequestHandlerInterface
|
||||
*/
|
||||
public function implement(string $interface): bool
|
||||
{
|
||||
if (!$this->isClosure()) {
|
||||
if (!$this->handler instanceof Closure) {
|
||||
return $this->handler[0] instanceof $interface;
|
||||
}
|
||||
return false;
|
||||
@@ -114,10 +87,10 @@ class Handler implements RequestHandlerInterface
|
||||
*/
|
||||
public function getClass(): ?string
|
||||
{
|
||||
if ($this->isClosure()) {
|
||||
if ($this->handler instanceof Closure) {
|
||||
return null;
|
||||
}
|
||||
return $this->handler[0]::class;
|
||||
return $this->handler[0];
|
||||
}
|
||||
|
||||
|
||||
@@ -126,12 +99,30 @@ class Handler implements RequestHandlerInterface
|
||||
*/
|
||||
public function getMethod(): ?string
|
||||
{
|
||||
if ($this->isClosure()) {
|
||||
if ($this->handler instanceof Closure) {
|
||||
return null;
|
||||
}
|
||||
return $this->handler[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $middlewares
|
||||
* @return void
|
||||
*/
|
||||
public function setMiddlewares(array $middlewares): void
|
||||
{
|
||||
$this->middlewares = $middlewares;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMiddlewares(): array
|
||||
{
|
||||
return $this->middlewares;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ServerRequestInterface $request
|
||||
@@ -140,7 +131,9 @@ class Handler implements RequestHandlerInterface
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$data = call_user_func($this->handler, ...$this->parameters);
|
||||
$controller = Kiri::getDi()->get($this->handler[0]);
|
||||
|
||||
$data = call_user_func([$controller, $this->handler[1]], ...$this->parameters);
|
||||
|
||||
/** 根据返回类型 */
|
||||
return $this->format->call($data);
|
||||
|
||||
@@ -7,7 +7,6 @@ use Kiri\Router\Base\AbstractHandler;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use ReflectionException;
|
||||
|
||||
class HttpRequestHandler extends AbstractHandler implements RequestHandlerInterface
|
||||
{
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
use PhpParser\Error;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\NodeDumper;
|
||||
use PhpParser\NodeFactory;
|
||||
use PhpParser\NodeTraverser;
|
||||
use PhpParser\NodeVisitorAbstract;
|
||||
use PhpParser\ParserFactory;
|
||||
use PhpParser\PrettyPrinter\Standard;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class MiddlewareProxyGenerator {
|
||||
private static string $cacheFile = __DIR__ . '/middleware_proxy.php';
|
||||
private static NodeFactory $factory;
|
||||
|
||||
public static function generateProxy(array $middlewares): string {
|
||||
self::$factory = new NodeFactory();
|
||||
|
||||
// 创建类结构(v5.5.0兼容写法)
|
||||
$class = new Node\Stmt\Class_(
|
||||
'MiddlewareProxy',
|
||||
[
|
||||
'implements' => [new Node\Name\FullyQualified(\Psr\Http\Server\MiddlewareInterface::class)],
|
||||
'stmts' => []
|
||||
]
|
||||
);
|
||||
|
||||
// 生成中间件调用链
|
||||
$method = self::createMethodNode($middlewares);
|
||||
$class->stmts[] = $method;
|
||||
|
||||
// 生成代码
|
||||
$printer = new Standard();
|
||||
return $printer->prettyPrintFile([$class]);
|
||||
}
|
||||
|
||||
private static function createMethodNode(array $middlewares): Node\Stmt\Function_ {
|
||||
$method = new Node\Stmt\Function_(
|
||||
'process',
|
||||
[
|
||||
'params' => [
|
||||
new Node\Param(
|
||||
new Node\Expr\Variable('request'),
|
||||
null,
|
||||
new Node\UnionType([new Node\Expr\Variable(\Psr\Http\Message\ServerRequestInterface::class)])
|
||||
),
|
||||
new Node\Param(
|
||||
new Node\Expr\Variable('handler'),
|
||||
null,
|
||||
new Node\UnionType([new Node\Expr\Variable(\Psr\Http\Server\RequestHandlerInterface::class)])
|
||||
)
|
||||
],
|
||||
'returnType' => new Node\Name(ResponseInterface::class),
|
||||
'stmts' => []
|
||||
]
|
||||
);
|
||||
|
||||
// 生成中间件调用链(v5.5.0闭包写法)
|
||||
$chain = [];
|
||||
$count = count($middlewares);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$className = $middlewares[$i]::class;
|
||||
$chain[] = self::createMiddlewareCall($i, $className, $count);
|
||||
}
|
||||
|
||||
$method->stmts = array_reverse($chain);
|
||||
return $method;
|
||||
}
|
||||
|
||||
private static function createMiddlewareCall(int $index, string $className, int $count): Node {
|
||||
$mwVar = new Node\Expr\Variable("mw{$index}");
|
||||
$nextHandler = new Node\Expr\Variable("next");
|
||||
|
||||
// 创建中间件实例(v5.5.0兼容)
|
||||
$instanciate = new Node\Expr\New_($className);
|
||||
|
||||
// 创建闭包表达式(v5.5.0语法)
|
||||
$closure = new Node\Expr\Closure([
|
||||
'static' => true,
|
||||
'params' => [new Node\Param($nextHandler)],
|
||||
'stmts' => [
|
||||
new Node\Stmt\Expression(
|
||||
new Node\Expr\MethodCall(
|
||||
$instanciate,
|
||||
'process',
|
||||
[new Node\Arg($nextHandler), new Node\Arg($mwVar)]
|
||||
)
|
||||
)
|
||||
]
|
||||
]);
|
||||
|
||||
return new Node\Stmt\Expression($closure);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成代理类代码
|
||||
$middlewares = [AuthMiddleware::class, LogMiddleware::class];
|
||||
$proxyCode = MiddlewareProxyGenerator::generateProxy($middlewares);
|
||||
|
||||
// 写入代理类文件
|
||||
file_put_contents(MiddlewareProxyGenerator::$cacheFile, $proxyCode);
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router;
|
||||
|
||||
use Kiri\Abstracts\CoordinatorManager;
|
||||
use Kiri\Coordinator;
|
||||
use Kiri\Di\Inject\Container;
|
||||
use Kiri\Di\Context;
|
||||
use Kiri\Di\Interface\ResponseEmitterInterface;
|
||||
use Kiri\Router\Base\ExceptionHandlerDispatcher;
|
||||
use Kiri\Router\Constrict\ConstrictRequest as CQ;
|
||||
use Kiri\Router\Constrict\ConstrictResponse;
|
||||
use Kiri\Router\Interface\ExceptionHandlerInterface;
|
||||
use Kiri\Router\Interface\OnRequestInterface;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\Http\Response;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* OnRequest event
|
||||
*/
|
||||
class OnRequest implements OnRequestInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var RouterCollector
|
||||
*/
|
||||
public RouterCollector $router;
|
||||
|
||||
|
||||
/**
|
||||
* @var ExceptionHandlerInterface
|
||||
*/
|
||||
public ExceptionHandlerInterface $exception;
|
||||
|
||||
|
||||
/**
|
||||
* @var ResponseEmitterInterface
|
||||
*/
|
||||
public ResponseEmitterInterface $responseEmitter;
|
||||
|
||||
|
||||
/**
|
||||
* @var ConstrictResponse
|
||||
*/
|
||||
#[Container(ConstrictResponse::class)]
|
||||
public ConstrictResponse $constrictResponse;
|
||||
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @param DataGrip $dataGrip
|
||||
*/
|
||||
public function __construct(public ResponseInterface $response, DataGrip $dataGrip)
|
||||
{
|
||||
$this->responseEmitter = $this->response->emmit;
|
||||
$exception = \config('servers.request.exception');
|
||||
if (!in_array(ExceptionHandlerInterface::class, class_implements($exception))) {
|
||||
$exception = ExceptionHandlerDispatcher::class;
|
||||
}
|
||||
$this->exception = \Kiri::getDi()->get($exception);
|
||||
$this->router = $dataGrip->get(ROUTER_TYPE_HTTP);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @throws
|
||||
*/
|
||||
public function onRequest(Request $request, Response $response): void
|
||||
{
|
||||
try {
|
||||
$this->setResponseHeaders($response, $this->response->headers);
|
||||
|
||||
/** @var CQ $PsrRequest */
|
||||
Context::set(ResponseInterface::class, new ConstrictResponse($this->response->contentType));
|
||||
$PsrRequest = Context::set(RequestInterface::class, CQ::builder($request));
|
||||
|
||||
CoordinatorManager::utility(Coordinator::WORKER_START)->yield();
|
||||
|
||||
$PsrResponse = $this->router->query($request->server['path_info'], $request->getMethod())->run($PsrRequest);
|
||||
} catch (Throwable $throwable) {
|
||||
\Kiri::getLogger()->json_log($throwable);
|
||||
$PsrResponse = $this->exception->emit($throwable, $this->constrictResponse);
|
||||
} finally {
|
||||
$this->responseEmitter->response($PsrResponse, $response, $PsrRequest);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Response $response
|
||||
* @param array $headers
|
||||
* @return void
|
||||
*/
|
||||
protected function setResponseHeaders(Response $response, array $headers): void
|
||||
{
|
||||
foreach ($headers as $key => $header) {
|
||||
$response->header($key, $header);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -33,8 +33,8 @@ class Request implements ServerRequestInterface
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middlewares = \config('request.middlewares', []);
|
||||
$this->exception = \config('request.exception', ExceptionHandlerDispatcher::class);
|
||||
$this->middlewares = \config('servers.request.middlewares', []);
|
||||
$this->exception = \config('servers.request.exception', ExceptionHandlerDispatcher::class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+419
-381
@@ -15,426 +15,464 @@ class Response implements ResponseInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var ContentType
|
||||
*/
|
||||
public ContentType $contentType = ContentType::JSON;
|
||||
/**
|
||||
* @var ContentType
|
||||
*/
|
||||
public ContentType $contentType = ContentType::JSON;
|
||||
|
||||
|
||||
/**
|
||||
* @var string|ResponseEmitterInterface
|
||||
*/
|
||||
public string|ResponseEmitterInterface $emmit = SwooleHttpResponseEmitter::class;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $headers = [];
|
||||
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->contentType = \config('response.content-type', ContentType::JSON);
|
||||
$this->emmit = \config('response.emmit', SwooleHttpResponseEmitter::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
if (is_string($this->emmit)) {
|
||||
$this->emmit = di($this->emmit);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @var string|ResponseEmitterInterface
|
||||
*/
|
||||
public string|ResponseEmitterInterface $emmit = SwooleHttpResponseEmitter::class;
|
||||
|
||||
|
||||
/**
|
||||
* @param ContentType $contentType
|
||||
* @return Response
|
||||
*/
|
||||
public function withContentType(ContentType $contentType): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $contentType);
|
||||
}
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->contentType = \config('servers.response.content-type', ContentType::JSON);
|
||||
$this->headers = \config('servers.response.headers', []);
|
||||
$this->emmit = \config('servers.response.emmit', SwooleHttpResponseEmitter::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
if (is_string($this->emmit)) {
|
||||
$this->emmit = di($this->emmit);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $content
|
||||
* @param int $statusCode
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function json(array $content, int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode);
|
||||
}
|
||||
/**
|
||||
* @param ContentType $contentType
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function withContentType(ContentType $contentType): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $contentType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $content
|
||||
* @param int $statusCode
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function xml(array $content, int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode);
|
||||
}
|
||||
/**
|
||||
* @param array $content
|
||||
* @param int $statusCode
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function json(array $content, int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param array $params
|
||||
* @param int $statusCode
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function redirectTo(string $url, array $params = [], int $statusCode = 302): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $url, $params, $statusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $content
|
||||
* @param int $statusCode
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function xml(array $content, int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param int $statusCode
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function html(string $content = '', int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode);
|
||||
}
|
||||
/**
|
||||
* @param string $content
|
||||
* @param int $statusCode
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function html(string $content = '', int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param int $statusCode
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function sendfile(string $content, int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode);
|
||||
}
|
||||
/**
|
||||
* @param string $content
|
||||
* @param int $statusCode
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function sendfile(string $content, int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @param int $statusCode
|
||||
* @param ContentType $type
|
||||
* @return Response
|
||||
*/
|
||||
public function write(mixed $data, int $statusCode = 200, ContentType $type = ContentType::HTML): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $data, $statusCode, $type);
|
||||
}
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @param int $statusCode
|
||||
* @param ContentType $type
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function write(mixed $data, int $statusCode = 200, ContentType $type = ContentType::HTML): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $data, $statusCode, $type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param mixed ...$params
|
||||
* @return mixed
|
||||
*/
|
||||
private function __call__(string $method, ...$params): mixed
|
||||
{
|
||||
return \response()->{$method}(...$params);
|
||||
}
|
||||
/**
|
||||
* @param string $method
|
||||
* @param mixed ...$params
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function __call__(string $method, ...$params): mixed
|
||||
{
|
||||
return \response()->{$method}(...$params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $cookies
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function withCookieParams(array $cookies): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $cookies);
|
||||
}
|
||||
/**
|
||||
* @param array $cookies
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function withCookieParams(array $cookies): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $cookies);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCookieParams(): array
|
||||
{
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCookieParams(): array
|
||||
{
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the HTTP protocol version as a string.
|
||||
*
|
||||
* The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
|
||||
*
|
||||
* @return string HTTP protocol version.
|
||||
*/
|
||||
public function getProtocolVersion(): string
|
||||
{
|
||||
// TODO: Implement getProtocolVersion() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
/**
|
||||
* Retrieves the HTTP protocol version as a string.
|
||||
*
|
||||
* The string MUST contain only the HTTP version number (e.g., "1.1", "1.0").
|
||||
*
|
||||
* @return string HTTP protocol version.
|
||||
*/
|
||||
public function getProtocolVersion(): string
|
||||
{
|
||||
// TODO: Implement getProtocolVersion() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified HTTP protocol version.
|
||||
*
|
||||
* The version string MUST contain only the HTTP version number (e.g.,
|
||||
* "1.1", "1.0").
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new protocol version.
|
||||
*
|
||||
* @param string $version HTTP protocol version
|
||||
* @return static
|
||||
*/
|
||||
public function withProtocolVersion(string $version): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withProtocolVersion() method.
|
||||
return $this->__call__(__FUNCTION__, $version);
|
||||
}
|
||||
/**
|
||||
* Return an instance with the specified HTTP protocol version.
|
||||
*
|
||||
* The version string MUST contain only the HTTP version number (e.g.,
|
||||
* "1.1", "1.0").
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new protocol version.
|
||||
*
|
||||
* @param string $version HTTP protocol version
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function withProtocolVersion(string $version): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withProtocolVersion() method.
|
||||
return $this->__call__(__FUNCTION__, $version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all message header values.
|
||||
*
|
||||
* The keys represent the header name as it will be sent over the wire, and
|
||||
* each value is an array of strings associated with the header.
|
||||
*
|
||||
* // Represent the headers as a string
|
||||
* foreach ($message->getHeaders() as $name => $values) {
|
||||
* echo $name . ": " . implode(", ", $values);
|
||||
* }
|
||||
*
|
||||
* // Emit headers iteratively:
|
||||
* foreach ($message->getHeaders() as $name => $values) {
|
||||
* foreach ($values as $value) {
|
||||
* header(sprintf('%s: %s', $name, $value), false);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* While header names are not case-sensitive, getHeaders() will preserve the
|
||||
* exact case in which headers were originally specified.
|
||||
*
|
||||
* @return string[][] Returns an associative array of the message's headers. Each
|
||||
* key MUST be a header name, and each value MUST be an array of strings
|
||||
* for that header.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
// TODO: Implement getHeaders() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
/**
|
||||
* Retrieves all message header values.
|
||||
*
|
||||
* The keys represent the header name as it will be sent over the wire, and
|
||||
* each value is an array of strings associated with the header.
|
||||
*
|
||||
* // Represent the headers as a string
|
||||
* foreach ($message->getHeaders() as $name => $values) {
|
||||
* echo $name . ": " . implode(", ", $values);
|
||||
* }
|
||||
*
|
||||
* // Emit headers iteratively:
|
||||
* foreach ($message->getHeaders() as $name => $values) {
|
||||
* foreach ($values as $value) {
|
||||
* header(sprintf('%s: %s', $name, $value), false);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* While header names are not case-sensitive, getHeaders() will preserve the
|
||||
* exact case in which headers were originally specified.
|
||||
*
|
||||
* @return string[][] Returns an associative array of the message's headers. Each
|
||||
* key MUST be a header name, and each value MUST be an array of strings
|
||||
* for that header.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
// TODO: Implement getHeaders() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a header exists by the given case-insensitive name.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @return bool Returns true if any header names match the given header
|
||||
* name using a case-insensitive string comparison. Returns false if
|
||||
* no matching header name is found in the message.
|
||||
*/
|
||||
public function hasHeader(string $name): bool
|
||||
{
|
||||
// TODO: Implement hasHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name);
|
||||
}
|
||||
/**
|
||||
* Checks if a header exists by the given case-insensitive name.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
*
|
||||
* @return bool Returns true if any header names match the given header
|
||||
* name using a case-insensitive string comparison. Returns false if
|
||||
* no matching header name is found in the message.
|
||||
*/
|
||||
public function hasHeader(string $name): bool
|
||||
{
|
||||
// TODO: Implement hasHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a message header value by the given case-insensitive name.
|
||||
*
|
||||
* This method returns an array of all the header values of the given
|
||||
* case-insensitive header name.
|
||||
*
|
||||
* If the header does not appear in the message, this method MUST return an
|
||||
* empty array.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @return string[] An array of string values as provided for the given
|
||||
* header. If the header does not appear in the message, this method MUST
|
||||
* return an empty array.
|
||||
*/
|
||||
public function getHeader(string $name): array
|
||||
{
|
||||
// TODO: Implement getHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name);
|
||||
}
|
||||
/**
|
||||
* Retrieves a message header value by the given case-insensitive name.
|
||||
*
|
||||
* This method returns an array of all the header values of the given
|
||||
* case-insensitive header name.
|
||||
*
|
||||
* If the header does not appear in the message, this method MUST return an
|
||||
* empty array.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
*
|
||||
* @return string[] An array of string values as provided for the given
|
||||
* header. If the header does not appear in the message, this method MUST
|
||||
* return an empty array.
|
||||
*/
|
||||
public function getHeader(string $name): array
|
||||
{
|
||||
// TODO: Implement getHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a comma-separated string of the values for a single header.
|
||||
*
|
||||
* This method returns all the header values of the given
|
||||
* case-insensitive header name as a string concatenated together using
|
||||
* a comma.
|
||||
*
|
||||
* NOTE: Not all header values may be appropriately represented using
|
||||
* comma concatenation. For such headers, use getHeader() instead
|
||||
* and supply your own delimiter when concatenating.
|
||||
*
|
||||
* If the header does not appear in the message, this method MUST return
|
||||
* an empty string.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @return string A string of values as provided for the given header
|
||||
* concatenated together using a comma. If the header does not appear in
|
||||
* the message, this method MUST return an empty string.
|
||||
*/
|
||||
public function getHeaderLine(string $name): string
|
||||
{
|
||||
// TODO: Implement getHeaderLine() method.
|
||||
return $this->__call__(__FUNCTION__, $name);
|
||||
}
|
||||
/**
|
||||
* Retrieves a comma-separated string of the values for a single header.
|
||||
*
|
||||
* This method returns all the header values of the given
|
||||
* case-insensitive header name as a string concatenated together using
|
||||
* a comma.
|
||||
*
|
||||
* NOTE: Not all header values may be appropriately represented using
|
||||
* comma concatenation. For such headers, use getHeader() instead
|
||||
* and supply your own delimiter when concatenating.
|
||||
*
|
||||
* If the header does not appear in the message, this method MUST return
|
||||
* an empty string.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
*
|
||||
* @return string A string of values as provided for the given header
|
||||
* concatenated together using a comma. If the header does not appear in
|
||||
* the message, this method MUST return an empty string.
|
||||
*/
|
||||
public function getHeaderLine(string $name): string
|
||||
{
|
||||
// TODO: Implement getHeaderLine() method.
|
||||
return $this->__call__(__FUNCTION__, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the provided value replacing the specified header.
|
||||
*
|
||||
* While header names are case-insensitive, the casing of the header will
|
||||
* be preserved by this function, and returned from getHeaders().
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new and/or updated header and value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @param string|string[] $value Header value(s).
|
||||
* @return static
|
||||
* @throws
|
||||
*/
|
||||
public function withHeader(string $name, $value): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name, $value);
|
||||
}
|
||||
/**
|
||||
* Return an instance with the provided value replacing the specified header.
|
||||
*
|
||||
* While header names are case-insensitive, the casing of the header will
|
||||
* be preserved by this function, and returned from getHeaders().
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new and/or updated header and value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name.
|
||||
* @param string|string[] $value Header value(s).
|
||||
*
|
||||
* @return static
|
||||
* @throws
|
||||
*/
|
||||
public function withHeader(string $name, $value): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name, $value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $headers
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function withHeaders(array $headers): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $headers);
|
||||
}
|
||||
/**
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function withHeaders(array $headers): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified header appended with the given value.
|
||||
*
|
||||
* Existing values for the specified header will be maintained. The new
|
||||
* value(s) will be appended to the existing list. If the header did not
|
||||
* exist previously, it will be added.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new header and/or value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name to add.
|
||||
* @param string|string[] $value Header value(s).
|
||||
* @return static
|
||||
* @throws
|
||||
*/
|
||||
public function withAddedHeader(string $name, $value): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withAddedHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name, $value);
|
||||
}
|
||||
/**
|
||||
* Return an instance with the specified header appended with the given value.
|
||||
*
|
||||
* Existing values for the specified header will be maintained. The new
|
||||
* value(s) will be appended to the existing list. If the header did not
|
||||
* exist previously, it will be added.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* new header and/or value.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name to add.
|
||||
* @param string|string[] $value Header value(s).
|
||||
*
|
||||
* @return static
|
||||
* @throws
|
||||
*/
|
||||
public function withAddedHeader(string $name, $value): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withAddedHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance without the specified header.
|
||||
*
|
||||
* Header resolution MUST be done without case-sensitivity.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that removes
|
||||
* the named header.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name to remove.
|
||||
* @return static
|
||||
*/
|
||||
public function withoutHeader(string $name): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withoutHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name);
|
||||
}
|
||||
/**
|
||||
* Return an instance without the specified header.
|
||||
*
|
||||
* Header resolution MUST be done without case-sensitivity.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that removes
|
||||
* the named header.
|
||||
*
|
||||
* @param string $name Case-insensitive header field name to remove.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function withoutHeader(string $name): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withoutHeader() method.
|
||||
return $this->__call__(__FUNCTION__, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the body of the message.
|
||||
*
|
||||
* @return StreamInterface Returns the body as a stream.
|
||||
*/
|
||||
public function getBody(): StreamInterface
|
||||
{
|
||||
// TODO: Implement getBody() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
/**
|
||||
* Gets the body of the message.
|
||||
*
|
||||
* @return StreamInterface Returns the body as a stream.
|
||||
*/
|
||||
public function getBody(): StreamInterface
|
||||
{
|
||||
// TODO: Implement getBody() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified message body.
|
||||
*
|
||||
* The body MUST be a StreamInterface object.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return a new instance that has the
|
||||
* new body stream.
|
||||
*
|
||||
* @param StreamInterface $body Body.
|
||||
* @return static
|
||||
* @throws
|
||||
*/
|
||||
public function withBody(StreamInterface $body): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withBody() method.
|
||||
return $this->__call__(__FUNCTION__, $body);
|
||||
}
|
||||
/**
|
||||
* Return an instance with the specified message body.
|
||||
*
|
||||
* The body MUST be a StreamInterface object.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return a new instance that has the
|
||||
* new body stream.
|
||||
*
|
||||
* @param StreamInterface $body Body.
|
||||
*
|
||||
* @return static
|
||||
* @throws
|
||||
*/
|
||||
public function withBody(StreamInterface $body): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withBody() method.
|
||||
return $this->__call__(__FUNCTION__, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response status code.
|
||||
*
|
||||
* The status code is a 3-digit integer result code of the server's attempt
|
||||
* to understand and satisfy the request.
|
||||
*
|
||||
* @return int Status code.
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
// TODO: Implement getStatusCode() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
/**
|
||||
* Gets the response status code.
|
||||
*
|
||||
* The status code is a 3-digit integer result code of the server's attempt
|
||||
* to understand and satisfy the request.
|
||||
*
|
||||
* @return int Status code.
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
// TODO: Implement getStatusCode() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param int $statusCode
|
||||
* @param ContentType $contentType
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function raw(string $content, int $statusCode = 200, ContentType $contentType = ContentType::JSON): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode, $contentType);
|
||||
}
|
||||
/**
|
||||
* @param string $content
|
||||
* @param int $statusCode
|
||||
* @param ContentType $contentType
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function raw(string $content, int $statusCode = 200, ContentType $contentType = ContentType::JSON): ResponseInterface
|
||||
{
|
||||
return $this->__call__(__FUNCTION__, $content, $statusCode, $contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an instance with the specified status code and, optionally, reason phrase.
|
||||
*
|
||||
* If no reason phrase is specified, implementations MAY choose to default
|
||||
* to the RFC 7231 or IANA recommended reason phrase for the response's
|
||||
* status code.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* updated status and reason phrase.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
* @param int $code The 3-digit integer result code to set.
|
||||
* @param string $reasonPhrase The reason phrase to use with the
|
||||
* provided status code; if none is provided, implementations MAY
|
||||
* use the defaults as suggested in the HTTP specification.
|
||||
* @return static
|
||||
* @throws
|
||||
*/
|
||||
public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withStatus() method.
|
||||
return $this->__call__(__FUNCTION__, $code, $reasonPhrase);
|
||||
}
|
||||
/**
|
||||
* Return an instance with the specified status code and, optionally, reason phrase.
|
||||
*
|
||||
* If no reason phrase is specified, implementations MAY choose to default
|
||||
* to the RFC 7231 or IANA recommended reason phrase for the response's
|
||||
* status code.
|
||||
*
|
||||
* This method MUST be implemented in such a way as to retain the
|
||||
* immutability of the message, and MUST return an instance that has the
|
||||
* updated status and reason phrase.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
*
|
||||
* @param int $code The 3-digit integer result code to set.
|
||||
* @param string $reasonPhrase The reason phrase to use with the
|
||||
* provided status code; if none is provided, implementations MAY
|
||||
* use the defaults as suggested in the HTTP specification.
|
||||
*
|
||||
* @return static
|
||||
* @throws
|
||||
*/
|
||||
public function withStatus(int $code, string $reasonPhrase = ''): ResponseInterface
|
||||
{
|
||||
// TODO: Implement withStatus() method.
|
||||
return $this->__call__(__FUNCTION__, $code, $reasonPhrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the response reason phrase associated with the status code.
|
||||
*
|
||||
* Because a reason phrase is not a required element in a response
|
||||
* status line, the reason phrase value MAY be null. Implementations MAY
|
||||
* choose to return the default RFC 7231 recommended reason phrase (or those
|
||||
* listed in the IANA HTTP Status Code Registry) for the response's
|
||||
* status code.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
* @return string Reason phrase; must return an empty string if none present.
|
||||
*/
|
||||
public function getReasonPhrase(): string
|
||||
{
|
||||
// TODO: Implement getReasonPhrase() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
/**
|
||||
* Gets the response reason phrase associated with the status code.
|
||||
*
|
||||
* Because a reason phrase is not a required element in a response
|
||||
* status line, the reason phrase value MAY be null. Implementations MAY
|
||||
* choose to return the default RFC 7231 recommended reason phrase (or those
|
||||
* listed in the IANA HTTP Status Code Registry) for the response's
|
||||
* status code.
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc7231#section-6
|
||||
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
* @return string Reason phrase; must return an empty string if none present.
|
||||
*/
|
||||
public function getReasonPhrase(): string
|
||||
{
|
||||
// TODO: Implement getReasonPhrase() method.
|
||||
return $this->__call__(__FUNCTION__);
|
||||
}
|
||||
}
|
||||
|
||||
+201
-174
@@ -4,10 +4,15 @@ declare(strict_types=1);
|
||||
namespace Kiri\Router;
|
||||
|
||||
use Closure;
|
||||
use Kiri\Server\Events\OnWorkerStart;
|
||||
use Kiri;
|
||||
use Kiri\Abstracts\CoordinatorManager;
|
||||
use Kiri\Coordinator;
|
||||
use Kiri\Router\Validator\ValidatorMiddleware;
|
||||
use Kiri\Router\Base\Middleware as MiddlewareManager;
|
||||
use Kiri\Router\Constrict\RequestMethod;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -22,214 +27,236 @@ class Router
|
||||
{
|
||||
|
||||
|
||||
const array METHODS = [RequestMethod::REQUEST_POST, RequestMethod::REQUEST_GET, RequestMethod::REQUEST_OPTIONS, RequestMethod::REQUEST_DELETE, RequestMethod::REQUEST_PUT, RequestMethod::REQUEST_HEAD];
|
||||
const array METHODS = [RequestMethod::REQUEST_POST, RequestMethod::REQUEST_GET, RequestMethod::REQUEST_OPTIONS, RequestMethod::REQUEST_DELETE, RequestMethod::REQUEST_PUT, RequestMethod::REQUEST_HEAD];
|
||||
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private static string $type = ROUTER_TYPE_HTTP;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private static string $type = ROUTER_TYPE_HTTP;
|
||||
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Closure $closure
|
||||
*/
|
||||
public static function addServer(string $name, Closure $closure): void
|
||||
{
|
||||
static::$type = $name;
|
||||
$closure();
|
||||
static::$type = ROUTER_TYPE_HTTP;
|
||||
}
|
||||
/**
|
||||
* @param string $name
|
||||
* @param Closure $closure
|
||||
*/
|
||||
public static function addServer(string $name, Closure $closure): void
|
||||
{
|
||||
static::$type = $name;
|
||||
$closure();
|
||||
static::$type = ROUTER_TYPE_HTTP;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Closure $handler
|
||||
*/
|
||||
public static function jsonp(Closure $handler): void
|
||||
{
|
||||
static::$type = 'json-rpc';
|
||||
$handler();
|
||||
static::$type = ROUTER_TYPE_HTTP;
|
||||
}
|
||||
/**
|
||||
* @param Closure $handler
|
||||
*/
|
||||
public static function jsonp(Closure $handler): void
|
||||
{
|
||||
static::$type = 'json-rpc';
|
||||
$handler();
|
||||
static::$type = ROUTER_TYPE_HTTP;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
* @throws
|
||||
*/
|
||||
public static function post(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_POST], $route, $handler);
|
||||
}
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public static function post(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_POST], $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
* @throws
|
||||
*/
|
||||
public static function get(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_GET], $route, $handler);
|
||||
}
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public static function get(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_GET], $route, $handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
* @throws
|
||||
*/
|
||||
public static function options(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_OPTIONS], $route, $handler);
|
||||
}
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public static function options(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_OPTIONS], $route, $handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
* @throws
|
||||
*/
|
||||
public static function any(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute(self::METHODS, $route, $handler);
|
||||
}
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public static function any(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute(self::METHODS, $route, $handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
* @throws
|
||||
*/
|
||||
public static function delete(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_DELETE], $route, $handler);
|
||||
}
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public static function delete(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_DELETE], $route, $handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
* @throws
|
||||
*/
|
||||
public static function head(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_HEAD], $route, $handler);
|
||||
}
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public static function head(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_HEAD], $route, $handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
* @throws
|
||||
*/
|
||||
public static function put(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_PUT], $route, $handler);
|
||||
}
|
||||
/**
|
||||
* @param string $route
|
||||
* @param string $handler
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public static function put(string $route, string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
$router->addRoute([RequestMethod::REQUEST_PUT], $route, $handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array|RequestMethod $methods
|
||||
* @param string $route
|
||||
* @param array|string $handler
|
||||
* @throws
|
||||
*/
|
||||
public static function addRoute(array|RequestMethod $methods, string $route, array|string $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
if ($methods instanceof RequestMethod) {
|
||||
$methods = [$methods];
|
||||
}
|
||||
$router->addRoute($methods, $route, $handler);
|
||||
}
|
||||
/**
|
||||
* @param array|RequestMethod $methods
|
||||
* @param string $route
|
||||
* @param string|array $handler
|
||||
*/
|
||||
public static function addRoute(array|RequestMethod $methods, string $route, string|array $handler): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
if ($methods instanceof RequestMethod) {
|
||||
$methods = [$methods];
|
||||
}
|
||||
$router->addRoute($methods, $route, $handler);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @param Closure $closure
|
||||
* @throws
|
||||
*/
|
||||
public static function group(array $config, Closure $closure): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
/**
|
||||
* @param array $config
|
||||
* @param Closure $closure
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
public static function group(array $config, Closure $closure): void
|
||||
{
|
||||
$router = Kiri::getDi()->get(DataGrip::class)->get(static::$type);
|
||||
|
||||
$router->groupTack[] = $config;
|
||||
$router->groupTack[] = $config;
|
||||
|
||||
call_user_func($closure);
|
||||
call_user_func($closure);
|
||||
|
||||
array_pop($router->groupTack);
|
||||
}
|
||||
array_pop($router->groupTack);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws
|
||||
*/
|
||||
public function scan_build_route(): void
|
||||
{
|
||||
$this->read_dir_file(APP_PATH . 'routes');
|
||||
/**
|
||||
* @throws
|
||||
*/
|
||||
public function scan_build_route(): void
|
||||
{
|
||||
$coordinator = CoordinatorManager::utility(Coordinator::WORKER_START);
|
||||
|
||||
$container = Kiri::getDi();
|
||||
$scanner = di(Kiri\Di\Scanner::class);
|
||||
$scanner->load_directory(APP_PATH . 'app/Controller');
|
||||
$this->reset($container);
|
||||
}
|
||||
$this->read_dir_file(APP_PATH . 'routes');
|
||||
|
||||
$container = Kiri::getDi();
|
||||
$scanner = $container->get(Kiri\Di\Scanner::class);
|
||||
$scanner->scan(APP_PATH . 'app/');
|
||||
$this->reset($container);
|
||||
|
||||
$coordinator->done();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public function reset(ContainerInterface $container): void
|
||||
{
|
||||
$router = $container->get(DataGrip::class)->get(static::$type);
|
||||
$middleware = $container->get(MiddlewareManager::class);
|
||||
foreach ($router->getMethods() as $name => $method) {
|
||||
$middlewares = $middleware->get($method->getClass(), $method->getMethod());
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public function reset(ContainerInterface $container): void
|
||||
{
|
||||
$router = $container->get(DataGrip::class)->get(static::$type);
|
||||
foreach ($router->getMethods() as $name => $method) {
|
||||
$middlewares = $method->getMiddlewares();
|
||||
|
||||
$router->setHttpHandler($name, new HttpRequestHandler($middlewares, $method));
|
||||
}
|
||||
}
|
||||
foreach ($middlewares as $key => $middleware) {
|
||||
$middlewares[$key] = di($middleware);
|
||||
}
|
||||
|
||||
$requestHandler = new HttpRequestHandler($middlewares, $method);
|
||||
$validator = MiddlewareManager::getValidator($method->getClass(), $method->getMethod());
|
||||
if (!is_null($validator)) {
|
||||
$requestHandler->withValidatorMiddleware(new ValidatorMiddleware(di(ResponseInterface::class), $method->getClass(), $method->getMethod()));
|
||||
}
|
||||
$router->setHttpHandler($name, $requestHandler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
private function read_dir_file($path): void
|
||||
{
|
||||
$files = glob($path . '/*');
|
||||
for ($i = 0; $i < count($files); $i++) {
|
||||
$file = $files[$i];
|
||||
if (is_dir($file)) {
|
||||
$this->read_dir_file($file);
|
||||
} else {
|
||||
$this->resolve_file($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param $path
|
||||
*
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
private function read_dir_file($path): void
|
||||
{
|
||||
$files = glob($path . '/*');
|
||||
for ($i = 0; $i < count($files); $i++) {
|
||||
$file = $files[$i];
|
||||
if (is_dir($file)) {
|
||||
$this->read_dir_file($file);
|
||||
} else {
|
||||
$this->resolve_file($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $files
|
||||
* @throws
|
||||
*/
|
||||
private function resolve_file($files): void
|
||||
{
|
||||
try {
|
||||
include "$files";
|
||||
} catch (\Throwable $throwable) {
|
||||
error($throwable);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param $files
|
||||
*
|
||||
* @throws
|
||||
*/
|
||||
private function resolve_file($files): void
|
||||
{
|
||||
try {
|
||||
include "$files";
|
||||
} catch (\Throwable $throwable) {
|
||||
\Kiri::getLogger()->json_log($throwable);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+299
-268
@@ -6,19 +6,14 @@ namespace Kiri\Router;
|
||||
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Kiri;
|
||||
use Kiri\Router\Base\NotFoundController;
|
||||
use Kiri\Router\Constrict\RequestMethod;
|
||||
use Kiri\Di\Inject\Container;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use Throwable;
|
||||
use Traversable;
|
||||
use Kiri\Router\Base\Middleware;
|
||||
use Kiri\Router\Format\ResponseFormat;
|
||||
|
||||
|
||||
/**
|
||||
@@ -28,313 +23,349 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $_item = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $_item = [];
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $dump = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private array $dump = [];
|
||||
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $groupTack = [];
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public array $groupTack = [];
|
||||
|
||||
|
||||
/**
|
||||
* @var array<string, Handler>
|
||||
*/
|
||||
private array $methods = [];
|
||||
/**
|
||||
* @var array<string, Handler>
|
||||
*/
|
||||
private array $methods = [];
|
||||
|
||||
|
||||
/**
|
||||
* @var array<string, HttpRequestHandler>
|
||||
*/
|
||||
protected array $httpHandler = [];
|
||||
/**
|
||||
* @var array<string, HttpRequestHandler>
|
||||
*/
|
||||
protected array $httpHandler = [];
|
||||
|
||||
|
||||
/**
|
||||
* @var Handler
|
||||
*/
|
||||
protected Handler $found;
|
||||
/**
|
||||
* @var Handler
|
||||
*/
|
||||
protected Handler $found;
|
||||
|
||||
|
||||
/**
|
||||
* @var ControllerInterpreter
|
||||
*/
|
||||
#[Container(ControllerInterpreter::class)]
|
||||
public ControllerInterpreter $interpreter;
|
||||
/**
|
||||
* @throws
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->found = new Handler([NotFoundController::class, 'fail'], [], ResponseFormat::class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @var ContainerInterface
|
||||
*/
|
||||
#[Container(ContainerInterface::class)]
|
||||
public ContainerInterface $container;
|
||||
/**
|
||||
* @return Handler[]
|
||||
*/
|
||||
public function getMethods(): array
|
||||
{
|
||||
return $this->methods;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$found = di(NotFoundController::class);
|
||||
|
||||
$reflection = new ReflectionMethod($found, 'fail');
|
||||
$this->found = new Handler([$found, 'fail'], $reflection);
|
||||
}
|
||||
/**
|
||||
* @param string $method
|
||||
* @param HttpRequestHandler $handler
|
||||
* @return void
|
||||
*/
|
||||
public function setHttpHandler(string $method, HttpRequestHandler $handler): void
|
||||
{
|
||||
$this->httpHandler[$method] = $handler;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Handler[]
|
||||
*/
|
||||
public function getMethods(): array
|
||||
{
|
||||
return $this->methods;
|
||||
}
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDump(): array
|
||||
{
|
||||
return $this->dump;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param HttpRequestHandler $handler
|
||||
* @return void
|
||||
*/
|
||||
public function setHttpHandler(string $method, HttpRequestHandler $handler): void
|
||||
{
|
||||
$this->httpHandler[$method] = $handler;
|
||||
}
|
||||
/**
|
||||
* @return Traversable
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new \ArrayIterator($this->_item);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDump(): array
|
||||
{
|
||||
return $this->dump;
|
||||
}
|
||||
/**
|
||||
* @param array $method
|
||||
* @param string $route
|
||||
* @param string|array|Closure $closure
|
||||
*/
|
||||
public function addRoute(array $method, string $route, string|array|Closure $closure): void
|
||||
{
|
||||
try {
|
||||
$route = $this->_splicing_routing($route);
|
||||
if ($closure instanceof Closure) {
|
||||
$handler = di(ControllerInterpreter::class)->addRouteByClosure($closure);
|
||||
} else {
|
||||
$handler = $this->resolve($closure, di(ControllerInterpreter::class));
|
||||
}
|
||||
foreach ($method as $value) {
|
||||
if ($value instanceof RequestMethod) {
|
||||
$value = $value->getString();
|
||||
}
|
||||
$handler->setRequestMethod($value);
|
||||
if (is_array($closure)) {
|
||||
$closure[0] = is_object($closure[0]) ? get_class($closure[0]) : $closure;
|
||||
} else if (is_string($closure)) {
|
||||
$closure = explode('@', $closure);
|
||||
}
|
||||
$this->register($route, $value, $handler);
|
||||
}
|
||||
} catch (Throwable $throwable) {
|
||||
\Kiri::getLogger()->json_log($throwable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Traversable
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new \ArrayIterator($this->_item);
|
||||
}
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dump(): array
|
||||
{
|
||||
$array = [];
|
||||
foreach ($this->methods as $methodPath => $handler) {
|
||||
[$path, $method] = explode('_', $methodPath);
|
||||
|
||||
$controller = $handler instanceof Closure ? '\Closure' : $handler->getClass() . '::' . $handler->getMethod();
|
||||
$array[] = [
|
||||
'path' => $path,
|
||||
'method' => $method,
|
||||
'handler' => $controller
|
||||
];
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $method
|
||||
* @param string $route
|
||||
* @param string|array|Closure $closure
|
||||
*/
|
||||
public function addRoute(array $method, string $route, string|array|Closure $closure): void
|
||||
{
|
||||
try {
|
||||
$route = $this->_splicing_routing($route);
|
||||
if ($closure instanceof Closure) {
|
||||
$handler = $this->interpreter->addRouteByClosure($closure);
|
||||
} else {
|
||||
$handler = $this->resolve($closure, $this->interpreter);
|
||||
}
|
||||
foreach ($method as $value) {
|
||||
if ($value instanceof RequestMethod) {
|
||||
$value = $value->getString();
|
||||
}
|
||||
$handler->setRequestMethod($value);
|
||||
if (is_array($closure)) {
|
||||
$closure[0] = is_object($closure[0]) ? get_class($closure[0]) : $closure;
|
||||
} else if (is_string($closure)) {
|
||||
$closure = explode('@', $closure);
|
||||
}
|
||||
$this->register($route, $value, $handler);
|
||||
}
|
||||
} catch (Throwable $throwable) {
|
||||
error($throwable);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param string|array $closure
|
||||
* @param ControllerInterpreter $interpreter
|
||||
* @return Handler
|
||||
* @throws
|
||||
*/
|
||||
private function resolve(string|array $closure, ControllerInterpreter $interpreter): Handler
|
||||
{
|
||||
$container = \Kiri::getDi();
|
||||
if (is_array($closure)) {
|
||||
if (is_string($closure[0])) {
|
||||
$closure[0] = $container->get($closure[0]);
|
||||
}
|
||||
return $interpreter->addRouteByString(... $closure);
|
||||
}
|
||||
if (!str_contains($closure, '@')) {
|
||||
$closure .= '@';
|
||||
}
|
||||
[$className, $method] = explode('@', $closure);
|
||||
$class = $container->get($this->resetName($className));
|
||||
return $interpreter->addRouteByString($class, $method);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function dump(): array
|
||||
{
|
||||
$array = [];
|
||||
foreach ($this->methods as $methodPath => $handler) {
|
||||
[$path, $method] = explode('_', $methodPath);
|
||||
|
||||
$controller = $handler->isClosure() ? '\Closure' : $handler->getClass() . '::' . $handler->getMethod();
|
||||
$array[] = [
|
||||
'path' => $path,
|
||||
'method' => $method,
|
||||
'handler' => $controller
|
||||
];
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
/**
|
||||
* @param string $className
|
||||
* @return string
|
||||
*/
|
||||
private function resetName(string $className): string
|
||||
{
|
||||
$namespace = array_filter(array_column($this->groupTack, 'namespace'));
|
||||
if (count($namespace) < 1) {
|
||||
return $className;
|
||||
}
|
||||
return implode('\\', $namespace) . '\\' . $className;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|array $closure
|
||||
* @param ControllerInterpreter $interpreter
|
||||
* @return Handler
|
||||
* @throws
|
||||
*/
|
||||
private function resolve(string|array $closure, ControllerInterpreter $interpreter): Handler
|
||||
{
|
||||
if (is_array($closure)) {
|
||||
return $interpreter->addRouteByString(... $closure);
|
||||
}
|
||||
if (!str_contains($closure, '@')) {
|
||||
$closure .= '@';
|
||||
}
|
||||
[$className, $method] = explode('@', $closure);
|
||||
$class = $this->container->get($this->resetName($className));
|
||||
return $interpreter->addRouteByString($class, $method);
|
||||
}
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $method
|
||||
* @param Handler $handler
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public function register(string $path, string $method, Handler $handler): void
|
||||
{
|
||||
$this->methods[$method . '_' . $path] = $handler;
|
||||
$handler->setMiddlewares($this->registerMiddleware($handler->getClass(), $handler->getMethod()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @return string
|
||||
*/
|
||||
private function resetName(string $className): string
|
||||
{
|
||||
$namespace = array_filter(array_column($this->groupTack, 'namespace'));
|
||||
if (count($namespace) < 1) {
|
||||
return $className;
|
||||
}
|
||||
return implode('\\', $namespace) . '\\' . $className;
|
||||
}
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return array
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function registerMiddleware(string $class, string $method): array
|
||||
{
|
||||
$response = [];
|
||||
$middlewares = \config('servers.request.middlewares', []);
|
||||
if (is_array($middlewares) && count($middlewares) > 0) {
|
||||
$response = $this->appendMiddleware($response, $middlewares);
|
||||
}
|
||||
$middlewares = array_column($this->groupTack, 'middleware');
|
||||
$response = $this->appendMiddleware($response, $middlewares);
|
||||
|
||||
return $this->read_method_middleware($response, $class, $method);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $method
|
||||
* @param Handler $handler
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public function register(string $path, string $method, Handler $handler): void
|
||||
{
|
||||
$this->methods[$method . '_' . $path] = $handler;
|
||||
$this->registerMiddleware($handler->getClass(), $handler->getMethod());
|
||||
}
|
||||
/**
|
||||
* @param array $response
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return array
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function read_method_middleware(array $response, string $class, string $method): array
|
||||
{
|
||||
$reflect = \Kiri::getDi()->getReflectionClass($class);
|
||||
$attributes = $reflect->getMethod($method)->getAttributes(Annotate\Middleware::class);
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
/** @var Annotate\Middleware $instance */
|
||||
$instance = $attribute->newInstance();
|
||||
|
||||
$data = $instance->middleware;
|
||||
if (is_string($data)) {
|
||||
$data = [$data];
|
||||
}
|
||||
|
||||
foreach ($data as $middleware) {
|
||||
if (!in_array($middleware, $response)) {
|
||||
$response[] = $middleware;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public function registerMiddleware(string $class, string $method): void
|
||||
{
|
||||
$middlewares = \request()->middlewares;
|
||||
if (count($middlewares) > 0) {
|
||||
$this->appendMiddleware($middlewares, $class, $method);
|
||||
}
|
||||
$middlewares = array_column($this->groupTack, 'middleware');
|
||||
if (count($middlewares) > 0) {
|
||||
$this->appendMiddleware($middlewares, $class, $method);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param array $response
|
||||
* @param array $middlewares
|
||||
* @return array
|
||||
*/
|
||||
private function appendMiddleware(array $response, array $middlewares): array
|
||||
{
|
||||
foreach ($middlewares as $middleware) {
|
||||
if (is_string($middleware)) {
|
||||
$middleware = [$middleware];
|
||||
}
|
||||
foreach ($middleware as $value) {
|
||||
if (!in_array($value, $response)) {
|
||||
$response[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $method
|
||||
* @return HttpRequestHandler
|
||||
* @throws
|
||||
*/
|
||||
public function query(string $path, string $method): HttpRequestHandler
|
||||
{
|
||||
return $this->httpHandler[$method . '_' . $path] ?? $this->not_found_handler();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $middlewares
|
||||
* @param $class
|
||||
* @param $method
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
private function appendMiddleware(array $middlewares, $class, $method): void
|
||||
{
|
||||
foreach ($middlewares as $middleware) {
|
||||
if (is_string($middleware)) {
|
||||
$middleware = [$middleware];
|
||||
}
|
||||
foreach ($middleware as $value) {
|
||||
Middleware::set($class, $method, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @param string $method
|
||||
* @return HttpRequestHandler
|
||||
* @throws
|
||||
*/
|
||||
public function query(string $path, string $method): HttpRequestHandler
|
||||
{
|
||||
return $this->httpHandler[$method . '_' . $path] ?? new HttpRequestHandler([], $this->found);
|
||||
}
|
||||
/**
|
||||
* @return HttpRequestHandler
|
||||
*/
|
||||
protected function not_found_handler(): HttpRequestHandler
|
||||
{
|
||||
$middlewares = \config('servers.request.middlewares', []);
|
||||
if (!is_array($middlewares) || count($middlewares) < 1) {
|
||||
return new HttpRequestHandler($middlewares, $this->found);
|
||||
}
|
||||
for ($index = 0; $index < count($middlewares); $index++) {
|
||||
$middlewares[$index] = \Kiri::getDi()->get($middlewares[$index]);
|
||||
}
|
||||
return new HttpRequestHandler($middlewares, $this->found);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $route
|
||||
* @return string
|
||||
*/
|
||||
protected function _splicing_routing(string $route): string
|
||||
{
|
||||
$route = ltrim($route, '/');
|
||||
$prefix = array_column($this->groupTack, 'prefix');
|
||||
if (empty($prefix = array_filter($prefix))) {
|
||||
return '/' . $route;
|
||||
}
|
||||
return '/' . implode('/', $prefix) . '/' . $route;
|
||||
}
|
||||
/**
|
||||
* @param string $route
|
||||
* @return string
|
||||
*/
|
||||
protected function _splicing_routing(string $route): string
|
||||
{
|
||||
$route = ltrim($route, '/');
|
||||
$prefix = array_column($this->groupTack, 'prefix');
|
||||
if (empty($prefix = array_filter($prefix))) {
|
||||
return '/' . $route;
|
||||
}
|
||||
return '/' . implode('/', $prefix) . '/' . $route;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
// TODO: Implement offsetExists() method.
|
||||
return isset($this->_item[$offset]);
|
||||
}
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
// TODO: Implement offsetExists() method.
|
||||
return isset($this->_item[$offset]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return Router|null
|
||||
*/
|
||||
public function offsetGet(mixed $offset): ?Router
|
||||
{
|
||||
if ($this->offsetExists($offset)) {
|
||||
return $this->_item[$offset];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return Router|null
|
||||
*/
|
||||
public function offsetGet(mixed $offset): ?Router
|
||||
{
|
||||
if ($this->offsetExists($offset)) {
|
||||
return $this->_item[$offset];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
// TODO: Implement offsetSet() method.
|
||||
$this->_item[$offset] = $value;
|
||||
}
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
// TODO: Implement offsetSet() method.
|
||||
$this->_item[$offset] = $value;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
unset($this->_item[$offset]);
|
||||
}
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return void
|
||||
*/
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
unset($this->_item[$offset]);
|
||||
}
|
||||
}
|
||||
|
||||
+415
@@ -0,0 +1,415 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router;
|
||||
|
||||
use Kiri\Di\Context;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Class Session
|
||||
* @package Kiri\Router
|
||||
*/
|
||||
class Session
|
||||
{
|
||||
/**
|
||||
* Session 数据存储键
|
||||
*/
|
||||
private const string SESSION_KEY = 'kiri.session.data';
|
||||
|
||||
/**
|
||||
* Session ID 键
|
||||
*/
|
||||
private const string SESSION_ID_KEY = 'kiri.session.id';
|
||||
|
||||
/**
|
||||
* Session 名称
|
||||
*/
|
||||
private const string SESSION_NAME = 'KIRI_SESSION_ID';
|
||||
|
||||
/**
|
||||
* Session 存储路径
|
||||
*/
|
||||
private static ?string $savePath = null;
|
||||
|
||||
/**
|
||||
* Session 生命周期(秒)
|
||||
*/
|
||||
private static int $lifetime = 7200; // 默认2小时
|
||||
|
||||
/**
|
||||
* 是否已启动
|
||||
*/
|
||||
private static bool $started = false;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化 Session
|
||||
* @param string|null $savePath Session 存储路径
|
||||
* @param int $lifetime Session 生命周期(秒)
|
||||
* @return void
|
||||
*/
|
||||
public static function init(?string $savePath = null, int $lifetime = 7200): void
|
||||
{
|
||||
self::$savePath = $savePath ?? storage(null, 'session');
|
||||
self::$lifetime = $lifetime;
|
||||
|
||||
// 确保存储目录存在
|
||||
if (!is_dir(self::$savePath)) {
|
||||
mkdir(self::$savePath, 0755, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 启动 Session
|
||||
* @param ServerRequestInterface|null $request
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public static function start(?ServerRequestInterface $request = null): void
|
||||
{
|
||||
if (self::$started) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($request === null) {
|
||||
$request = \request();
|
||||
}
|
||||
|
||||
// 获取或创建 Session ID
|
||||
$sessionId = self::getSessionId($request);
|
||||
|
||||
// 加载 Session 数据
|
||||
$sessionData = self::loadSessionData($sessionId);
|
||||
|
||||
// 存储到 Context
|
||||
Context::set(self::SESSION_KEY, $sessionData);
|
||||
Context::set(self::SESSION_ID_KEY, $sessionId);
|
||||
|
||||
self::$started = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 Session ID
|
||||
* @param ServerRequestInterface $request
|
||||
* @return string
|
||||
*/
|
||||
private static function getSessionId(ServerRequestInterface $request): string
|
||||
{
|
||||
$cookies = $request->getCookieParams();
|
||||
|
||||
// 从 Cookie 中获取 Session ID
|
||||
if (isset($cookies[self::SESSION_NAME]) && !empty($cookies[self::SESSION_NAME])) {
|
||||
$sessionId = $cookies[self::SESSION_NAME];
|
||||
// 验证 Session ID 格式
|
||||
if (preg_match('/^[a-zA-Z0-9]{32,}$/', $sessionId)) {
|
||||
return $sessionId;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成新的 Session ID
|
||||
return self::generateSessionId();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成 Session ID
|
||||
* @return string
|
||||
*/
|
||||
private static function generateSessionId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载 Session 数据
|
||||
* @param string $sessionId
|
||||
* @return array
|
||||
*/
|
||||
private static function loadSessionData(string $sessionId): array
|
||||
{
|
||||
$filePath = self::getSessionFilePath($sessionId);
|
||||
|
||||
if (!file_exists($filePath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = file_get_contents($filePath);
|
||||
if ($data === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sessionData = json_decode($data, true);
|
||||
if (!is_array($sessionData)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (isset($sessionData['expires_at']) && $sessionData['expires_at'] < time()) {
|
||||
self::destroySession($sessionId);
|
||||
return [];
|
||||
}
|
||||
|
||||
return $sessionData['data'] ?? [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存 Session 数据
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public static function save(): void
|
||||
{
|
||||
if (!self::$started) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessionId = Context::get(self::SESSION_ID_KEY);
|
||||
$sessionData = Context::get(self::SESSION_KEY);
|
||||
|
||||
if ($sessionId === null || $sessionData === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filePath = self::getSessionFilePath($sessionId);
|
||||
$data = [
|
||||
'data' => $sessionData,
|
||||
'expires_at' => time() + self::$lifetime,
|
||||
'created_at' => time(),
|
||||
];
|
||||
|
||||
file_put_contents($filePath, json_encode($data, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// 设置 Cookie
|
||||
self::setSessionCookie($sessionId);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置 Session Cookie
|
||||
* @param string $sessionId
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
private static function setSessionCookie(string $sessionId): void
|
||||
{
|
||||
$response = \response();
|
||||
$expires = time() + self::$lifetime;
|
||||
|
||||
// 获取现有的 Cookie 参数
|
||||
$cookieParams = $response->getCookieParams();
|
||||
|
||||
// 添加 Session Cookie
|
||||
// Swoole setCookie 参数: name, value, expires, path, domain, secure, httponly, samesite
|
||||
$cookieParams[] = [
|
||||
self::SESSION_NAME, // name
|
||||
$sessionId, // value
|
||||
$expires, // expires
|
||||
'/', // path
|
||||
'', // domain
|
||||
false, // secure
|
||||
true, // httponly
|
||||
'Lax' // samesite
|
||||
];
|
||||
|
||||
$response->withCookieParams($cookieParams);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 Session 文件路径
|
||||
* @param string $sessionId
|
||||
* @return string
|
||||
*/
|
||||
private static function getSessionFilePath(string $sessionId): string
|
||||
{
|
||||
if (self::$savePath === null) {
|
||||
self::init();
|
||||
}
|
||||
|
||||
return self::$savePath . '/' . $sessionId . '.json';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 销毁 Session 文件
|
||||
* @param string $sessionId
|
||||
* @return void
|
||||
*/
|
||||
private static function destroySession(string $sessionId): void
|
||||
{
|
||||
$filePath = self::getSessionFilePath($sessionId);
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 存储 Session 数据
|
||||
* @param string|array $key 键名或键值对数组
|
||||
* @param mixed $value 值(当 $key 为字符串时)
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public static function put(string|array $key, mixed $value = null): void
|
||||
{
|
||||
if (!self::$started) {
|
||||
self::start();
|
||||
}
|
||||
|
||||
$sessionData = Context::get(self::SESSION_KEY, []);
|
||||
|
||||
// 支持批量设置
|
||||
if (is_array($key)) {
|
||||
$sessionData = array_merge($sessionData, $key);
|
||||
} else {
|
||||
$sessionData[$key] = $value;
|
||||
}
|
||||
|
||||
Context::set(self::SESSION_KEY, $sessionData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取 Session 数据
|
||||
* @param string|null $key 键名,为 null 时返回所有数据
|
||||
* @param mixed $default 默认值
|
||||
* @return mixed
|
||||
* @throws
|
||||
*/
|
||||
public static function get(?string $key = null, mixed $default = null): mixed
|
||||
{
|
||||
if (!self::$started) {
|
||||
self::start();
|
||||
}
|
||||
|
||||
$sessionData = Context::get(self::SESSION_KEY, []);
|
||||
|
||||
// 如果 key 为 null,返回所有数据
|
||||
if ($key === null) {
|
||||
return $sessionData;
|
||||
}
|
||||
|
||||
return $sessionData[$key] ?? $default;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查 Session 中是否存在指定键
|
||||
* @param string $key
|
||||
* @return bool
|
||||
* @throws
|
||||
*/
|
||||
public static function has(string $key): bool
|
||||
{
|
||||
if (!self::$started) {
|
||||
self::start();
|
||||
}
|
||||
|
||||
$sessionData = Context::get(self::SESSION_KEY, []);
|
||||
return isset($sessionData[$key]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除 Session 数据
|
||||
* @param string $key
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public static function forget(string $key): void
|
||||
{
|
||||
if (!self::$started) {
|
||||
self::start();
|
||||
}
|
||||
|
||||
$sessionData = Context::get(self::SESSION_KEY, []);
|
||||
unset($sessionData[$key]);
|
||||
Context::set(self::SESSION_KEY, $sessionData);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清空所有 Session 数据
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public static function flush(): void
|
||||
{
|
||||
if (!self::$started) {
|
||||
self::start();
|
||||
}
|
||||
|
||||
Context::set(self::SESSION_KEY, []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有 Session 数据
|
||||
* @return array
|
||||
* @throws
|
||||
*/
|
||||
public static function all(): array
|
||||
{
|
||||
if (!self::$started) {
|
||||
self::start();
|
||||
}
|
||||
|
||||
return Context::get(self::SESSION_KEY, []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 销毁 Session
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public static function destroy(): void
|
||||
{
|
||||
if (!self::$started) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessionId = Context::get(self::SESSION_ID_KEY);
|
||||
if ($sessionId !== null) {
|
||||
self::destroySession($sessionId);
|
||||
}
|
||||
|
||||
Context::set(self::SESSION_KEY, []);
|
||||
Context::set(self::SESSION_ID_KEY, null);
|
||||
self::$started = false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 重新生成 Session ID
|
||||
* @return void
|
||||
* @throws
|
||||
*/
|
||||
public static function regenerate(): void
|
||||
{
|
||||
if (!self::$started) {
|
||||
self::start();
|
||||
}
|
||||
|
||||
$oldSessionId = Context::get(self::SESSION_ID_KEY);
|
||||
|
||||
// 生成新的 Session ID
|
||||
$newSessionId = self::generateSessionId();
|
||||
|
||||
// 删除旧 Session 文件
|
||||
if ($oldSessionId !== null) {
|
||||
self::destroySession($oldSessionId);
|
||||
}
|
||||
|
||||
// 保存新 Session
|
||||
Context::set(self::SESSION_ID_KEY, $newSessionId);
|
||||
self::setSessionCookie($newSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router;
|
||||
|
||||
use Kiri\Di\Inject\Container;
|
||||
use Kiri\Di\Interface\ResponseEmitterInterface;
|
||||
use Kiri\Events\EventDispatch;
|
||||
use Kiri\Events\EventProvider;
|
||||
use Kiri\Server\Events\OnAfterRequest;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use ReflectionException;
|
||||
use SplPriorityQueue;
|
||||
use function swoole_version;
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,12 +32,11 @@ class SwooleHttpResponseEmitter implements ResponseEmitterInterface
|
||||
|
||||
/**
|
||||
* @param EventDispatch $dispatch
|
||||
* @param EventProvider $provider
|
||||
*/
|
||||
public function __construct(readonly public EventDispatch $dispatch, readonly public EventProvider $provider)
|
||||
public function __construct(readonly public EventDispatch $dispatch)
|
||||
{
|
||||
$this->afterRequest = new OnAfterRequest();
|
||||
$this->events = $this->provider->getListenersForEvent($this->afterRequest);
|
||||
$this->events = di(EventProvider::class)->getListenersForEvent($this->afterRequest);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +66,7 @@ class SwooleHttpResponseEmitter implements ResponseEmitterInterface
|
||||
*/
|
||||
private function writeParams(ResponseInterface $proxy, object $response, object $request): void
|
||||
{
|
||||
/** @var \Swoole\Http\Response $response */
|
||||
$response->setStatusCode($proxy->getStatusCode());
|
||||
$headers = $proxy->getHeaders();
|
||||
if (count($headers) > 0) foreach ($headers as $name => $header) {
|
||||
@@ -79,9 +76,9 @@ class SwooleHttpResponseEmitter implements ResponseEmitterInterface
|
||||
if (count($cookieParams) > 0) foreach ($cookieParams as $cookie) {
|
||||
$response->setCookie(...$cookie);
|
||||
}
|
||||
$response->header('Run-Time', $this->getRunTime($request));
|
||||
$response->header('Run-Time', $this->getRunTime($request) . '');
|
||||
$response->header('Server', 'swoole');
|
||||
$response->header('Swoole-Version', \swoole_version());
|
||||
$response->header('Swoole-Version', swoole_version());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,22 +7,12 @@ use Kiri\Di\Interface\ResponseEmitterInterface;
|
||||
use Kiri\Events\EventDispatch;
|
||||
use Kiri\Events\EventProvider;
|
||||
use Kiri\Server\Events\OnAfterRequest;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use ReflectionException;
|
||||
use SplPriorityQueue;
|
||||
|
||||
class SwowHttpResponseEmitter implements ResponseEmitterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EventProvider
|
||||
*/
|
||||
#[Container(EventProvider::class)]
|
||||
public EventProvider $provider;
|
||||
|
||||
|
||||
/**
|
||||
* @var EventDispatch
|
||||
*/
|
||||
@@ -48,7 +38,7 @@ class SwowHttpResponseEmitter implements ResponseEmitterInterface
|
||||
public function init(): void
|
||||
{
|
||||
$this->afterRequest = new OnAfterRequest();
|
||||
$this->events = $this->provider->getListenersForEvent($this->afterRequest);
|
||||
$this->events = di(EventProvider::class)->getListenersForEvent($this->afterRequest);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -54,9 +54,7 @@ class BindForm implements InjectParameterInterface
|
||||
}
|
||||
}
|
||||
|
||||
$middleware = \instance(ValidatorMiddleware::class);
|
||||
$middleware->validator = $validator;
|
||||
Middleware::set($class, $method, $middleware);
|
||||
Middleware::setValidator($class, $method, $validator);
|
||||
|
||||
return $validator->getFormData();
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ class ArrayProxy extends TypesProxy
|
||||
*/
|
||||
public function dispatch(object $form, string $field, mixed $value): bool
|
||||
{
|
||||
if (is_null($value)) {
|
||||
$form->{$field} = !$this->allowsNull ? [] : null;
|
||||
return true;
|
||||
if (empty($value)) {
|
||||
return ($form->{$field} = []) === [];
|
||||
} else {
|
||||
return $value == ($form->{$field} = $value);
|
||||
}
|
||||
return $value == ($form->{$field} = $value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,7 @@ class MixedProxy extends TypesProxy
|
||||
try {
|
||||
return $value == ($form->{$field} = $value);
|
||||
} catch (\Throwable $throwable) {
|
||||
return false;
|
||||
return $this->getLogger()->json_log($throwable, [], false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Kiri\Router\Validator\Types;
|
||||
|
||||
abstract class TypesProxy
|
||||
use Kiri\Abstracts\Component;
|
||||
|
||||
abstract class TypesProxy extends Component
|
||||
{
|
||||
|
||||
|
||||
|
||||
@@ -133,11 +133,9 @@ class Validator
|
||||
}
|
||||
|
||||
/** @var array $rule */
|
||||
foreach ($rules as $rule) {
|
||||
if (!call_user_func($rule, $this->formData->{$name})) {
|
||||
return $this->addError('Request field ' . $name . ' value format error');
|
||||
}
|
||||
}
|
||||
if (array_any($rules, fn($rule) => !call_user_func($rule, $this->formData->{$name}))) {
|
||||
return $this->addError('Request field ' . $name . ' value format error');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Validator;
|
||||
|
||||
use Kiri\Di\Inject\Container;
|
||||
use Kiri;
|
||||
use Kiri\Router\Base\Middleware;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
@@ -16,14 +17,14 @@ class ValidatorMiddleware implements MiddlewareInterface
|
||||
{
|
||||
|
||||
|
||||
public Validator $validator;
|
||||
|
||||
|
||||
/**
|
||||
* @var ResponseInterface
|
||||
*/
|
||||
#[Container(ResponseInterface::class)]
|
||||
public ResponseInterface $response;
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
*/
|
||||
public function __construct(public ResponseInterface $response ,public string $class, public string $method)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -34,8 +35,11 @@ class ValidatorMiddleware implements MiddlewareInterface
|
||||
*/
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if (!$this->validator->run($request)) {
|
||||
return $this->response->html($this->validator->error(), 415);
|
||||
$validator = Middleware::getValidator($this->class, $this->method);
|
||||
if (!$validator->run($request)) {
|
||||
Kiri::getLogger()->println($request->getUri()->getPath() . ' `' . $validator->error() . '`');
|
||||
|
||||
return $this->response->html($validator->error(), 400);
|
||||
} else {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Kiri\Router\Blade\BladeFactory;
|
||||
use Kiri\Router\Blade\BladeHelper;
|
||||
|
||||
/**
|
||||
* 渲染 Blade 视图
|
||||
*
|
||||
* @param string $path 视图路径(支持 . 分隔,如 'user.profile')
|
||||
* @param array $data 视图数据
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
function View(string $path, array $data = []): ResponseInterface
|
||||
{
|
||||
$response = \response();
|
||||
$response->withAddedHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
|
||||
try {
|
||||
// 获取视图路径和缓存路径
|
||||
$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);
|
||||
}
|
||||
|
||||
// 渲染视图
|
||||
return $response->html($factory->render($path, $data));
|
||||
} catch (\Exception $throwable) {
|
||||
\Kiri::getLogger()->json_log($throwable);
|
||||
|
||||
ob_start();
|
||||
|
||||
extract(['errorData' => $throwable], EXTR_SKIP);
|
||||
include __DIR__.'/template/error.php';
|
||||
|
||||
$message = ob_get_clean();
|
||||
|
||||
return $response->html($message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
||||
<title>Error: <?= htmlspecialchars($errorData['type']) ?></title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.error-header {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
color: white;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.error-title i {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.error-subtitle {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.error-section {
|
||||
margin-bottom: 30px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
background: #f5f5f5;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.section-header:hover {
|
||||
background: #e8e8e8;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.section-toggle {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 20px;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #282c34;
|
||||
color: #abb2bf;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
font-size: 14px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.file-path {
|
||||
color: #61afef;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.line-number {
|
||||
color: #98c379;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #e06c75;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f5f5f5;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.trace-item {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid #667eea;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.trace-header {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.trace-location {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.badge-error {
|
||||
background: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: #ff9800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-info {
|
||||
background: #2196f3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.environment-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #5a6fd8;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.error-container {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.environment-info {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<div class="error-header">
|
||||
<h1 class="error-title">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<?= htmlspecialchars($errorData['type']) ?>
|
||||
</h1>
|
||||
<div class="error-subtitle">
|
||||
<?= htmlspecialchars($errorData['message']) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error-content">
|
||||
<!-- Error Details -->
|
||||
<div class="error-section">
|
||||
<div class="section-header" onclick="toggleSection('details')">
|
||||
<span class="section-title">Error Details</span>
|
||||
<span class="section-toggle" id="toggle-details">▼</span>
|
||||
</div>
|
||||
<div class="section-content" id="section-details">
|
||||
<div class="code-block">
|
||||
<span class="file-path"><?= htmlspecialchars($errorData['file']) ?></span>
|
||||
<span class="line-number">:<?= $errorData['line'] ?></span>
|
||||
<br><br>
|
||||
<span class="error-message"><?= htmlspecialchars($errorData['message']) ?></span>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<td><span class="badge badge-error"><?= htmlspecialchars($errorData['type']) ?></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<td><?= $errorData['code'] ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<td><?= $errorData['timestamp'] ?></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stack Trace -->
|
||||
<div class="error-section">
|
||||
<div class="section-header" onclick="toggleSection('trace')">
|
||||
<span class="section-title">Stack Trace</span>
|
||||
<span class="section-toggle" id="toggle-trace">▼</span>
|
||||
</div>
|
||||
<div class="section-content" id="section-trace">
|
||||
<?php
|
||||
foreach ($errorData['trace'] as $index => $trace): ?>
|
||||
<div class="trace-item">
|
||||
<div class="trace-header">
|
||||
#<?= $index ?>:
|
||||
<?= $trace['class'] ?? '' ?><?= $trace['type'] ?? '' ?><?= $trace['function'] ?>()
|
||||
</div>
|
||||
<div class="trace-location">
|
||||
<?= $trace['file'] ?? 'internal' ?><?= isset($trace['line']) ? ':' . $trace['line'] : '' ?>
|
||||
</div>
|
||||
<?php
|
||||
if (!empty($trace['args'])): ?>
|
||||
<div style="margin-top: 5px; font-size: 12px; color: #666;">
|
||||
Arguments: <?= implode(', ', $trace['args']) ?>
|
||||
</div>
|
||||
<?php
|
||||
endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Server Information -->
|
||||
<div class="error-section">
|
||||
<div class="section-header" onclick="toggleSection('server')">
|
||||
<span class="section-title">Server Information</span>
|
||||
<span class="section-toggle" id="toggle-server">▼</span>
|
||||
</div>
|
||||
<div class="section-content hidden" id="section-server">
|
||||
<table>
|
||||
<?php
|
||||
foreach ($errorData['server'] as $key => $value): ?>
|
||||
<tr>
|
||||
<th><?= htmlspecialchars($key) ?></th>
|
||||
<td><?= htmlspecialchars($value) ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
endforeach; ?>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Request Information -->
|
||||
<div class="error-section">
|
||||
<div class="section-header" onclick="toggleSection('request')">
|
||||
<span class="section-title">Request Information</span>
|
||||
<span class="section-toggle" id="toggle-request">▼</span>
|
||||
</div>
|
||||
<div class="section-content hidden" id="section-request">
|
||||
<h4>GET Parameters</h4>
|
||||
<?php
|
||||
if (!empty($errorData['request']['GET'])): ?>
|
||||
<table>
|
||||
<?php
|
||||
foreach ($errorData['request']['GET'] as $key => $value): ?>
|
||||
<tr>
|
||||
<th><?= htmlspecialchars($key) ?></th>
|
||||
<td><?= htmlspecialchars(is_array($value) ? json_encode($value) : $value) ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
endforeach; ?>
|
||||
</table>
|
||||
<?php
|
||||
else: ?>
|
||||
<p>No GET parameters</p>
|
||||
<?php
|
||||
endif; ?>
|
||||
|
||||
<h4>POST Parameters</h4>
|
||||
<?php
|
||||
if (!empty($errorData['request']['POST'])): ?>
|
||||
<table>
|
||||
<?php
|
||||
foreach ($errorData['request']['POST'] as $key => $value): ?>
|
||||
<tr>
|
||||
<th><?= htmlspecialchars($key) ?></th>
|
||||
<td><?= htmlspecialchars(is_array($value) ? json_encode($value) : $value) ?></td>
|
||||
</tr>
|
||||
<?php
|
||||
endforeach; ?>
|
||||
</table>
|
||||
<?php
|
||||
else: ?>
|
||||
<p>No POST parameters</p>
|
||||
<?php
|
||||
endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Environment Info -->
|
||||
<div class="environment-info">
|
||||
<div class="info-item">
|
||||
<div class="info-label">Memory Usage</div>
|
||||
<div class="info-value"><?= $errorData['memory_usage'] ?></div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Peak Memory</div>
|
||||
<div class="info-value"><?= $errorData['peak_memory'] ?></div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Execution Time</div>
|
||||
<div class="info-value"><?= round($errorData['execution_time'], 4) ?> seconds</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">PHP Version</div>
|
||||
<div class="info-value"><?= PHP_VERSION ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" onclick="window.location.reload()">
|
||||
<i class="fas fa-redo"></i> Reload Page
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="window.history.back()">
|
||||
<i class="fas fa-arrow-left"></i> Go Back
|
||||
</button>
|
||||
<a href="/" class="btn btn-secondary">
|
||||
<i class="fas fa-home"></i> Go Home
|
||||
</a>
|
||||
<button class="btn btn-secondary" onclick="copyErrorDetails()">
|
||||
<i class="fas fa-copy"></i> Copy Error Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleSection(sectionId) {
|
||||
const section = document.getElementById(`section-${sectionId}`);
|
||||
const toggle = document.getElementById(`toggle-${sectionId}`);
|
||||
|
||||
if (section.classList.contains('hidden')) {
|
||||
section.classList.remove('hidden');
|
||||
toggle.textContent = '▲';
|
||||
} else {
|
||||
section.classList.add('hidden');
|
||||
toggle.textContent = '▼';
|
||||
}
|
||||
}
|
||||
|
||||
function copyErrorDetails() {
|
||||
const errorDetails = {
|
||||
type: '<?= addslashes($errorData['type']) ?>',
|
||||
message: '<?= addslashes($errorData['message']) ?>',
|
||||
file: '<?= addslashes($errorData['file']) ?>',
|
||||
line: <?= $errorData['line'] ?>,
|
||||
timestamp: '<?= $errorData['timestamp'] ?>',
|
||||
trace: <?= json_encode($errorData['trace']) ?>
|
||||
};
|
||||
|
||||
const text = `Error Details:
|
||||
Type: ${errorDetails.type}
|
||||
Message: ${errorDetails.message}
|
||||
File: ${errorDetails.file}
|
||||
Line: ${errorDetails.line}
|
||||
Timestamp: ${errorDetails.timestamp}
|
||||
|
||||
Stack Trace:
|
||||
${JSON.stringify(errorDetails.trace, null, 2)}`;
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('Error details copied to clipboard!');
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy: ', err);
|
||||
alert('Failed to copy error details');
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-expand first section
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
toggleSection('details');
|
||||
toggleSection('trace');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user