Compare commits
10
Commits
v1.12
...
7bc8d04df3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bc8d04df3 | ||
|
|
e7d5d1b567 | ||
|
|
614f21b240 | ||
|
|
1bca442f55 | ||
|
|
aac6d5b80a | ||
|
|
48298ef1f7 | ||
|
|
d4a1e9c8d7 | ||
|
|
f9ac567bfe | ||
|
|
be7c5da071 | ||
|
|
fec0715c40 |
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSTORM_META {
|
||||
registerArgumentsSet(
|
||||
'router_actions',
|
||||
\App\Controller\SiteController::class . '@globSetting'
|
||||
);
|
||||
|
||||
expectedArguments(\Kiri\Router\Router::get(), 1, argumentsSet('router_actions'));
|
||||
expectedArguments(\Kiri\Router\Router::post(), 1, argumentsSet('router_actions'));
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
namespace Kiri\Router\Annotate;
|
||||
|
||||
use Kiri\Di\Interface\InjectMethodInterface;
|
||||
use Kiri\Router\Defer\DeferRegistry;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
class Defer implements InjectMethodInterface
|
||||
@@ -18,11 +19,6 @@ class Defer implements InjectMethodInterface
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return void
|
||||
*/
|
||||
public function dispatch(string $class, string $method): void
|
||||
{
|
||||
DeferRegistry::add($class, $method, $this);
|
||||
|
||||
+39
-4
@@ -38,7 +38,7 @@ class File
|
||||
4 => 'No file was uploaded.',
|
||||
6 => 'Missing a temporary folder.',
|
||||
7 => 'Failed to write file to disk.',
|
||||
8 => 'A PHP extension stopped the file upload.'
|
||||
8 => 'A PHP extension stopped the file upload.',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -65,14 +65,19 @@ class File
|
||||
*/
|
||||
public function rename(): string
|
||||
{
|
||||
if (!empty($this->newName)) return $this->newName;
|
||||
if (!empty($this->newName))
|
||||
return $this->newName;
|
||||
if (!file_exists($this->getTmpPath())) {
|
||||
throw new Exception('(' . $this->name . ')Failed to open stream: No such file or directory');
|
||||
}
|
||||
|
||||
$hash = md5_file($this->getTmpPath());
|
||||
|
||||
$later = '.' . exif_imagetype($this->getTmpPath());
|
||||
$later = match ($this->type) {
|
||||
'image/jpeg', 'image/jpg' => '.jpeg',
|
||||
'image/gif' => '.gif',
|
||||
'image/png' => '.png',
|
||||
default => '.' . $this->exif_imageType(),
|
||||
};
|
||||
|
||||
$match = '/(\w{12})(\w{5})(\w{9})(\w{6})/';
|
||||
$tmp = preg_replace($match, '$1-$2-$3-$4', $hash);
|
||||
@@ -80,6 +85,36 @@ class File
|
||||
return $this->name = strtoupper($tmp) . $later;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
* @throws Exception
|
||||
*/
|
||||
private function exif_imageType(): ?int
|
||||
{
|
||||
return match (\exif_imagetype($this->tmp_name)) {
|
||||
IMAGETYPE_GIF => '.gif',
|
||||
IMAGETYPE_JPEG => '.jpg',
|
||||
IMAGETYPE_PNG => '.png',
|
||||
IMAGETYPE_SWF => '.swf',
|
||||
IMAGETYPE_PSD => '.psd',
|
||||
IMAGETYPE_BMP => '.bmp',
|
||||
IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM => '.tiff',
|
||||
IMAGETYPE_JPC => '.jpc',
|
||||
IMAGETYPE_JP2 => '.jp2',
|
||||
IMAGETYPE_JPX => '.jpx',
|
||||
IMAGETYPE_JB2 => '.jb2',
|
||||
IMAGETYPE_SWC => '.swc',
|
||||
IMAGETYPE_IFF => '.iff',
|
||||
IMAGETYPE_WBMP => '.wbmp',
|
||||
IMAGETYPE_XBM => '.xbm',
|
||||
IMAGETYPE_ICO => '.ico',
|
||||
IMAGETYPE_WEBP => '.webp',
|
||||
IMAGETYPE_AVIF => '.avif',
|
||||
default => throw new Exception('未知的图片类型'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Defer;
|
||||
|
||||
use Kiri;
|
||||
use Kiri\Router\Annotate\Defer;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use ReflectionClass;
|
||||
use Swoole\Coroutine;
|
||||
|
||||
/**
|
||||
* Defer 回调执行器 — 统一处理协程安全的上下文注入与异步执行
|
||||
*/
|
||||
class DeferExecutor
|
||||
{
|
||||
|
||||
/**
|
||||
* 执行一批 Defer 回调
|
||||
*
|
||||
* @param Defer[] $defers
|
||||
*/
|
||||
public static function run(array $defers): void
|
||||
{
|
||||
if (empty($defers)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = self::captureRequest();
|
||||
$response = self::captureResponse();
|
||||
|
||||
if (Coroutine::getCid() <= 0) {
|
||||
self::executeSync($defers, $request, $response);
|
||||
return;
|
||||
}
|
||||
|
||||
Coroutine::create(function () use ($defers, $request, $response) {
|
||||
self::executeSync($defers, $request, $response);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 为实例注入 request/response 上下文
|
||||
*/
|
||||
public static function inject(object $instance): object
|
||||
{
|
||||
try {
|
||||
$request = self::captureRequest();
|
||||
if ($request !== null) {
|
||||
self::setProperty($instance, 'request', $request);
|
||||
}
|
||||
|
||||
$response = self::captureResponse();
|
||||
if ($response !== null) {
|
||||
self::setProperty($instance, 'response', $response);
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
|
||||
private static function executeSync(
|
||||
array $defers,
|
||||
?ServerRequestInterface $request,
|
||||
?ResponseInterface $response
|
||||
): void {
|
||||
foreach ($defers as $defer) {
|
||||
try {
|
||||
self::invokeDefer($defer, $request, $response);
|
||||
} catch (\Throwable $throwable) {
|
||||
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static function invokeDefer(
|
||||
Defer $defer,
|
||||
?ServerRequestInterface $request,
|
||||
?ResponseInterface $response
|
||||
): void {
|
||||
$callback = $defer->callback;
|
||||
$params = $defer->params;
|
||||
|
||||
if (is_array($callback)) {
|
||||
[$class, $method] = $callback;
|
||||
$instance = self::resolveInstance($class, $request, $response);
|
||||
call_user_func([$instance, $method], ...$params);
|
||||
} else {
|
||||
$instance = self::resolveInstance($callback, $request, $response);
|
||||
call_user_func([$instance, '__invoke'], ...$params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static function resolveInstance(
|
||||
string $class,
|
||||
?ServerRequestInterface $request,
|
||||
?ResponseInterface $response
|
||||
): object {
|
||||
$instance = Kiri::getDi()->get($class);
|
||||
|
||||
if ($instance instanceof DeferHandler) {
|
||||
if ($request !== null) {
|
||||
$instance->request = $request;
|
||||
}
|
||||
if ($response !== null) {
|
||||
$instance->response = $response;
|
||||
}
|
||||
return $instance;
|
||||
}
|
||||
|
||||
if ($request !== null) {
|
||||
self::setProperty($instance, 'request', $request);
|
||||
}
|
||||
if ($response !== null) {
|
||||
self::setProperty($instance, 'response', $response);
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
|
||||
private static function setProperty(object $instance, string $name, mixed $value): void
|
||||
{
|
||||
try {
|
||||
$reflect = new ReflectionClass($instance);
|
||||
if (!$reflect->hasProperty($name)) return;
|
||||
$prop = $reflect->getProperty($name);
|
||||
if ($prop->isStatic()) return;
|
||||
$prop->setAccessible(true);
|
||||
$prop->setValue($instance, $value);
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static function captureRequest(): ?ServerRequestInterface
|
||||
{
|
||||
try {
|
||||
if (function_exists('request')) return \request();
|
||||
} catch (\Throwable) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static function captureResponse(): ?ResponseInterface
|
||||
{
|
||||
try {
|
||||
if (function_exists('response')) return \response();
|
||||
} catch (\Throwable) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Defer;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Defer 回调基类 — 提供 request/response 上下文属性
|
||||
*
|
||||
* 所有需要在 #[Defer] 回调中访问请求上下文的类应继承此类。
|
||||
* DeferExecutor 会自动将父协程的 request/response 注入到这两个属性。
|
||||
*/
|
||||
abstract class DeferHandler
|
||||
{
|
||||
|
||||
/** @var ServerRequestInterface 当前请求上下文 (DeferExecutor 自动注入) */
|
||||
public ServerRequestInterface $request;
|
||||
|
||||
/** @var ResponseInterface 当前响应上下文 (DeferExecutor 自动注入) */
|
||||
public ResponseInterface $response;
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Annotate;
|
||||
namespace Kiri\Router\Defer;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Stmt;
|
||||
@@ -18,9 +18,6 @@ class DeferProxyGenerator
|
||||
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @param array $construct
|
||||
* @return object
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public static function create(string $className, array $construct): object
|
||||
@@ -59,8 +56,6 @@ class DeferProxyGenerator
|
||||
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @return string
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
private static function generate(string $className): string
|
||||
@@ -70,32 +65,20 @@ class DeferProxyGenerator
|
||||
$stmts = [];
|
||||
|
||||
foreach ($methods as $methodName => $defers) {
|
||||
if (!$reflect->hasMethod($methodName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$reflect->hasMethod($methodName)) continue;
|
||||
$method = $reflect->getMethod($methodName);
|
||||
if ($method->isPrivate() || $method->isStatic() || $method->isFinal() || $method->isConstructor() || $method->isDestructor()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($method->isPrivate() || $method->isStatic() || $method->isFinal() || $method->isConstructor() || $method->isDestructor()) continue;
|
||||
$stmts[] = self::buildMethod($method);
|
||||
}
|
||||
|
||||
if (empty($stmts)) {
|
||||
return '';
|
||||
}
|
||||
if (empty($stmts)) return '';
|
||||
|
||||
$classNode = new Stmt\Class_(
|
||||
new Name($className . '__DeferProxy'),
|
||||
[
|
||||
'extends' => new Name\FullyQualified($className),
|
||||
'stmts' => $stmts,
|
||||
]
|
||||
['extends' => new Name\FullyQualified($className), 'stmts' => $stmts]
|
||||
);
|
||||
|
||||
$namespace = $reflect->getNamespaceName();
|
||||
|
||||
$namespaceNode = new Stmt\Namespace_(
|
||||
$namespace !== '' ? new Name($namespace) : null,
|
||||
[$classNode]
|
||||
@@ -106,10 +89,6 @@ class DeferProxyGenerator
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param ReflectionMethod $method
|
||||
* @return Stmt\ClassMethod
|
||||
*/
|
||||
private static function buildMethod(ReflectionMethod $method): Stmt\ClassMethod
|
||||
{
|
||||
$methodName = $method->getName();
|
||||
@@ -130,16 +109,12 @@ class DeferProxyGenerator
|
||||
|
||||
$var = new Expr\Variable($param->getName());
|
||||
|
||||
$params[] = new Node\Param(
|
||||
$var,
|
||||
$default,
|
||||
$type,
|
||||
$params[] = new Node\Param($var, $default, $type,
|
||||
byRef: $param->isPassedByReference(),
|
||||
variadic: $param->isVariadic()
|
||||
);
|
||||
|
||||
$args[] = new Node\Arg(
|
||||
$var,
|
||||
$args[] = new Node\Arg($var,
|
||||
byRef: $param->isPassedByReference(),
|
||||
unpack: $param->isVariadic()
|
||||
);
|
||||
@@ -151,19 +126,12 @@ class DeferProxyGenerator
|
||||
$returnType = new Name($refReturnType->getName());
|
||||
}
|
||||
|
||||
$parentCall = new Expr\StaticCall(
|
||||
new Name('parent'),
|
||||
$methodName,
|
||||
$args
|
||||
);
|
||||
|
||||
$stmts = [
|
||||
new Stmt\Expression(new Expr\Assign(new Expr\Variable('result'), $parentCall)),
|
||||
new Stmt\Expression(new Expr\Assign(new Expr\Variable('result'),
|
||||
new Expr\StaticCall(new Name('parent'), $methodName, $args))),
|
||||
new Stmt\Expression(
|
||||
new Expr\StaticCall(
|
||||
new Name\FullyQualified(DeferRegistry::class),
|
||||
'execute',
|
||||
[
|
||||
new Name\FullyQualified(DeferRegistry::class), 'execute', [
|
||||
new Node\Arg(new Expr\ClassConstFetch(new Name\FullyQualified($method->getDeclaringClass()->getName()), 'class')),
|
||||
new Node\Arg(new Node\Scalar\String_($methodName)),
|
||||
]
|
||||
@@ -172,66 +140,49 @@ class DeferProxyGenerator
|
||||
new Stmt\Return_(new Expr\Variable('result')),
|
||||
];
|
||||
|
||||
return new Stmt\ClassMethod(
|
||||
$methodName,
|
||||
[
|
||||
return new Stmt\ClassMethod($methodName, [
|
||||
'flags' => $method->isPublic() ? Stmt\Class_::MODIFIER_PUBLIC : Stmt\Class_::MODIFIER_PROTECTED,
|
||||
'params' => $params,
|
||||
'returnType' => $returnType,
|
||||
'stmts' => $stmts,
|
||||
]
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \ReflectionParameter $param
|
||||
* @return Node\Expr
|
||||
*/
|
||||
private static function buildDefaultValue(\ReflectionParameter $param): Node\Expr
|
||||
{
|
||||
if (!$param->isDefaultValueAvailable()) {
|
||||
return new Expr\ConstFetch(new Name('null'));
|
||||
}
|
||||
|
||||
$value = $param->getDefaultValue();
|
||||
|
||||
return match (true) {
|
||||
is_bool($value) => new Expr\ConstFetch(new Name($value ? 'true' : 'false')),
|
||||
is_int($value) => new Node\Scalar\LNumber($value),
|
||||
is_float($value) => new Node\Scalar\DNumber($value),
|
||||
is_string($value)=> new Node\Scalar\String_($value),
|
||||
is_array($value) => new Expr\Array_(
|
||||
array_map(fn($k, $v) => new Expr\ArrayItem(
|
||||
is_string($value) => new Node\Scalar\String_($value),
|
||||
is_array($value) => new Expr\Array_(array_map(
|
||||
fn($k, $v) => new Expr\ArrayItem(
|
||||
self::buildDefaultValueFromScalar($v),
|
||||
is_string($k) ? new Node\Scalar\String_($k) : null
|
||||
), array_keys($value), $value)
|
||||
),
|
||||
), array_keys($value), $value
|
||||
)),
|
||||
default => new Expr\ConstFetch(new Name('null')),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @return Node\Expr
|
||||
*/
|
||||
private static function buildDefaultValueFromScalar(mixed $value): Node\Expr
|
||||
{
|
||||
return match (true) {
|
||||
is_bool($value) => new Expr\ConstFetch(new Name($value ? 'true' : 'false')),
|
||||
is_int($value) => new Node\Scalar\LNumber($value),
|
||||
is_float($value) => new Node\Scalar\DNumber($value),
|
||||
is_string($value)=> new Node\Scalar\String_($value),
|
||||
is_string($value) => new Node\Scalar\String_($value),
|
||||
default => new Expr\ConstFetch(new Name('null')),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
* @return string|null
|
||||
*/
|
||||
private static function getCacheFile(string $className): ?string
|
||||
{
|
||||
if (self::$cacheDir === null) {
|
||||
@@ -241,7 +192,6 @@ class DeferProxyGenerator
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$cacheDir . str_replace('\\', '_', $className) . '__DeferProxy.php';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kiri\Router\Annotate;
|
||||
namespace Kiri\Router\Defer;
|
||||
|
||||
use Kiri\Router\Annotate\Defer;
|
||||
|
||||
class DeferRegistry
|
||||
{
|
||||
@@ -12,12 +14,6 @@ class DeferRegistry
|
||||
private static array $registry = [];
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @param Defer $defer
|
||||
* @return void
|
||||
*/
|
||||
public static function add(string $class, string $method, Defer $defer): void
|
||||
{
|
||||
$key = self::key($class, $method);
|
||||
@@ -26,8 +22,6 @@ class DeferRegistry
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return Defer[]
|
||||
*/
|
||||
public static function get(string $class, string $method): array
|
||||
@@ -36,10 +30,6 @@ class DeferRegistry
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @return bool
|
||||
*/
|
||||
public static function hasAny(string $class): bool
|
||||
{
|
||||
$prefix = $class . '::';
|
||||
@@ -53,7 +43,6 @@ class DeferRegistry
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @return array<string, Defer[]> method => Defer[]
|
||||
*/
|
||||
public static function getAll(string $class): array
|
||||
@@ -71,9 +60,7 @@ class DeferRegistry
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return void
|
||||
* 异步执行 Defer 回调 — 委托 DeferExecutor 处理协程安全与上下文注入
|
||||
*/
|
||||
public static function execute(string $class, string $method): void
|
||||
{
|
||||
@@ -85,30 +72,12 @@ class DeferRegistry
|
||||
$defers = self::$registry[$key];
|
||||
unset(self::$registry[$key]);
|
||||
|
||||
foreach ($defers as $defer) {
|
||||
try {
|
||||
$callback = $defer->callback;
|
||||
$params = $defer->params;
|
||||
|
||||
if (is_array($callback)) {
|
||||
[$cbClass, $cbMethod] = $callback;
|
||||
$instance = \Kiri::getDi()->get($cbClass);
|
||||
call_user_func([$instance, $cbMethod], ...$params);
|
||||
} else {
|
||||
$instance = \Kiri::getDi()->get($callback);
|
||||
call_user_func([$instance, '__invoke'], ...$params);
|
||||
}
|
||||
} catch (\Throwable $throwable) {
|
||||
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
|
||||
}
|
||||
}
|
||||
DeferExecutor::run($defers);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 移除指定类的所有 Defer 注册,用于热重载时清理失效类
|
||||
* @param string $class
|
||||
* @return void
|
||||
* 移除指定类的所有 Defer 注册
|
||||
*/
|
||||
public static function removeClass(string $class): void
|
||||
{
|
||||
@@ -122,7 +91,6 @@ class DeferRegistry
|
||||
|
||||
|
||||
/**
|
||||
* 获取注册表统计信息,用于内存监控
|
||||
* @return array{totalKeys: int, totalDefer: int}
|
||||
*/
|
||||
public static function getStats(): array
|
||||
@@ -138,20 +106,12 @@ class DeferRegistry
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public static function clear(): void
|
||||
{
|
||||
self::$registry = [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return string
|
||||
*/
|
||||
private static function key(string $class, string $method): string
|
||||
{
|
||||
return $class . '::' . $method;
|
||||
+9
-17
@@ -6,6 +6,7 @@ namespace Kiri\Router;
|
||||
use Closure;
|
||||
use Kiri;
|
||||
use Kiri\Router\Annotate\Defer;
|
||||
use Kiri\Router\Defer\DeferExecutor;
|
||||
use Kiri\Router\Format\IFormat;
|
||||
use Kiri\Router\Format\MixedFormat;
|
||||
use Kiri\Router\Format\NoBody;
|
||||
@@ -196,27 +197,18 @@ class Handler implements RequestHandlerInterface
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* 异步执行 Defer 回调 — 委托 DeferExecutor 处理协程安全与上下文注入
|
||||
*/
|
||||
private function executeDeferred(): void
|
||||
{
|
||||
foreach ($this->deferred as $defer) {
|
||||
try {
|
||||
$callback = $defer->callback;
|
||||
$params = $defer->params;
|
||||
if (empty($this->deferred)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_array($callback)) {
|
||||
[$class, $method] = $callback;
|
||||
$instance = Kiri::getDi()->get($class);
|
||||
call_user_func([$instance, $method], ...$params);
|
||||
} else {
|
||||
$instance = Kiri::getDi()->get($callback);
|
||||
call_user_func([$instance, '__invoke'], ...$params);
|
||||
}
|
||||
} catch (\Throwable $throwable) {
|
||||
\Kiri::getLogger()->error('Defer callback failed: ' . $throwable->getMessage());
|
||||
}
|
||||
}
|
||||
$defers = $this->deferred;
|
||||
$this->deferred = [];
|
||||
|
||||
DeferExecutor::run($defers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+101
-13
@@ -8,21 +8,78 @@ class RouteArtifactState
|
||||
{
|
||||
public function store(string $type, array $artifact): void
|
||||
{
|
||||
$previous = $this->loadPayload($type);
|
||||
$payload = [
|
||||
'timestamp' => time(),
|
||||
'type' => $type,
|
||||
'artifact' => $artifact,
|
||||
'build' => is_array($previous['build'] ?? null) ? $previous['build'] : null,
|
||||
];
|
||||
|
||||
$directory = dirname($this->getFilePath($type));
|
||||
if (!is_dir($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($this->getFilePath($type), json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
$this->writePayload($type, $payload);
|
||||
}
|
||||
|
||||
public function load(string $type): array
|
||||
{
|
||||
$data = $this->loadPayload($type);
|
||||
return is_array($data['artifact'] ?? null) ? $data['artifact'] : [];
|
||||
}
|
||||
|
||||
public function markBuild(string $type, array $changedFiles): void
|
||||
{
|
||||
$data = $this->loadPayload($type);
|
||||
if (!is_array($data['artifact'] ?? null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data['build'] = [
|
||||
'generation' => time(),
|
||||
'timestamp' => time(),
|
||||
'changed_files' => $this->normalizeFiles($changedFiles),
|
||||
];
|
||||
|
||||
$this->writePayload($type, $data);
|
||||
}
|
||||
|
||||
public function clearBuild(string $type): void
|
||||
{
|
||||
$data = $this->loadPayload($type);
|
||||
if (empty($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data['build'] = null;
|
||||
$this->writePayload($type, $data);
|
||||
}
|
||||
public function coversChangedFiles(string $type, array $changedFiles): bool
|
||||
{
|
||||
if (empty($changedFiles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = $this->loadPayload($type);
|
||||
$build = is_array($data['build'] ?? null) ? $data['build'] : [];
|
||||
$builtFiles = is_array($build['changed_files'] ?? null) ? $build['changed_files'] : [];
|
||||
if (empty($builtFiles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$built = array_fill_keys($this->normalizeFiles($builtFiles), true);
|
||||
foreach ($this->normalizeFiles($changedFiles) as $file) {
|
||||
if (!isset($built[$file])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function has(string $type): bool
|
||||
{
|
||||
return file_exists($this->getFilePath($type));
|
||||
}
|
||||
|
||||
private function loadPayload(string $type): array
|
||||
{
|
||||
$file = $this->getFilePath($type);
|
||||
if (!file_exists($file)) {
|
||||
@@ -30,16 +87,47 @@ class RouteArtifactState
|
||||
}
|
||||
|
||||
$data = json_decode((string)file_get_contents($file), true);
|
||||
if (!is_array($data)) {
|
||||
return [];
|
||||
return is_array($data) ? $data : [];
|
||||
}
|
||||
|
||||
return is_array($data['artifact'] ?? null) ? $data['artifact'] : [];
|
||||
}
|
||||
|
||||
public function has(string $type): bool
|
||||
private function writePayload(string $type, array $payload): void
|
||||
{
|
||||
return file_exists($this->getFilePath($type));
|
||||
$file = $this->getFilePath($type);
|
||||
$directory = dirname($file);
|
||||
if (!is_dir($directory)) {
|
||||
mkdir($directory, 0755, true);
|
||||
}
|
||||
|
||||
$tmpFile = $file . '.tmp.' . getmypid();
|
||||
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
|
||||
if (file_put_contents($tmpFile, $json, LOCK_EX) === false) {
|
||||
throw new \RuntimeException('Unable to write route artifact: ' . $tmpFile);
|
||||
}
|
||||
|
||||
if (@rename($tmpFile, $file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Windows rename() cannot replace an existing file.
|
||||
if (is_file($file) && @unlink($file) && @rename($tmpFile, $file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@unlink($tmpFile);
|
||||
throw new \RuntimeException('Unable to publish route artifact: ' . $file);
|
||||
}
|
||||
|
||||
private function normalizeFiles(array $files): array
|
||||
{
|
||||
$normalized = [];
|
||||
foreach ($files as $file) {
|
||||
if (!is_string($file) || $file === '') {
|
||||
continue;
|
||||
}
|
||||
$normalized[] = str_replace('\\', '/', realpath($file) ?: $file);
|
||||
}
|
||||
|
||||
return array_values(array_unique($normalized));
|
||||
}
|
||||
|
||||
private function getFilePath(string $type): string
|
||||
|
||||
+35
-15
@@ -214,6 +214,18 @@ class Router
|
||||
|
||||
$changedFiles = $container->get(HotReloadState::class)->consume();
|
||||
|
||||
$artifactState = $container->get(RouteArtifactState::class);
|
||||
if (getenv('KIRI_FILE_BUILD') !== '1' && !empty($changedFiles) && $artifactState->coversChangedFiles(static::$type, $changedFiles)) {
|
||||
$container->get(DataGrip::class)->reset(static::$type);
|
||||
$router = $container->get(DataGrip::class)->get(static::$type);
|
||||
if ($router->importArtifact($artifactState->load(static::$type))) {
|
||||
$this->read_dir_file(APP_PATH . 'routes');
|
||||
$this->reset($container);
|
||||
$coordinator->done();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Worker 首次启动(无变更文件 + Master 已完成扫描):
|
||||
// 重新 include 路由文件(Router::get/post 显式注册) + 基于 Master 扫描清单重建注解路由
|
||||
// 避免 opcache_compile_file,仅用 Reflection 重建路由,内存开销极小
|
||||
@@ -232,21 +244,20 @@ class Router
|
||||
$container->get(DataGrip::class)->reset(static::$type);
|
||||
|
||||
$scanner = $container->get(Kiri\Di\Scanner::class);
|
||||
$artifactState = $container->get(RouteArtifactState::class);
|
||||
$scanConfig = array_merge(
|
||||
config('servers.reload.scan', []),
|
||||
config('site.scanner', [])
|
||||
);
|
||||
$scanner->setConfig($scanConfig);
|
||||
|
||||
$normalizedAppPath = str_replace('\\', '/', APP_PATH . 'app');
|
||||
$normalizedRoutePath = str_replace('\\', '/', APP_PATH . 'routes');
|
||||
$routeChanged = false;
|
||||
$normalizedAppPath = str_replace('\\', '/', realpath(APP_PATH . 'app') ?: APP_PATH . 'app');
|
||||
$normalizedRoutePath = str_replace('\\', '/', realpath(APP_PATH . 'routes') ?: APP_PATH . 'routes');
|
||||
$routeChangedFiles = [];
|
||||
$appChangedFiles = [];
|
||||
|
||||
foreach ($changedFiles as $changedFile) {
|
||||
if (str_starts_with($changedFile, $normalizedRoutePath . '/')) {
|
||||
$routeChanged = true;
|
||||
$routeChangedFiles[] = $changedFile;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -255,23 +266,29 @@ class Router
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($appChangedFiles) && (bool)($scanConfig['expand_dependencies'] ?? false)) {
|
||||
$appChangedFiles = $scanner->expandDependentFiles($appChangedFiles, APP_PATH . 'app/');
|
||||
}
|
||||
|
||||
$usedArtifact = false;
|
||||
if (($scanConfig['cache_enabled'] ?? false) && !$routeChanged && $artifactState->has(static::$type)) {
|
||||
$canUseArtifact = $artifactState->has(static::$type)
|
||||
&& (!empty($changedFiles) || ($scanConfig['cache_enabled'] ?? false));
|
||||
if ($canUseArtifact) {
|
||||
$artifact = $artifactState->load(static::$type);
|
||||
$router = $container->get(DataGrip::class)->get(static::$type);
|
||||
$usedArtifact = $router->importArtifact($artifact, $appChangedFiles);
|
||||
$usedArtifact = $router->importArtifact($artifact, array_merge($appChangedFiles, $routeChangedFiles));
|
||||
}
|
||||
|
||||
if (!$usedArtifact) {
|
||||
// routes 目录中的显式路由文件必须每次重建路由表时重新 include。
|
||||
// route artifact 只加速注解路由,不能替代 routes/*.php 的注册副作用。
|
||||
$this->read_dir_file(APP_PATH . 'routes');
|
||||
}
|
||||
|
||||
if (!$routeChanged && !empty($appChangedFiles) && ($scanConfig['cache_enabled'] ?? false)) {
|
||||
$scanner->scanFiles($appChangedFiles, APP_PATH . 'app/', null, !$usedArtifact);
|
||||
if (!empty($appChangedFiles) && (($scanConfig['cache_enabled'] ?? false) || $usedArtifact)) {
|
||||
$scanner->scanFiles($appChangedFiles, APP_PATH . 'app/', null, true, !$usedArtifact);
|
||||
} elseif (!$usedArtifact) {
|
||||
$scanner->scan(APP_PATH . 'app/');
|
||||
} else {
|
||||
$scanner->scanFiles([], APP_PATH . 'app/', null, false);
|
||||
$scanner->scanFiles([], APP_PATH . 'app/', null, false, false);
|
||||
}
|
||||
$this->reset($container);
|
||||
$artifactState->store(static::$type, $container->get(DataGrip::class)->get(static::$type)->exportArtifact());
|
||||
@@ -462,8 +479,12 @@ class Router
|
||||
private function resolve_file($files): void
|
||||
{
|
||||
try {
|
||||
static::$currentSourceFile = str_replace('\\', '/', realpath($files) ?: $files);
|
||||
include "$files";
|
||||
$file = realpath($files) ?: $files;
|
||||
static::$currentSourceFile = str_replace('\\\\', '/', $file);
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
@opcache_invalidate($file, true);
|
||||
}
|
||||
include $file;
|
||||
} catch (\Throwable $throwable) {
|
||||
\Kiri::getLogger()->json_log($throwable);
|
||||
} finally {
|
||||
@@ -471,7 +492,6 @@ class Router
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function getCurrentSourceFile(): ?string
|
||||
{
|
||||
return static::$currentSourceFile;
|
||||
|
||||
+10
-18
@@ -7,7 +7,7 @@ namespace Kiri\Router;
|
||||
|
||||
use Closure;
|
||||
use Kiri\Router\Annotate\Defer;
|
||||
use Kiri\Router\Annotate\DeferRegistry;
|
||||
use Kiri\Router\Defer\DeferRegistry;
|
||||
use Kiri\Router\Base\NotFoundController;
|
||||
use Kiri\Router\Constrict\RequestMethod;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
@@ -164,7 +164,7 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
||||
$array[] = [
|
||||
'path' => $path,
|
||||
'method' => $method,
|
||||
'handler' => $controller
|
||||
'handler' => $controller,
|
||||
];
|
||||
}
|
||||
return $array;
|
||||
@@ -282,10 +282,6 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
||||
|
||||
public function importArtifact(array $artifact, array $excludeSourceFiles = []): bool
|
||||
{
|
||||
if (($artifact['has_closure_routes'] ?? false) === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entries = $artifact['entries'] ?? null;
|
||||
if (!is_array($entries)) {
|
||||
return false;
|
||||
@@ -311,16 +307,12 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->methods[$requestMethod . '_' . $path] = new RouteEntry(
|
||||
requestMethod: $requestMethod,
|
||||
path: $path,
|
||||
class: $class,
|
||||
method: $method,
|
||||
middlewares: is_array($entry['middlewares'] ?? null) ? $entry['middlewares'] : [],
|
||||
sourceFile: is_string($sourceFile) ? $this->normalizePath($sourceFile) : null,
|
||||
sourceKind: is_string($entry['source_kind'] ?? null) ? $entry['source_kind'] : 'attribute',
|
||||
deferred: is_array($entry['deferred'] ?? null) ? $entry['deferred'] : [],
|
||||
);
|
||||
$middlewares = is_array($entry['middlewares'] ?? null) ? $entry['middlewares'] : [];
|
||||
$sourceFile = is_string($sourceFile) ? $this->normalizePath($sourceFile) : null;
|
||||
$sourceKind = is_string($entry['source_kind'] ?? null) ? $entry['source_kind'] : 'attribute';
|
||||
$deferred = is_array($entry['deferred'] ?? null) ? $entry['deferred'] : [];
|
||||
|
||||
$this->methods[$requestMethod . '_' . $path] = new RouteEntry(requestMethod: $requestMethod, path: $path, class: $class, method: $method, middlewares: $middlewares, sourceFile: $sourceFile, sourceKind: $sourceKind, deferred: $deferred);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -379,8 +371,8 @@ class RouterCollector implements \ArrayAccess, \IteratorAggregate
|
||||
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @param array $response
|
||||
* @param array $middlewares
|
||||
* @return Defer[]
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user