140 Commits

Author SHA1 Message Date
as2252258 7827b8d5b1 eee 2026-06-12 23:57:19 +08:00
as2252258 0ff2cd7a06 eee 2026-04-17 16:47:04 +08:00
as2252258 807b86c4cc eee 2026-04-17 16:45:30 +08:00
as2252258 d23e55391e eee 2026-04-17 16:30:52 +08:00
as2252258 754042b994 11
Signed-off-by: 向林 <as2252258@163.com>
2025-12-31 06:57:29 +00:00
as2252258 8a4f68e984 eee 2025-12-31 01:50:09 +08:00
as2252258 3b7e6f992f eee 2025-12-31 01:10:27 +08:00
as2252258 de0a39d207 eee 2025-12-31 01:09:30 +08:00
as2252258 0110f1947a eee 2025-12-31 01:07:57 +08:00
as2252258 93fc09342a eee 2025-12-31 00:42:12 +08:00
as2252258 e16ceadaa7 eee 2025-12-31 00:19:29 +08:00
dongzhiyong 34685ead22 a 2025-12-23 14:39:55 +08:00
as2252258 c643be8548 eee 2025-12-22 23:14:45 +08:00
as2252258 f9e23c59ef 添加opcache重置
Signed-off-by: 向林 <as2252258@163.com>
2025-12-22 06:57:32 +00:00
as2252258 f89e8106f5 eee 2025-12-18 15:39:42 +08:00
as2252258 2f02c5af12 eee 2025-07-14 15:34:56 +08:00
as2252258 9e954135f6 eee 2025-07-11 15:55:15 +08:00
as2252258 7de790d65f eee 2025-07-11 15:32:33 +08:00
as2252258 855da03137 eee 2025-07-11 15:28:10 +08:00
as2252258 0081a30f22 eee 2025-07-11 15:25:06 +08:00
as2252258 3f2d3b0f04 eee 2025-07-11 15:22:52 +08:00
as2252258 5fa5646024 eee 2025-07-11 15:11:27 +08:00
as2252258 045c9293d5 eee 2025-07-11 11:51:05 +08:00
as2252258 6ae7f5a721 eee 2024-11-21 10:43:16 +08:00
as2252258 9775d16db5 eee 2024-11-06 21:47:21 +08:00
as2252258 03bffb5e5b eee 2024-11-06 21:41:43 +08:00
as2252258 a21b949512 eee 2024-02-09 17:08:07 +08:00
as2252258 3f614ae8cd eee 2023-12-19 17:50:59 +08:00
as2252258 cddf84c76a eee 2023-12-19 16:44:38 +08:00
as2252258 67a83e5712 eee 2023-12-19 16:26:22 +08:00
as2252258 bedeaa63e2 eee 2023-12-18 22:25:54 +08:00
as2252258 052709d27d eee 2023-12-18 22:23:43 +08:00
as2252258 ae8ba9ca4e eee 2023-12-18 22:23:16 +08:00
as2252258 c6e9133566 eee 2023-12-18 21:55:43 +08:00
as2252258 4a168cb4a0 eee 2023-12-18 21:30:40 +08:00
as2252258 de670304b5 eee 2023-12-18 17:18:54 +08:00
as2252258 048f49b27f eee 2023-12-18 17:10:34 +08:00
as2252258 950d23131e eee 2023-12-18 16:56:45 +08:00
as2252258 7d99a73c83 eee 2023-12-18 16:39:21 +08:00
as2252258 e925a78f02 eee 2023-12-18 16:36:20 +08:00
as2252258 dfada33039 eee 2023-12-18 16:32:47 +08:00
as2252258 eeb06cf623 eee 2023-12-18 16:28:00 +08:00
as2252258 04b64bb4b8 eee 2023-12-18 16:20:38 +08:00
as2252258 00c089363f eee 2023-12-12 18:03:08 +08:00
as2252258 1d3c255be8 eee 2023-12-12 15:35:35 +08:00
as2252258 ef3555eca1 eee 2023-12-12 14:06:40 +08:00
as2252258 fe8d41ec41 eee 2023-12-12 10:56:43 +08:00
as2252258 5d8bc6d2cf eee 2023-12-02 11:28:19 +08:00
as2252258 500ebbf7f1 eee 2023-12-01 22:57:25 +08:00
as2252258 7b84c59fe9 eee 2023-12-01 22:43:54 +08:00
as2252258 0b6c62c41c eee 2023-11-30 17:10:37 +08:00
as2252258 f552dc994c eee 2023-11-30 17:02:20 +08:00
as2252258 c01514e516 eee 2023-11-29 14:49:18 +08:00
as2252258 bed67a08bc eee 2023-11-22 09:26:18 +08:00
as2252258 c54057e79c eee 2023-11-22 09:23:59 +08:00
as2252258 1b9c817ceb eee 2023-11-16 23:50:06 +08:00
as2252258 d3cc7e7afe eee 2023-11-16 23:01:58 +08:00
as2252258 ae11af9a7b eee 2023-11-16 21:13:16 +08:00
as2252258 e4e0cc1d77 eee 2023-11-04 00:57:06 +08:00
as2252258 3ce9ee662a eee 2023-10-24 17:22:28 +08:00
as2252258 cab51a97a9 eee 2023-10-17 15:35:55 +08:00
as2252258 13afca9c8f eee 2023-10-17 14:50:45 +08:00
as2252258 dc49f51386 qqq 2023-08-17 16:15:07 +08:00
as2252258 bea41f0579 qqq 2023-08-17 16:09:59 +08:00
as2252258 23206d2253 qqq 2023-08-17 16:05:17 +08:00
as2252258 5ac43532af qqq 2023-08-03 14:09:40 +08:00
as2252258 0a171da047 qqq 2023-08-03 14:08:16 +08:00
as2252258 3611b87817 qqq 2023-08-03 14:06:58 +08:00
as2252258 b55d513f5b qqq 2023-08-03 14:06:28 +08:00
as2252258 d7ab423a0a qqq 2023-08-03 14:02:06 +08:00
as2252258 e1f76a476e qqq 2023-07-31 23:08:59 +08:00
as2252258 a6283ba90f qqq 2023-07-13 09:02:45 +08:00
as2252258 b15a4d233b qqq 2023-07-06 17:57:10 +08:00
as2252258 a5a958c43b qqq 2023-07-06 17:25:46 +08:00
as2252258 ef6672426a qqq 2023-07-06 17:25:33 +08:00
as2252258 3244aed2e0 qqq 2023-07-06 17:21:32 +08:00
as2252258 1b3101f07d qqq 2023-05-26 11:16:44 +08:00
as2252258 2d0ee50758 qqq 2023-05-26 10:16:21 +08:00
as2252258 75bae59d62 qqq 2023-05-25 16:59:17 +08:00
as2252258 f7768bbe40 变更 2023-04-25 16:10:55 +08:00
as2252258 6b2a71c6af 变更 2023-04-25 16:09:20 +08:00
as2252258 764a7f4eb7 变更 2023-04-25 11:51:26 +08:00
as2252258 7b5b21b305 变更 2023-04-24 22:31:08 +08:00
as2252258 5e474fd9f4 变更 2023-04-24 21:47:52 +08:00
as2252258 e3837281fa 变更 2023-04-24 14:22:11 +08:00
as2252258 0b1016089e 变更 2023-04-24 10:54:56 +08:00
as2252258 ea844c3317 变更 2023-04-19 12:35:39 +08:00
as2252258 c58b3080ad 变更 2023-04-18 23:47:30 +08:00
as2252258 dfc87000c0 变更 2023-04-18 10:44:30 +08:00
as2252258 e5f048b778 变更 2023-04-18 10:26:32 +08:00
as2252258 478fe7a859 变更 2023-04-16 23:38:32 +08:00
as2252258 45e0ec0c17 变更 2023-04-16 17:31:07 +08:00
as2252258 edfe966e83 变更 2023-04-16 16:58:34 +08:00
as2252258 9c67fb556f 变更 2023-04-16 16:57:27 +08:00
as2252258 552785c16b 变更 2023-04-16 16:54:38 +08:00
as2252258 9471104c79 变更 2023-04-16 16:52:31 +08:00
as2252258 f8a4270b84 变更 2023-04-16 16:52:16 +08:00
as2252258 4b4bd185f5 变更 2023-04-16 16:51:23 +08:00
as2252258 8cde1f32ac 变更 2023-04-16 16:40:44 +08:00
as2252258 728a2dd560 变更 2023-04-16 13:47:32 +08:00
as2252258 8890d03d2c 变更 2023-04-16 13:36:35 +08:00
as2252258 2877f3faa5 变更 2023-04-16 12:49:55 +08:00
as2252258 e9f3499f2f 变更 2023-04-16 12:23:16 +08:00
as2252258 93f10ade37 变更 2023-04-16 02:20:20 +08:00
as2252258 dc08bcd81f 变更 2023-04-16 02:18:22 +08:00
as2252258 ca7ec312b9 变更 2023-04-16 02:01:26 +08:00
as2252258 efd8ad2f71 变更 2023-04-16 01:45:33 +08:00
as2252258 b8d7e7052f 变更 2023-04-16 00:59:31 +08:00
as2252258 43ce6b3486 变更 2023-04-16 00:15:10 +08:00
as2252258 eebd01bcc5 变更 2023-04-15 23:32:00 +08:00
as2252258 06e2b691bb 变更 2023-04-07 00:04:59 +08:00
as2252258 9a8d2b8bea 变更 2023-04-03 14:18:58 +08:00
as2252258 293092f8d6 变更 2023-04-03 11:17:15 +08:00
as2252258 54230ffe75 变更 2023-04-03 11:10:56 +08:00
as2252258 be23a272ba 变更 2023-04-03 11:10:15 +08:00
as2252258 f153fde08d 变更 2023-04-03 11:08:11 +08:00
as2252258 73754f3bfb 变更 2023-03-30 18:35:19 +08:00
as2252258 e1687ed535 变更 2023-02-06 12:05:44 +08:00
as2252258 f02b540f62 变更 2022-12-13 14:10:21 +08:00
as2252258 d8b5990ee8 变更 2022-12-12 17:31:11 +08:00
as2252258 8a4dcd8475 变更 2022-09-08 11:31:12 +08:00
as2252258 7b67bc1dcf modify plugin name 2022-06-24 15:07:46 +08:00
as2252258 2dac0fa9af modify plugin name 2022-06-16 18:54:15 +08:00
as2252258 c96db92b5b modify plugin name 2022-06-16 18:53:40 +08:00
as2252258 c975458a17 modify plugin name 2022-06-16 17:38:22 +08:00
as2252258 b9b1936611 modify plugin name 2022-03-03 18:00:49 +08:00
as2252258 ce6e38a27e modify plugin name 2022-03-01 16:26:26 +08:00
as2252258 4af17359a7 modify plugin name 2022-02-28 10:20:46 +08:00
as2252258 e7e951ab39 modify plugin name 2022-02-27 18:56:44 +08:00
as2252258 7f40dd8711 modify plugin name 2022-02-27 18:49:36 +08:00
as2252258 d20d3f1b77 modify plugin name 2022-02-27 17:36:34 +08:00
as2252258 2ebf134a83 modify plugin name 2022-02-27 17:22:23 +08:00
as2252258 d3d884b050 modify plugin name 2022-02-27 17:22:09 +08:00
as2252258 b278f17175 modify plugin name 2022-02-23 17:20:25 +08:00
as2252258 1190a0d26c modify plugin name 2022-02-23 17:04:14 +08:00
as2252258 939f2304ee modify plugin name 2022-02-23 16:32:07 +08:00
as2252258 b8993ecc56 modify plugin name 2022-02-23 10:48:38 +08:00
as2252258 0bf824b66e modify plugin name 2022-02-17 18:57:17 +08:00
as2252258 287abbfacf modify plugin name 2022-02-17 18:41:48 +08:00
as2252258 ef274b00fc Revert "改名"
This reverts commit fdf58326
2022-02-03 14:54:38 +08:00
21 changed files with 1892 additions and 774 deletions
+157
View File
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace Kiri\Di;
class ChangeSet
{
/**
* @var array
*/
private array $changedFiles = [];
/**
* @var array
*/
private array $removedFiles = [];
/**
* @var array
*/
private array $changedClasses = [];
/**
* @var array
*/
private array $removedClasses = [];
/**
* @param string $file
* @return void
*/
public function addChangedFile(string $file): void
{
$this->changedFiles[$file] = true;
}
/**
* @param string $file
* @return void
*/
public function addRemovedFile(string $file): void
{
$this->removedFiles[$file] = true;
}
/**
* @param string $class
* @return void
*/
public function addChangedClass(string $class): void
{
$this->changedClasses[$class] = true;
}
/**
* @param string $class
* @return void
*/
public function addRemovedClass(string $class): void
{
$this->removedClasses[$class] = true;
}
/**
* @param ChangeSet $changeSet
* @return $this
*/
public function merge(ChangeSet $changeSet): self
{
foreach ($changeSet->getChangedFiles() as $file) {
$this->addChangedFile($file);
}
foreach ($changeSet->getRemovedFiles() as $file) {
$this->addRemovedFile($file);
}
foreach ($changeSet->getChangedClasses() as $class) {
$this->addChangedClass($class);
}
foreach ($changeSet->getRemovedClasses() as $class) {
$this->addRemovedClass($class);
}
return $this;
}
/**
* @return array
*/
public function getChangedFiles(): array
{
return array_keys($this->changedFiles);
}
/**
* @return array
*/
public function getRemovedFiles(): array
{
return array_keys($this->removedFiles);
}
/**
* @return array
*/
public function getChangedClasses(): array
{
return array_keys($this->changedClasses);
}
/**
* @return array
*/
public function getRemovedClasses(): array
{
return array_keys($this->removedClasses);
}
/**
* @return bool
*/
public function hasChanges(): bool
{
return $this->changedFiles !== [] || $this->removedFiles !== [] || $this->changedClasses !== [] || $this->removedClasses !== [];
}
/**
* @return array
*/
public function toArray(): array
{
return [
'changed_files' => $this->getChangedFiles(),
'removed_files' => $this->getRemovedFiles(),
'changed_classes' => $this->getChangedClasses(),
'removed_classes' => $this->getRemovedClasses(),
];
}
}
+427 -382
View File
@@ -9,446 +9,491 @@ declare(strict_types=1);
namespace Kiri\Di;
use Closure;
use Exception;
use Kiri;
use Kiri\Abstracts\Logger;
use Kiri\Annotation\Inject;
use Kiri\Di\Interface\InjectTargetInterface;
use Kiri\Router\Interface\ValidatorInterface;
use Kiri\Server\Task\OnTaskFinish;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionProperty;
use ReflectionParameter;
/**
* Class Container
* @package Kiri\Di
*/
class Container implements ContainerInterface
{
/**
* @var array
*
* instance class by className
*/
private array $_singletons = [];
/**
* @var ReflectionMethod[]
*
* class new instance construct parameter
*/
private array $_constructs = [];
/**
* @var array
*
* implements \ReflectClass
*/
private array $_reflection = [];
/**
* @var array
*
* instance class by className
*/
private array $_singletons = [];
/** @var array */
private array $_parameters = [];
/**
* @var array
*
* implements \ReflectClass
*/
private array $_reflection = [];
/** @var array|string[] */
private array $_interfaces = [
LoggerInterface::class => Logger::class
];
/**
* @var array
*/
private array $_parameters = [];
/**
* @param string $id
* @return mixed
* @throws
*/
public function get(string $id): mixed
{
if ($id == ContainerInterface::class) {
return $this;
}
return $this->make($id, [], []);
}
/**
* @var array
*/
private array $_interfaces = [];
/**
* @param $class
* @param array $constrict
* @param array $config
* @return mixed
* @throws
*/
public function make($class, array $constrict = [], array $config = []): mixed
{
if ($class == ContainerInterface::class) {
return $this;
}
if ($this->isInterface($class)) {
$class = $this->_interfaces[$class];
}
if (!isset($this->_singletons[$class])) {
$this->_singletons[$class] = $this->resolve($class, $constrict, $config);
}
return $this->_singletons[$class];
}
/**
* @var Container|null
*/
private static ?Container $container = null;
/**
* @param string $interface
* @param string $class
*/
public function mapping(string $interface, string $class)
{
$this->_interfaces[$interface] = $class;
}
/**
* Construct \ContainerInterface
*/
private function __construct()
{
$this->_singletons[ContainerInterface::class] = $this;
}
/**
* @param $class
* @return bool
*/
public function isInterface($class): bool
{
$reflect = $this->getReflect($class);
if ($reflect->isInterface()) {
return true;
}
return false;
}
/**
* @return static
*/
public static function instance(): static
{
if (static::$container === null) {
static::$container = new Container();
}
return static::$container;
}
/**
* @param string $interface
* @param $object
*/
public function setBindings(string $interface, $object)
{
if (is_string($object)) {
$this->_interfaces[$interface] = $object;
/**
* @param string $id
* @return mixed
* @throws
*/
public function get(string $id): object
{
if (isset($this->_singletons[$id]))
return $this->_singletons[$id];
if (isset($this->_interfaces[$id])) {
return $this->_singletons[$id] = $this->make($this->_interfaces[$id]);
} else {
return $this->_singletons[$id] = $this->make($id);
}
}
/**
* @param string $id
* @return object
* @throws
*/
public function parse(string $id): object
{
if (!isset($this->_singletons[$id])) {
return $this->make($id);
}
return $this->_singletons[$id];
}
/**
* @param string $interface
* @param string $class
* @return void
*/
public function set(string $interface, string $class): void
{
$this->_interfaces[$interface] = $class;
}
/**
* @param string $interface
* @param object $object
* @return object
*/
public function bind(string $interface, object $object): object
{
$this->_singletons[$interface] = $object;
return $object;
}
/**
* @param string $className
* @return ReflectionClass
* @throws
*/
public function getReflectionClass(string $className): ReflectionClass
{
if (!isset($this->_reflection[$className])) {
$this->_reflection[$className] = new ReflectionClass($className);
}
return $this->_reflection[$className];
}
public function forgetReflection(string $className): void
{
unset($this->_reflection[$className]);
}
public function forgetMethodParams(string $className, ?string $method = null): void
{
if (!isset($this->_parameters[$className])) {
return;
}
if ($method === null) {
unset($this->_parameters[$className]);
return;
}
unset($this->_parameters[$className][$method]);
if ($this->_parameters[$className] === []) {
unset($this->_parameters[$className]);
}
}
public function forgetClass(string $className): void
{
unset($this->_singletons[$className]);
$this->forgetReflection($className);
$this->forgetMethodParams($className);
}
public function forgetNamespace(string $prefix): void
{
foreach (array_keys($this->_singletons) as $className) {
if ($className !== ContainerInterface::class && str_starts_with($className, $prefix)) {
unset($this->_singletons[$className]);
}
}
foreach (array_keys($this->_reflection) as $className) {
if (str_starts_with($className, $prefix)) {
unset($this->_reflection[$className]);
}
}
foreach (array_keys($this->_parameters) as $className) {
if (str_starts_with($className, $prefix)) {
unset($this->_parameters[$className]);
}
}
}
/**
* @param string $className
* @param array $construct
* @param array $config
* @return object|null
* @throws
*/
public function make(string $className, array $construct = [], array $config = []): ?object
{
$reflect = $this->getReflectionClass($className);
if (!$reflect->isInstantiable()) {
throw new ReflectionException('Class ' . $className . ' cannot be instantiated');
}
if (empty($construct) && ($handler = $reflect->getConstructor()) !== null) {
$construct = $this->getMethodParams($handler);
}
$isController = class_exists(\Kiri\Router\Base\Controller::class) && $reflect->isSubclassOf(\Kiri\Router\Base\Controller::class);
$needsProxy = !$isController && class_exists(\Kiri\Router\Annotate\DeferRegistry::class) && \Kiri\Router\Annotate\DeferRegistry::hasAny($className);
if ($needsProxy) {
$newInstance = \Kiri\Router\Annotate\DeferProxyGenerator::create($className, $construct);
} else {
$className = get_class($object);
$this->_interfaces[$interface] = $className;
$this->_singletons[$className] = $object;
}
}
/**
* @param $class
* @param array $constrict
* @param array $config
* @return object
* @throws
*/
public function create($class, array $constrict = [], array $config = []): object
{
return $this->resolve($class, $constrict, $config);
}
/**
* @param $class
* @param $constrict
* @param $config
*
* @return object
* @throws Exception
*/
private function resolve($class, $constrict, $config): object
{
$reflect = $this->resolveDependencies($class);
if (!$reflect->isInstantiable()) {
throw new ReflectionException('Class ' . $class . ' cannot be instantiated');
$newInstance = $reflect->newInstanceArgs($construct);
}
$object = $this->newInstance($reflect, $constrict);
$this->propertyInject($reflect, $object);
return $this->onAfterInit($object, $config);
}
return $this->runInit($reflect, static::configure($newInstance, $config));
}
/**
* @param ReflectionClass $reflect
* @param $dependencies
* @return object
* @throws ReflectionException
*/
private function newInstance(ReflectionClass $reflect, $dependencies): object
{
if (!isset($this->_constructs[$reflect->getName()])) {
return $reflect->newInstance();
/**
* @param ReflectionClass $reflect
* @param array $construct
* @param array $config
* @return object|null
* @throws ReflectionException
*/
public function makeReflection(ReflectionClass $reflect, array $construct = [], array $config = []): ?object
{
if (isset($this->_singletons[$reflect->getName()])) {
return $this->_singletons[$reflect->getName()];
}
if (!$reflect->isInstantiable()) {
throw new ReflectionException('Class ' . $reflect->getName() . ' cannot be instantiated');
}
if (empty($construct) && ($handler = $reflect->getConstructor()) !== null) {
$construct = $this->getMethodParams($handler);
}
$isController = class_exists(\Kiri\Router\Base\Controller::class) && $reflect->isSubclassOf(\Kiri\Router\Base\Controller::class);
$needsProxy = !$isController && class_exists(\Kiri\Router\Annotate\DeferRegistry::class) && \Kiri\Router\Annotate\DeferRegistry::hasAny($reflect->getName());
if ($needsProxy) {
$newInstance = \Kiri\Router\Annotate\DeferProxyGenerator::create($reflect->getName(), $construct);
} else {
$newInstance = $reflect->newInstanceArgs($construct);
}
$construct = $this->_constructs[$reflect->getName()];
if ($construct->getNumberOfParameters() < 1) {
return $reflect->newInstance();
}
$parameters = $this->mergeParam($this->resolveMethodParameters($construct), $dependencies);
return $reflect->newInstanceArgs($parameters);
}
return $this->runInit($reflect, static::configure($newInstance, $config));
}
/**
* @param ReflectionClass $reflect
* @param $object
* @return mixed
* @throws Exception
*/
public function propertyInject(ReflectionClass $reflect, $object): mixed
{
foreach (NoteManager::getPropertyAnnotation($reflect) as $property => $inject) {
/** @var Inject $inject */
$inject->execute($object, $property);
}
return $object;
}
/**
* @param ReflectionClass $reflect
* @param object $object
* @return void
*/
protected function injectClassTarget(ReflectionClass $reflect, object $object): void
{
$this->resolveProperties($reflect, $object);
$attributes = $reflect->getAttributes();
foreach ($attributes as $attribute) {
if (class_exists($attribute->getName())) {
$instance = $attribute->newInstance();
if ($instance instanceof InjectTargetInterface) {
$instance->dispatch($object::class);
}
}
}
}
/**
* @param $className
* @param $method
* @return array
*/
public function getMethodAttribute($className, $method = null): array
{
$methods = NoteManager::getMethodAnnotation($this->getReflect($className));
if (!empty($method)) {
return $methods[$method] ?? [];
}
return $methods;
}
/**
* @param ReflectionClass $reflect
* @param object $object
* @return object
*/
protected function runInit(ReflectionClass $reflect, object $object): object
{
$this->injectClassTarget($reflect, $object);
if ($reflect->getName() === 'Symfony\Component\Console\Application') {
return $object;
}
if (method_exists($object, 'init')) {
call_user_func([$object, 'init']);
}
return $object;
}
/**
* @param string $class
* @param string|null $property
* @return ReflectionProperty|ReflectionProperty[]|null
*/
public function getClassReflectionProperty(string $class, string $property = null): ReflectionProperty|null|array
{
$lists = NoteManager::getProperty($this->getReflect($class));
if (empty($lists)) {
return null;
}
if (!empty($property)) {
return $lists[$property] ?? null;
}
return $lists;
}
/**
* @param ReflectionClass $reflectionClass
* @param object $class
* @return void
*/
public function resolveProperties(ReflectionClass $reflectionClass, object $class): void
{
$properties = $reflectionClass->getProperties();
foreach ($properties as $property) {
$propertyAttributes = $property->getAttributes();
foreach ($propertyAttributes as $attribute) {
if (!class_exists($attribute->getName()) || $this->isValidatorInterface($attribute)) {
continue;
}
$instance = $attribute->newInstance();
$instance->dispatch($class, $property->getName());
}
}
}
/**
* @param $object
* @param $config
* @return mixed
*/
private function onAfterInit($object, $config): mixed
{
Kiri::configure($object, $config);
if (method_exists($object, 'init') && is_callable([$object, 'init'])) {
call_user_func([$object, 'init']);
}
return $object;
}
/**
* @param ReflectionAttribute $attribute
* @return bool
*/
protected function isValidatorInterface(ReflectionAttribute $attribute): bool
{
return in_array(ValidatorInterface::class, class_implements($attribute->getName()));
}
/**
* @param string $className
* @param string $method
* @return ReflectionMethod
* @throws
*/
public function getMethod(string $className, string $method): ReflectionMethod
{
$reflection = $this->getReflectionClass($className);
return $reflection->getMethod($method);
}
/**
* @param $class
* @return ReflectionClass
*/
private function resolveDependencies($class): ReflectionClass
{
if (isset($this->_reflection[$class])) {
return $this->_reflection[$class];
}
$reflect = new ReflectionClass($class);
if ($reflect->isAbstract() || $reflect->isTrait() || $reflect->isInterface()) {
return $this->_reflection[$class] = $reflect;
}
$construct = NoteManager::resolveTarget($reflect);
if (!empty($construct) && $construct->getNumberOfParameters() > 0) {
$this->_constructs[$class] = $construct;
}
return $this->_reflection[$class] = $reflect;
}
/**
* @param string $className
* @return ReflectionMethod[]
* @throws
*/
public function getMethods(string $className): array
{
$reflection = $this->getReflectionClass($className);
return $reflection->getMethods();
}
/**
* @param ReflectionClass|string $class
* @return ReflectionMethod[]
* @throws ReflectionException
*/
public function getReflectMethods(ReflectionClass|string $class): array
{
if (is_string($class)) {
$class = $this->getReflect($class);
}
return NoteManager::getMethods($class);
}
/**
* @param ReflectionMethod $parameters
* @return array
* @throws
*/
public function getMethodParams(ReflectionMethod $parameters): array
{
$className = $parameters->getDeclaringClass()->getName();
$methodName = $parameters->getName();
if (!isset($this->_parameters[$className]))
$this->_parameters[$className] = [];
if (!isset($this->_parameters[$className][$methodName])) {
return $this->_parameters[$className][$methodName] = $this->resolveMethodParams($parameters);
} else {
return $this->_parameters[$className][$methodName];
}
}
/**
* @param ReflectionClass|string $class
* @param string $method
* @return ReflectionMethod|null
* @throws ReflectionException
*/
public function getReflectMethod(ReflectionClass|string $class, string $method): ?ReflectionMethod
{
return $this->getReflectMethods($class)[$method] ?? null;
}
/**
* @param Closure $parameters
* @return array
* @throws
*/
public function getFunctionParams(Closure $parameters): array
{
return $this->resolveMethodParams(new ReflectionFunction($parameters));
}
/**
* @param string $className
* @param string $method
* @return array|null
* @throws ReflectionException
*/
public function getMethodParameters(string $className, string $method): ?array
{
if (isset($this->_parameters[$className]) && isset($this->_parameters[$className][$method])) {
return $this->_parameters[$className][$method];
}
$reflectMethod = $this->getReflectMethod($this->getReflect($className), $method);
if (!($reflectMethod instanceof ReflectionMethod)) {
throw new ReflectionException("Class does not have a function $className::$method");
}
$className = $reflectMethod->getDeclaringClass()->getName();
if (isset($this->_parameters[$className]) && isset($this->_parameters[$className][$reflectMethod->getName()])) {
return $this->_parameters[$className][$reflectMethod->getName()];
}
return $this->setParameters($className, $reflectMethod->getName(), $this->resolveMethodParameters($reflectMethod));
}
/**
* @param ReflectionMethod|ReflectionFunction $parameters
* @return array
* @throws
*/
public function resolveMethodParams(ReflectionMethod|ReflectionFunction $parameters): array
{
$params = [];
if ($parameters->getNumberOfParameters() < 1) {
return $params;
}
$parametersArray = $parameters->getParameters();
$class = $parameters->getDeclaringClass()->getName();
foreach ($parametersArray as $parameter) {
$parameterAttributes = $parameter->getAttributes();
$name = $parameter->getName();
if (count($parameterAttributes) > 0) {
$attribute = $parameterAttributes[0]->newInstance();
$params[$name] = $attribute->dispatch($class, $parameters->getName());
} else {
$params[$name] = $this->contractParams($parameter);
}
}
return $params;
}
/**
* @param $class
* @param $method
* @param $parameters
* @return mixed
*/
private function setParameters($class, $method, $parameters): mixed
{
if (!isset($this->_parameters[$class])) {
$this->_parameters[$class] = [];
}
return $this->_parameters[$class][$method] = $parameters;
}
/**
* @param $parameter
* @return bool|int|mixed|object|string|null
* @throws
*/
protected function contractParams($parameter): mixed
{
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
if ($parameter->getType() === null) {
return $parameter->getType();
}
$value = $parameter->getType()->getName();
if (class_exists($value) || interface_exists($value)) {
return $this->get($value);
} else {
return $this->getTypeValue($parameter);
}
}
/**
* @param Closure $reflectionMethod
* @return array
* @throws ReflectionException
*/
public function getFunctionParameters(Closure $reflectionMethod): array
{
return $this->resolveMethodParameters(new ReflectionFunction($reflectionMethod));
}
/**
* @param ReflectionParameter $parameter
* @return string|int|bool|null|array
* @throws
*/
private function getTypeValue(ReflectionParameter $parameter): string|int|bool|null|array
{
$class = $parameter->getDeclaringClass()->getName();
$name = $parameter->getName();
return match ($parameter->getType()->getName()) {
'string' => '',
'int', 'float' => 0,
'', 'mixed' => NULL,
'bool' => false,
'object' => throw new Exception('Param type ' . $class . '::' . $name . ' must has default value.'),
'array' => [],
default => null
};
}
/**
* @param ReflectionMethod|ReflectionFunction $reflectionMethod
* @return array
*/
private function resolveMethodParameters(ReflectionMethod|ReflectionFunction $reflectionMethod): array
{
if ($reflectionMethod->getNumberOfParameters() < 1) {
return [];
}
$params = [];
foreach ($reflectionMethod->getParameters() as $key => $parameter) {
if ($parameter->isDefaultValueAvailable()) {
$params[$key] = $parameter->getDefaultValue();
} else if ($parameter->getType() === null) {
$params[$key] = $parameter->getType();
} else {
$type = $parameter->getType()->getName();
if (is_string($type) && class_exists($type) || isset($this->_interfaces[$type])) {
$type = Kiri::getDi()->get($type);
}
$params[$key] = match ($parameter->getType()) {
'string' => '',
'int', 'float' => 0,
'', null, 'object', 'mixed' => NULL,
'bool' => false,
default => $type
};
}
}
return $params;
}
/**
* @param object $object
* @param array $config
* @return object
*/
public static function configure(object $object, array $config): object
{
foreach ($config as $key => $value) {
if (!property_exists($object, $key)) {
continue;
}
$object->{$key} = $value;
}
return $object;
}
/**
* @param $class
* @return ReflectionClass|null
*/
public function getReflect($class): ?ReflectionClass
{
if (!isset($this->_reflection[$class])) {
return $this->resolveDependencies($class);
}
return $this->_reflection[$class];
}
/**
* @param string $id
* @return bool
*/
public function has(string $id): bool
{
// TODO: Implement has() method.
return isset($this->_singletons[$id]) && isset($this->_reflection[$id]);
}
/**
* @param $class
*/
public function unset($class)
{
if (is_array($class) && isset($class['class'])) {
$class = $class['class'];
} else if (is_object($class)) {
$class = $class::class;
}
unset(
$this->_reflection[$class], $this->_singletons[$class], $this->_constructs[$class]
);
}
/**
* @return $this
*/
public function flush(): static
{
$this->_reflection = [];
$this->_singletons = [];
$this->_constructs = [];
return $this;
}
/**
* @param $old
* @param $newParam
*
* @return mixed
*/
private function mergeParam($old, $newParam): array
{
if (empty($old)) {
return $newParam;
} else if (empty($newParam)) {
return $old;
}
foreach ($newParam as $key => $val) {
$old[$key] = $val;
}
return $old;
}
/**
* @param string $id
* @return bool
*/
public function has(string $id): bool
{
return isset($this->_singletons[$id]) || isset($this->_interfaces[$id]);
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Kiri\Di;
use Kiri\Di\Context\AsyncContext;
use Kiri\Di\Context\ContextInterface;
use Kiri\Di\Context\CoroutineContext;
use Swoole\Coroutine;
/**
* Class Context
* @package Yoc\http
* @mixin ContextInterface
*/
class Context
{
/**
* @param string $name
* @param array $arguments
* @return mixed
*/
public static function __callStatic(string $name, array $arguments): mixed
{
// TODO: Implement __callStatic() method.
if (static::inCoroutine()) {
return call_user_func([CoroutineContext::class, $name], ...$arguments);
} else {
return call_user_func([AsyncContext::class, $name], ...$arguments);
}
}
/**
* @return bool
*/
public static function inCoroutine(): bool
{
return Coroutine::getCid() > -1;
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Context;
class AsyncContext implements ContextInterface
{
/**
* @var array
*/
private static array $context = [];
/**
* @return bool
*/
public static function inCoroutine(): bool
{
return false;
}
/**
* @param string $key
* @param mixed $value
* @param int|null $coroutineId
* @return mixed
*/
public static function set(string $key, mixed $value, ?int $coroutineId = null): mixed
{
// TODO: Implement set() method.
return static::$context[$key] = $value;
}
/**
* @param string $key
* @param mixed|null $defaultValue
* @param int|null $coroutineId
* @return mixed
*/
public static function get(string $key, mixed $defaultValue = null, ?int $coroutineId = null): mixed
{
// TODO: Implement get() method.
return static::$context[$key] ?? $defaultValue;
}
/**
* @param string $key
* @param int|null $coroutineId
* @return mixed
*/
public static function exists(string $key, ?int $coroutineId = null): bool
{
// TODO: Implement exists() method.
return isset(static::$context[$key]);
}
/**
* @param string $key
* @param int|null $coroutineId
* @return void
*/
public static function remove(string $key, ?int $coroutineId = null): void
{
// TODO: Implement remove() method.
static::$context[$key] = null;
unset(static::$context[$key]);
}
/**
* @param string $id
* @param int $value
* @param int|null $coroutineId
* @return int
*/
public static function increment(string $id, int $value = 1, ?int $coroutineId = null): int
{
if (!isset(static::$context[$id])) {
static::$context[$id] = 0;
}
return static::$context[$id] += $value;
}
/**
* @param string $id
* @param int $value
* @param int|null $coroutineId
* @return int
*/
public static function decrement(string $id, int $value = 1, ?int $coroutineId = null): int
{
if (!isset(static::$context[$id])) {
static::$context[$id] = 0;
}
return static::$context[$id] -= $value;
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Context;
interface ContextInterface
{
/**
* @return bool
*/
public static function inCoroutine(): bool;
/**
* @param string $key
* @param mixed $value
* @param int|null $coroutineId
* @return mixed
*/
public static function set(string $key, mixed $value, ?int $coroutineId = null): mixed;
/**
* @param string $key
* @param mixed|null $defaultValue
* @param int|null $coroutineId
* @return mixed
*/
public static function get(string $key, mixed $defaultValue = null, ?int $coroutineId = null): mixed;
/**
* @param string $key
* @param int|null $coroutineId
* @return mixed
*/
public static function exists(string $key, ?int $coroutineId = null): bool;
/**
* @param string $key
* @param int|null $coroutineId
* @return void
*/
public static function remove(string $key, ?int $coroutineId = null): void;
/**
* @param string $id
* @param int $value
* @param int|null $coroutineId
* @return int
*/
public static function increment(string $id, int $value = 1, ?int $coroutineId = null): int;
/**
* @param string $id
* @param int $value
* @param int|null $coroutineId
* @return int
*/
public static function decrement(string $id, int $value = 1, ?int $coroutineId = null): int;
}
+114
View File
@@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Context;
use Swoole\Coroutine;
class CoroutineContext implements ContextInterface
{
/**
* @return bool
*/
public static function inCoroutine(): bool
{
return Coroutine::getCid() > -1;
}
/**
* @param string $key
* @param mixed $value
* @param int|null $coroutineId
* @return mixed
*/
public static function set(string $key, mixed $value, ?int $coroutineId = null): mixed
{
// TODO: Implement set() method.
if (is_null($coroutineId)) {
$coroutineId = Coroutine::getCid();
}
return Coroutine::getContext($coroutineId)[$key] = $value;
}
/**
* @param string $key
* @param mixed|null $defaultValue
* @param int|null $coroutineId
* @return mixed
*/
public static function get(string $key, mixed $defaultValue = null, ?int $coroutineId = null): mixed
{
// TODO: Implement get() method.
if (is_null($coroutineId)) {
$coroutineId = Coroutine::getCid();
}
return Coroutine::getContext($coroutineId)[$key] ?? $defaultValue;
}
/**
* @param string $key
* @param int|null $coroutineId
* @return mixed
*/
public static function exists(string $key, ?int $coroutineId = null): bool
{
// TODO: Implement exists() method.
if (is_null($coroutineId)) {
$coroutineId = Coroutine::getCid();
}
return isset(Coroutine::getContext($coroutineId)[$key]);
}
/**
* @param string $key
* @param int|null $coroutineId
* @return void
*/
public static function remove(string $key, ?int $coroutineId = null): void
{
// TODO: Implement remove() method.
if (is_null($coroutineId)) {
$coroutineId = Coroutine::getCid();
}
Coroutine::getContext($coroutineId)[$key] = null;
unset(Coroutine::getContext($coroutineId)[$key]);
}
/**
* @param string $id
* @param int $value
* @param int|null $coroutineId
* @return int
*/
public static function increment(string $id, int $value = 1, ?int $coroutineId = null): int
{
if (is_null($coroutineId)) {
$coroutineId = Coroutine::getCid();
}
if (!isset(Coroutine::getContext($coroutineId)[$id])) {
Coroutine::getContext($coroutineId)[$id] = 0;
}
return Coroutine::getContext($coroutineId)[$id] += $value;
}
/**
* @param string $id
* @param int $value
* @param int|null $coroutineId
* @return int
*/
public static function decrement(string $id, int $value = 1, ?int $coroutineId = null): int
{
if (is_null($coroutineId)) {
$coroutineId = Coroutine::getCid();
}
if (!isset(Coroutine::getContext($coroutineId)[$id])) {
Coroutine::getContext($coroutineId)[$id] = 0;
}
return Coroutine::getContext($coroutineId)[$id] -= $value;
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Kiri\Di;
class HotReloadState
{
private const int MAX_AGE_SECONDS = 30;
public function store(array $changedFiles): void
{
$payload = [
'timestamp' => time(),
'changed_files' => $changedFiles
|> array_filter(...)
|> (fn($x) => array_map([$this, 'normalizePath'], $x))
|> array_unique(...)
|> array_values(...),
];
$directory = dirname($this->getFilePath());
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
file_put_contents($this->getFilePath(), json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
public function consume(): array
{
return $this->peek();
}
public function peek(): array
{
$file = $this->getFilePath();
if (!file_exists($file)) {
return [];
}
$data = json_decode((string)file_get_contents($file), true);
if (!is_array($data)) {
return [];
}
$timestamp = (int)($data['timestamp'] ?? 0);
if ($timestamp < 1 || (time() - $timestamp) > self::MAX_AGE_SECONDS) {
return [];
}
$files = is_array($data['changed_files'] ?? null) ? $data['changed_files'] : [];
return array_map([$this, 'normalizePath'], $files)
|> array_unique(...)
|> array_values(...);
}
private function getFilePath(): string
{
$basePath = $this->normalizePath($_SERVER['PWD'] ?? APP_PATH ?? getcwd());
$runtimePath = defined('APP_PATH')
? rtrim(str_replace('\\', '/', APP_PATH), '/') . '/storage/.kiri-hot-reload/'
: sys_get_temp_dir() . '/kiri-hot-reload/';
return $runtimePath . md5($basePath) . '.json';
}
private function normalizePath(string $path): string
{
$resolved = realpath($path) ?: $path;
return str_replace('\\', '/', $resolved);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Inject;
use Kiri\Di\Interface\InjectPropertyInterface;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Config implements InjectPropertyInterface
{
/**
* @param string $key
*/
public function __construct(public string $key)
{
}
/**
* @param object $class
* @param string $property
* @return void
*/
public function dispatch(object $class, string $property): void
{
// TODO: Implement dispatch() method.
$class->{$property} = config($this->key);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Inject;
use Kiri\Di\Interface\InjectPropertyInterface;
use Kiri\Di\Container as DContainer;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
class Container implements InjectPropertyInterface
{
/**
* @param string $service
* @param mixed|null $default
* @throws
*/
public function __construct(readonly public string $service, public mixed $default = null)
{
}
/**
* @param object $class
* @param string $property
* @return void
* @throws
*/
public function dispatch(object $class, string $property): void
{
$class->{$property} = DContainer::instance()->get($this->service);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Inject;
use Exception;
use Kiri\Di\Container;
use Kiri\Di\Interface\InjectParameterInterface;
#[\Attribute(\Attribute::TARGET_PARAMETER)]
class ContainerParams implements InjectParameterInterface
{
/**
* @param mixed $value
*/
public function __construct(readonly public mixed $value)
{
}
/**
* @return mixed|null
* @throws Exception
*/
public function dispatch(string $class, string $method): mixed
{
return Container::instance()->get($this->value);
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
namespace Kiri\Di\Inject;
#[\Attribute(\Attribute::TARGET_CLASS)]
class Skip
{
}
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace Kiri\Di\Interface;
interface InjectMethodInterface
{
/**
* @param string $class
* @param string $method
* @return void
*/
public function dispatch(string $class, string $method): void;
}
+17
View File
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Interface;
interface InjectParameterInterface
{
/**
* @param string $class
* @param string $method
* @return mixed
*/
public function dispatch(string $class, string $method): mixed;
}
+17
View File
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Interface;
interface InjectPropertyInterface
{
/**
* @param object $class
* @param string $property
* @return void
*/
public function dispatch(object $class, string $property): void;
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Kiri\Di\Interface;
interface InjectTargetInterface
{
/**
* @param string $class
* @return void
*/
public function dispatch(string $class): void;
}
+25
View File
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Kiri\Di\Interface;
use Psr\Http\Message\ResponseInterface;
/**
* Response Emitter Interface
*/
interface ResponseEmitterInterface
{
/**
* @param ResponseInterface $proxy
* @param object $response
* @param object $request
* @return void
*/
public function response(ResponseInterface $proxy, object $response, object $request): void;
}
-88
View File
@@ -1,88 +0,0 @@
<?php
namespace Kiri\Di;
use Kiri\Abstracts\Component;
use Kiri;
/**
* 服务定位器
*/
class LocalService extends Component
{
private array $_components = [];
private array $_definition = [];
/**
* @param $name
* @param $define
*/
public function set($name, $define)
{
unset($this->_components[$name]);
$this->_definition[$name] = $define;
if (is_object($define) || $define instanceof \Closure) {
$this->_components[$name] = $define;
}
}
/**
* @throws \Exception
*/
public function get(string $name, $throwException = true)
{
if (isset($this->_components[$name])) {
return $this->_components[$name];
}
if (isset($this->_definition[$name])) {
$definition = $this->_definition[$name];
if (is_object($definition) && !$definition instanceof \Closure) {
return $this->_components[$name] = $definition;
}
return $this->_components[$name] = Kiri::createObject($definition);
} else if ($throwException) {
throw new \Exception("Unknown component ID: $name");
}
return null;
}
/**
* @param array $components
*/
public function setComponents(array $components)
{
foreach ($components as $name => $component) {
$this->set($name, $component);
}
}
/**
* @param $id
* @return bool
*/
public function has($id): bool
{
return isset($this->_components[$id]) || isset($this->_definition[$id]);
}
/**
* @param $id
*/
public function remove($id): void
{
unset($this->_components[$id], $this->_definition[$id]);
}
}
-303
View File
@@ -1,303 +0,0 @@
<?php
namespace Kiri\Di;
use JetBrains\PhpStorm\Pure;
use ReflectionAttribute;
use ReflectionClass;
use ReflectionProperty;
class NoteManager
{
private static array $_classTarget = [];
private static array $_classMethodAnnotation = [];
private static array $_classMethod = [];
private static array $_classPropertyAnnotation = [];
private static array $_classProperty = [];
private static array $_mapping = [];
/**
* @return void
*/
public static function clear()
{
static::$_classTarget = [];
static::$_classMethodAnnotation = [];
static::$_classMethod = [];
static::$_classPropertyAnnotation = [];
static::$_classProperty = [];
static::$_mapping = [];
}
/**
* @param ReflectionClass $class
*/
public static function setTargetAnnotation(ReflectionClass $class)
{
$className = $class->getName();
if (!isset(static::$_classTarget[$className])) {
static::$_classTarget[$className] = [];
}
foreach ($class->getAttributes() as $attribute) {
if (!class_exists($attribute->getName())) {
continue;
}
$instance = $attribute->newInstance();
static::$_classTarget[$className][] = $instance;
self::setMappingClass($attribute, $className);
}
}
/**
* @param ReflectionAttribute $attribute
* @param string $class
*/
public static function setMappingClass(ReflectionAttribute $attribute, string $class)
{
if (!isset(static::$_mapping[$attribute->getName()])) {
static::$_mapping[$attribute->getName()] = [];
}
if (!isset(static::$_mapping[$attribute->getName()][$class])) {
static::$_mapping[$attribute->getName()][$class] = [];
}
}
/**
* @param ReflectionAttribute $attribute
* @param string $class
* @param string $method
* @param mixed $instance
*/
public static function setMappingMethod(ReflectionAttribute $attribute, string $class, string $method, mixed $instance)
{
self::setMappingClass($attribute, $class);
if (!isset(static::$_mapping[$attribute->getName()][$class]['method'])) {
static::$_mapping[$attribute->getName()][$class]['method'] = [];
}
static::$_mapping[$attribute->getName()][$class]['method'][] = [$method => $instance];
}
/**
* @param ReflectionAttribute $attribute
* @param string $class
* @param string $property
* @param $instance
*/
public static function setMappingProperty(ReflectionAttribute $attribute, string $class, string $property, $instance)
{
self::setMappingClass($attribute, $class);
$mapping = static::$_mapping[$attribute->getName()][$class];
if (!isset($mapping['property'])) {
$mapping['property'] = [];
}
$mapping['property'][] = [$property => $instance];
static::$_mapping[$attribute->getName()][$class] = $mapping;
}
/**
* @param mixed $class
* @return array
*/
public static function getTargetAnnotation(mixed $class): array
{
if (!is_string($class)) {
$class = $class::class;
}
return static::$_classTarget[$class] ?? [];
}
/**
* @param ReflectionClass $class
*/
public static function setMethodAnnotation(ReflectionClass $class)
{
$className = $class->getName();
static::$_classMethodAnnotation[$className] = static::$_classMethod[$className] = [];
foreach ($class->getMethods() as $ReflectionMethod) {
static::$_classMethod[$className][$ReflectionMethod->getName()] = $ReflectionMethod;
static::$_classMethodAnnotation[$className][$ReflectionMethod->getName()] = [];
foreach ($ReflectionMethod->getAttributes() as $attribute) {
if (!class_exists($attribute->getName())) {
continue;
}
$instance = $attribute->newInstance();
static::$_classMethodAnnotation[$className][$ReflectionMethod->getName()][] = $instance;
self::setMappingMethod($attribute, $className, $ReflectionMethod->getName(), $instance);
}
}
}
/**
* @param string $class
* @param string $method
* @return bool
*/
public static function hasMethod(string $class, string $method): bool
{
return isset(static::$_classMethod[$class]) && isset(static::$_classMethod[$class][$method]);
}
/**
* @param ReflectionClass $class
* @return array
*/
#[Pure] public static function getMethodAnnotation(ReflectionClass $class): array
{
return static::$_classMethodAnnotation[$class->getName()] ?? [];
}
/**
* @param \ReflectionClass $reflect
* @return \ReflectionMethod|null
*/
public static function resolveTarget(ReflectionClass $reflect): ?\ReflectionMethod
{
NoteManager::setPropertyAnnotation($reflect);
NoteManager::setTargetAnnotation($reflect);
NoteManager::setMethodAnnotation($reflect);
return $reflect->getConstructor();
}
/**
* @param ReflectionClass $class
*/
public static function setPropertyAnnotation(ReflectionClass $class)
{
$className = $class->getName();
static::$_classProperty[$className] = static::$_classPropertyAnnotation[$className] = [];
foreach ($class->getProperties(ReflectionProperty::IS_PRIVATE | ReflectionProperty::IS_PUBLIC |
ReflectionProperty::IS_PROTECTED) as $ReflectionMethod) {
static::$_classProperty[$className][$ReflectionMethod->getName()] = $ReflectionMethod;
foreach ($ReflectionMethod->getAttributes() as $attribute) {
if (!class_exists($attribute->getName())) {
continue;
}
$instance = $attribute->newInstance();
static::$_classPropertyAnnotation[$className][$ReflectionMethod->getName()] = $instance;
self::setMappingProperty($attribute, $className, $ReflectionMethod->getName(), $instance);
}
}
}
/**
* @param string $attribute
* @param string|null $class
* @return array[]
*/
public static function getAttributeTrees(string $attribute, string $class = null): array
{
$mapping = static::$_mapping[$attribute] ?? [];
if (empty($mapping) || empty($class)) {
return $mapping;
}
return $mapping[$class] ?? [];
}
/**
* @param string $attribute
* @param string $class
* @param string|null $method
* @return array
*/
public static function getSpecify_annotation(string $attribute, string $class, string $method = null): mixed
{
$class = self::getAttributeTrees($attribute, $class);
if (empty($class) || !isset($class['method'])) {
return null;
}
if (empty($method)) {
return $class['method'];
}
foreach ($class['method'] as $value) {
$key = key($value);
if ($method == $key) {
return $value[$key];
}
}
return null;
}
/**
* @param string $attribute
* @param string $class
* @param string $method
* @return mixed
*/
public static function getPropertyByAnnotation(string $attribute, string $class, string $method): mixed
{
$class = self::getAttributeTrees($attribute, $class);
if (empty($class) || !isset($class['property'])) {
return [];
}
foreach ($class['property'] as $value) {
$key = key($value);
if ($method == $key) {
return $value[$key];
}
}
return null;
}
/**
* @param ReflectionClass|string $class
* @return array
* @throws \ReflectionException
*/
public static function getMethods(ReflectionClass|string $class): array
{
if (is_string($class)) {
$class = self::getReflect($class);
}
return static::$_classMethod[$class->getName()] ?? [];
}
/**
* @param ReflectionClass $class
* @return ReflectionProperty[]
*/
#[Pure] public static function getProperty(ReflectionClass $class): array
{
return static::$_classProperty[$class->getName()] ?? [];
}
/**
* @param ReflectionClass $class
* @return array
*/
#[Pure] public static function getPropertyAnnotation(ReflectionClass $class): array
{
return static::$_classPropertyAnnotation[$class->getName()] ?? [];
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace Kiri\Di;
class ScanManifest
{
private array $entries = [];
/**
* @param string $path
* @param int $mtime
* @param array $classes
* @return void
*/
public function set(string $path, int $mtime, array $classes): void
{
$this->entries[$path] = [
'mtime' => $mtime,
'classes' => array_values(array_unique($classes)),
];
}
/**
* @param string $path
* @return bool
*/
public function has(string $path): bool
{
return isset($this->entries[$path]);
}
/**
* @param string $path
* @return int|null
*/
public function getMtime(string $path): ?int
{
return $this->entries[$path]['mtime'] ?? null;
}
/**
* @param string $path
* @return array
*/
public function getClasses(string $path): array
{
return $this->entries[$path]['classes'] ?? [];
}
/**
* @param string $path
* @return array
*/
public function remove(string $path): array
{
$classes = $this->getClasses($path);
unset($this->entries[$path]);
return $classes;
}
/**
* @return array
*/
public function all(): array
{
return $this->entries;
}
/**
* @param string|null $prefix
* @return array
*/
public function paths(?string $prefix = null): array
{
if ($prefix === null) {
return array_keys($this->entries);
}
return $this->entries
|> array_keys(...)
|> (fn($x) => array_filter($x, fn(string $path) => str_starts_with($path, $prefix)))
|> array_values(...);
}
/**
* @param array $entries
* @return void
*/
public function fromArray(array $entries): void
{
$this->entries = [];
foreach ($entries as $path => $entry) {
$mtime = (int)($entry['mtime'] ?? 0);
$classes = is_array($entry['classes'] ?? null) ? $entry['classes'] : [];
$this->set($path, $mtime, $classes);
}
}
}
+594
View File
@@ -0,0 +1,594 @@
<?php
declare(strict_types=1);
namespace Kiri\Di;
use DirectoryIterator;
use Kiri\Abstracts\Component;
use Kiri\Di\Inject\Container;
use Kiri\Di\Inject\Skip;
use Kiri\Di\Interface\InjectMethodInterface;
use Psr\Container\ContainerInterface;
use ReflectionClass;
use ReflectionMethod;
use Throwable;
class Scanner extends Component
{
#[Container(ContainerInterface::class)]
public ContainerInterface $container;
public array $files = [];
private array $fileMtimes = [];
private ?bool $hasOpcache = null;
private string $basePath;
private ScanManifest $manifest;
private ?ChangeSet $changeSet = null;
private array $visitedFiles = [];
private array $config = [
'skip_patterns' => ['/vendor/', '/tests/', '/cache/', '/node_modules/'],
'skip_directories' => [],
'extensions' => ['php'],
'max_depth' => 20,
'follow_links' => false,
'cache_enabled' => false,
'cache_ttl' => 3600,
'debug' => false,
];
public function __construct()
{
$this->basePath = $this->normalizePath($_SERVER['PWD'] ?? APP_PATH ?? getcwd());
$this->manifest = new ScanManifest();
}
public function setConfig(array $config): void
{
$this->config = array_merge($this->config, $config);
}
public function scan(string $path, ?string $cacheFile = null): ChangeSet
{
$path = $this->normalizePath($path);
$this->changeSet = new ChangeSet();
$this->visitedFiles = [];
$cacheLoaded = false;
if ($this->config['cache_enabled']) {
$cacheFile = $cacheFile ?? $this->getDefaultCacheFile();
if ($this->loadFromCache($cacheFile, $path)) {
$this->replayManifest($path);
return $this->changeSet;
}
$cacheLoaded = $this->manifest->all() !== [];
}
$this->scanDirectory($path);
$this->detectRemovedFiles($path);
if ($cacheLoaded) {
$this->replayManifest($path, $this->changeSet->getChangedFiles());
}
$this->syncLegacyState();
if ($this->config['cache_enabled']) {
$this->saveToCache($cacheFile);
}
return $this->changeSet;
}
public function scanFiles(array $files, ?string $scopePath = null, ?string $cacheFile = null): ChangeSet
{
$this->changeSet = new ChangeSet();
$this->visitedFiles = [];
$cacheLoaded = false;
if ($scopePath !== null) {
$scopePath = $this->normalizePath($scopePath);
}
if ($this->config['cache_enabled'] && $scopePath !== null) {
$cacheFile = $cacheFile ?? $this->getDefaultCacheFile();
$this->loadFromCache($cacheFile, $scopePath);
$cacheLoaded = $this->manifest->all() !== [];
}
foreach (array_values(array_unique($files)) as $file) {
$path = $this->normalizePath($file);
if (file_exists($path)) {
$this->processFile($path);
continue;
}
$this->markRemovedFile($path);
}
if ($cacheLoaded && $scopePath !== null) {
$this->replayManifest($scopePath, $this->changeSet->getChangedFiles());
}
$this->syncLegacyState();
return $this->changeSet;
}
private function scanDirectory(string $path, int $depth = 0): void
{
if ($depth > $this->config['max_depth']) {
return;
}
try {
$dir = new DirectoryIterator($path);
} catch (Throwable $e) {
$this->logError($e, ['path' => $path, 'action' => 'open_directory']);
return;
}
foreach ($dir as $item) {
if ($this->shouldSkipItem($item)) {
continue;
}
$realPath = $this->normalizePath($item->getRealPath() ?: $item->getPathname());
if ($item->isDir()) {
if ($this->shouldSkipDirectory($realPath)) {
continue;
}
if ($item->isLink() && !$this->config['follow_links']) {
continue;
}
$this->scanDirectory($realPath, $depth + 1);
continue;
}
if ($item->isFile()) {
$this->processFile($realPath);
}
}
}
private function shouldSkipItem(DirectoryIterator $item): bool
{
return $item->isDot() || str_starts_with($item->getFilename(), '.');
}
private function shouldSkipDirectory(string $path): bool
{
$path = rtrim($this->normalizePath($path), '/') . '/';
$skipDirs = array_merge(
$this->config['skip_directories'],
config('site.scanner.skip', [])
);
if (in_array($path, $skipDirs, true)) {
return true;
}
foreach ($this->config['skip_patterns'] as $pattern) {
if (strpos($path, $pattern) !== false) {
return true;
}
}
return false;
}
private function processFile(string $path): void
{
$path = $this->normalizePath($path);
if (!in_array(pathinfo($path, PATHINFO_EXTENSION), $this->config['extensions'], true)) {
return;
}
$this->visitedFiles[$path] = true;
if (!$this->shouldProcessFile($path)) {
return;
}
try {
$oldClasses = $this->manifest->getClasses($path);
$this->invalidateClasses($oldClasses);
$newClasses = $this->loadAndParseFile($path);
$this->manifest->set($path, (int)filemtime($path), $newClasses);
$this->changeSet?->addChangedFile($path);
foreach (array_diff($oldClasses, $newClasses) as $class) {
$this->changeSet?->addRemovedClass($class);
}
foreach ($newClasses as $class) {
$this->changeSet?->addChangedClass($class);
}
} catch (Throwable $e) {
$this->logError($e, [
'file' => $path,
'action' => 'process_file',
]);
if ($e instanceof \ParseError) {
throw $e;
}
}
}
private function shouldProcessFile(string $path): bool
{
if (!file_exists($path)) {
return false;
}
$mtime = (int)filemtime($path);
$cachedMtime = $this->manifest->getMtime($path);
return $cachedMtime === null || $cachedMtime < $mtime;
}
private function loadAndParseFile(string $path): array
{
$this->optimizeWithOpcache($path);
$before = get_declared_classes();
require_once $path;
$after = get_declared_classes();
$classes = array_values(array_diff($after, $before));
foreach ($classes as $class) {
if (class_exists($class)) {
$this->analyzeClass($class);
}
}
return $classes;
}
private function optimizeWithOpcache(string $path): void
{
if ($this->hasOpcache === null) {
$this->hasOpcache = function_exists('opcache_invalidate') && function_exists('opcache_compile_file');
}
if (!$this->hasOpcache) {
return;
}
try {
opcache_invalidate($path, true);
opcache_compile_file($path);
} catch (Throwable $e) {
if ($this->config['debug']) {
$this->logError($e, [
'file' => $path,
'action' => 'opcache_optimize',
]);
}
}
}
private function analyzeClass(string $class): void
{
try {
$reflect = $this->container->getReflectionClass($class);
if (!$reflect->isInstantiable() || $reflect->isTrait() || $reflect->isEnum() || $reflect->isInterface()) {
return;
}
if ($this->shouldSkipClass($reflect)) {
return;
}
$this->analyzeClassMethods($reflect, $class);
} catch (Throwable $e) {
$this->logError($e, [
'class' => $class,
'action' => 'analyze_class',
]);
}
}
private function shouldSkipClass(ReflectionClass $reflect): bool
{
$attributes = array_map(fn($attr) => $attr->getName(), $reflect->getAttributes());
return in_array(Skip::class, $attributes, true)
|| in_array(\Attribute::class, $attributes, true);
}
private function analyzeClassMethods(ReflectionClass $reflect, string $class): void
{
foreach ($reflect->getMethods() as $method) {
if ($method->isStatic() || $method->getDeclaringClass()->getName() !== $class) {
continue;
}
$this->processMethodAttributes($method, $class);
}
}
private function processMethodAttributes(ReflectionMethod $method, string $class): void
{
foreach ($method->getAttributes() as $attribute) {
$attributeName = $attribute->getName();
if (!class_exists($attributeName)) {
continue;
}
try {
$instance = $attribute->newInstance();
if ($instance instanceof InjectMethodInterface) {
$instance->dispatch($class, $method->getName());
}
} catch (Throwable $e) {
$this->logError($e, [
'class' => $class,
'method' => $method->getName(),
'attribute' => $attributeName,
'action' => 'process_attribute',
]);
}
}
}
private function getDefaultCacheFile(): string
{
$cacheDir = defined('APP_PATH')
? rtrim(str_replace('\\', '/', APP_PATH), '/') . '/storage/.kiri-scanner-cache/'
: sys_get_temp_dir() . '/kiri-scanner-cache/';
if (!is_dir($cacheDir)) {
mkdir($cacheDir, 0755, true);
}
$projectHash = md5(__DIR__);
return $cacheDir . 'scanner_' . $projectHash . '.json';
}
private function loadFromCache(string $cacheFile, string $path): bool
{
if (!file_exists($cacheFile)) {
return false;
}
if (filemtime($cacheFile) + $this->config['cache_ttl'] < time()) {
return false;
}
$data = json_decode(file_get_contents($cacheFile), true);
if (!is_array($data) || !isset($data['manifest'])) {
return false;
}
$this->manifest->fromArray($data['manifest']);
$this->syncLegacyState();
return !$this->hasDirectoryChanged($path);
}
private function hasDirectoryChanged(string $path): bool
{
$currentFiles = $this->collectFiles($path);
$cachedFiles = $this->manifest->paths(rtrim($path, '/\\') . '/');
sort($currentFiles);
sort($cachedFiles);
if ($currentFiles !== $cachedFiles) {
return true;
}
foreach ($currentFiles as $file) {
if (!file_exists($file)) {
return true;
}
if ($this->manifest->getMtime($file) !== (int)filemtime($file)) {
return true;
}
}
return false;
}
private function saveToCache(string $cacheFile): void
{
$data = [
'manifest' => $this->manifest->all(),
'timestamp' => time(),
'version' => '2.0',
];
file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT));
$this->cleanupOldCacheFiles(dirname($cacheFile));
}
private function cleanupOldCacheFiles(string $cacheDir): void
{
$files = glob($cacheDir . '/scanner_*.json');
$now = time();
foreach ($files as $file) {
if (filemtime($file) + (7 * 24 * 3600) < $now) {
unlink($file);
}
}
}
private function logError(Throwable $e, array $context = []): void
{
$fullContext = array_merge($context, [
'message' => $e->getMessage(),
'code' => $e->getCode(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
'time' => date('Y-m-d H:i:s'),
]);
if (function_exists('error')) {
error($e, $fullContext);
} else {
error_log(json_encode($fullContext));
}
if ($this->config['debug'] && php_sapi_name() === 'cli') {
echo "Scanner Error: {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}\n";
if ($context !== []) {
echo 'Context: ' . json_encode($context) . "\n";
}
}
}
public function load_directory(string $path): void
{
$this->scan($path);
}
protected function parseFile($file): void
{
$absolutePath = $this->normalizePath($this->basePath . '/' . ltrim((string)$file, '/')) . '.php';
if (file_exists($absolutePath)) {
$this->processFile($absolutePath);
}
}
protected function skipNames(ReflectionClass $reflect): array
{
return array_map(fn($attr) => $attr->getName(), $reflect->getAttributes());
}
public function getStats(): array
{
return [
'total_files' => count($this->files),
'cached_mtimes' => count($this->fileMtimes),
'base_path' => $this->basePath,
'config' => $this->config,
'manifest_entries' => count($this->manifest->all()),
];
}
public function reset(): void
{
$this->files = [];
$this->fileMtimes = [];
$this->hasOpcache = null;
$this->manifest = new ScanManifest();
$this->changeSet = null;
$this->visitedFiles = [];
}
private function detectRemovedFiles(string $path): void
{
$prefix = rtrim($path, '/\\') . '/';
foreach ($this->manifest->paths($prefix) as $file) {
if (!isset($this->visitedFiles[$file])) {
$this->markRemovedFile($file);
}
}
}
private function markRemovedFile(string $path): void
{
if (!$this->manifest->has($path)) {
return;
}
$classes = $this->manifest->remove($path);
$this->invalidateClasses($classes);
foreach ($classes as $class) {
$this->changeSet?->addRemovedClass($class);
}
$this->changeSet?->addRemovedFile($path);
}
private function syncLegacyState(): void
{
$this->files = array_keys($this->manifest->all());
$this->fileMtimes = [];
foreach ($this->manifest->all() as $path => $entry) {
$this->fileMtimes[$path] = (int)$entry['mtime'];
}
}
private function collectFiles(string $path, int $depth = 0): array
{
if ($depth > $this->config['max_depth']) {
return [];
}
$files = [];
try {
$dir = new DirectoryIterator($path);
} catch (Throwable) {
return [];
}
foreach ($dir as $item) {
if ($this->shouldSkipItem($item)) {
continue;
}
$realPath = $this->normalizePath($item->getRealPath() ?: $item->getPathname());
if ($item->isDir()) {
if ($this->shouldSkipDirectory($realPath)) {
continue;
}
if ($item->isLink() && !$this->config['follow_links']) {
continue;
}
$files = array_merge($files, $this->collectFiles($realPath, $depth + 1));
continue;
}
if ($item->isFile() && in_array(pathinfo($realPath, PATHINFO_EXTENSION), $this->config['extensions'], true)) {
$files[] = $realPath;
}
}
return $files;
}
private function normalizePath(string $path): string
{
$resolved = realpath($path) ?: $path;
return str_replace('\\', '/', $resolved);
}
private function replayManifest(string $path, array $skipPaths = []): void
{
$skip = array_fill_keys(array_map([$this, 'normalizePath'], $skipPaths), true);
$prefix = rtrim($path, '/\\') . '/';
foreach ($this->manifest->paths($prefix) as $file) {
if (isset($skip[$file])) {
continue;
}
foreach ($this->manifest->getClasses($file) as $class) {
if (class_exists($class)) {
$this->analyzeClass($class);
}
}
}
}
private function invalidateClasses(array $classes): void
{
foreach ($classes as $class) {
if (method_exists($this->container, 'forgetClass')) {
$this->container->forgetClass($class);
}
}
}
}
+2 -1
View File
@@ -9,7 +9,8 @@
],
"license": "MIT",
"require": {
"php": ">=8.0"
"php": ">=8.5",
"psr/container": "^2.0"
},
"autoload": {
"psr-4": {