feat(database): 添加用户角色权限系统及相关监控功能

- 创建用户表(users)包含基本信息和认证字段
- 创建角色表(roles)用于权限控制
- 创建权限表(permissions)定义系统权限
- 创建用户角色关联表(user_roles)建立用户与角色关系
- 创建角色权限关联表(role_permissions)建立角色与权限关系
- 创建迁移记录表(migrations)追踪数据库变更
- 添加AdminController提供管理员面板功能
- 实现系统监控、配置管理、缓存清理等功能
- 添加AOP切面编程支持的各种通知类型
- 实现告警管理AlertManager支持多渠道告警
- 添加文档注解接口规范
This commit is contained in:
Lawson
2026-04-08 17:00:28 +08:00
commit 2782d765fb
270 changed files with 107192 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Annotation;
#[\Attribute(\Attribute::TARGET_CLASS)]
final class Controller
{
public string $prefix;
public function __construct(string $prefix = '')
{
$this->prefix = $prefix;
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Annotation;
#[\Attribute(\Attribute::TARGET_CLASS)]
final class Dao
{
public string $name;
public function __construct(string $name = '')
{
$this->name = $name;
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Annotation;
#[\Attribute(\Attribute::TARGET_PROPERTY)]
final class Inject
{
public ?string $name;
public function __construct(?string $name = null)
{
$this->name = $name;
}
}

View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Annotation;
#[\Attribute(\Attribute::TARGET_CLASS)]
final class Service
{
public string $name;
public function __construct(string $name = '')
{
$this->name = $name;
}
}

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Aop;
/**
* AOP通知接口
* 定义各种通知类型的基础接口
*/
interface Advice
{
/**
* 执行通知
*/
public function invoke(JoinPoint $joinPoint): mixed;
}
/**
* 前置通知
*/
class BeforeAdvice implements Advice
{
private callable $callback;
public function __construct(callable $callback)
{
$this->callback = $callback;
}
public function invoke(JoinPoint $joinPoint): mixed
{
return ($this->callback)($joinPoint);
}
}
/**
* 后置通知
*/
class AfterAdvice implements Advice
{
private callable $callback;
public function __construct(callable $callback)
{
$this->callback = $callback;
}
public function invoke(JoinPoint $joinPoint): mixed
{
try {
$result = $joinPoint->proceed();
($this->callback)($joinPoint);
return $result;
} catch (\Throwable $e) {
($this->callback)($joinPoint);
throw $e;
}
}
}
/**
* 环绕通知
*/
class AroundAdvice implements Advice
{
private callable $callback;
public function __construct(callable $callback)
{
$this->callback = $callback;
}
public function invoke(JoinPoint $joinPoint): mixed
{
return ($this->callback)($joinPoint);
}
}
/**
* 返回后通知
*/
class AfterReturningAdvice implements Advice
{
private callable $callback;
public function __construct(callable $callback)
{
$this->callback = $callback;
}
public function invoke(JoinPoint $joinPoint): mixed
{
$result = $joinPoint->proceed();
if (!$joinPoint->hasException()) {
($this->callback)($joinPoint, $result);
}
return $result;
}
}
/**
* 异常后通知
*/
class AfterThrowingAdvice implements Advice
{
private callable $callback;
public function __construct(callable $callback)
{
$this->callback = $callback;
}
public function invoke(JoinPoint $joinPoint): mixed
{
try {
return $joinPoint->proceed();
} catch (\Throwable $e) {
($this->callback)($joinPoint, $e);
throw $e;
}
}
}

View File

@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Aop;
use Fendx\Core\Container\Container;
use ReflectionClass;
use ReflectionMethod;
use ReflectionException;
final class AopManager
{
private static ?self $instance = null;
private array $aspects = [];
private Container $container;
private function __construct(Container $container)
{
$this->container = $container;
}
public static function getInstance(Container $container): self
{
if (self::$instance === null) {
self::$instance = new self($container);
}
return self::$instance;
}
public function registerAspect(string $aspectClass, array $pointcuts): void
{
$aspect = $this->container->make($aspectClass);
$this->aspects[$aspectClass] = [
'aspect' => $aspect,
'pointcuts' => $pointcuts
];
}
public function createProxy(object $target): object
{
$className = get_class($target);
$proxyClass = $this->generateProxyClass($className);
return new $proxyClass($target, $this);
}
private function generateProxyClass(string $className): string
{
$proxyClassName = $className . '_AopProxy';
if (class_exists($proxyClassName)) {
return $proxyClassName;
}
$reflection = new ReflectionClass($className);
$methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
$classCode = "<?php\n";
$classCode .= "declare(strict_types=1);\n\n";
$classCode .= "class {$proxyClassName}\n";
$classCode .= "{\n";
$classCode .= " private object \$target;\n";
$classCode .= " private Fendx\\Core\\Aop\\AopManager \$aopManager;\n\n";
$classCode .= " public function __construct(object \$target, Fendx\\Core\\Aop\\AopManager \$aopManager)\n";
$classCode .= " {\n";
$classCode .= " \$this->target = \$target;\n";
$classCode .= " \$this->aopManager = \$aopManager;\n";
$classCode .= " }\n\n";
foreach ($methods as $method) {
if ($method->getName() === '__construct') {
continue;
}
$methodName = $method->getName();
$params = $this->generateMethodParameters($method);
$classCode .= " public function {$methodName}({$params}): mixed\n";
$classCode .= " {\n";
$classCode .= " \$args = func_get_args();\n";
$classCode .= " \$aspect = \$this->aopManager->getMatchingAspect(\$this->target, '{$methodName}');\n";
$classCode .= " \n";
$classCode .= " if (\$aspect) {\n";
$classCode .= " return \$aspect->weave(\$this->target, '{$methodName}', \$args);\n";
$classCode .= " }\n";
$classCode .= " \n";
$classCode .= " return \$this->target->{$methodName}(...\$args);\n";
$classCode .= " }\n\n";
}
$classCode .= "}\n";
eval($classCode);
return $proxyClassName;
}
private function generateMethodParameters(ReflectionMethod $method): string
{
$params = [];
foreach ($method->getParameters() as $param) {
$type = $param->getType();
$typeStr = $type ? $type->getName() . ' ' : '';
$default = $param->isDefaultValueAvailable() ? ' = ' . var_export($param->getDefaultValue(), true) : '';
$params[] = $typeStr . '$' . $param->getName() . $default;
}
return implode(', ', $params);
}
public function getMatchingAspect(object $target, string $method): ?Aspect
{
foreach ($this->aspects as $aspectData) {
foreach ($aspectData['pointcuts'] as $pointcut) {
if ($this->matchesPointcut($target, $method, $pointcut)) {
return $aspectData['aspect'];
}
}
}
return null;
}
private function matchesPointcut(object $target, string $method, array $pointcut): bool
{
$className = get_class($target);
// 注解切点
if (isset($pointcut['annotation'])) {
$reflection = new ReflectionClass($className);
if ($reflection->getAttributes($pointcut['annotation'])) {
return true;
}
}
// 方法名匹配切点
if (isset($pointcut['method'])) {
$pattern = str_replace('*', '.*', $pointcut['method']);
if (preg_match("/^{$pattern}$/", $method)) {
return true;
}
}
// 路由匹配切点
if (isset($pointcut['route'])) {
// TODO: 实现路由匹配逻辑
}
return false;
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Aop;
use Closure;
final class Aspect
{
private array $beforeAdvice = [];
private array $afterAdvice = [];
private array $aroundAdvice = [];
private array $afterReturningAdvice = [];
private array $afterThrowingAdvice = [];
public function before(Closure $advice): self
{
$this->beforeAdvice[] = $advice;
return $this;
}
public function after(Closure $advice): self
{
$this->afterAdvice[] = $advice;
return $this;
}
public function around(Closure $advice): self
{
$this->aroundAdvice[] = $advice;
return $this;
}
public function afterReturning(Closure $advice): self
{
$this->afterReturningAdvice[] = $advice;
return $this;
}
public function afterThrowing(Closure $advice): self
{
$this->afterThrowingAdvice[] = $advice;
return $this;
}
public function weave(object $target, string $method, array $args = []): mixed
{
// 执行Before通知
foreach ($this->beforeAdvice as $advice) {
$advice($target, $method, $args);
}
try {
// 如果有Around通知使用Around包装
if (!empty($this->aroundAdvice)) {
$result = $this->executeAroundAdvice($target, $method, $args, 0);
} else {
$result = $target->$method(...$args);
}
// 执行AfterReturning通知
foreach ($this->afterReturningAdvice as $advice) {
$advice($target, $method, $args, $result);
}
return $result;
} catch (\Throwable $exception) {
// 执行AfterThrowing通知
foreach ($this->afterThrowingAdvice as $advice) {
$advice($target, $method, $args, $exception);
}
throw $exception;
} finally {
// 执行After通知
foreach ($this->afterAdvice as $advice) {
$advice($target, $method, $args);
}
}
}
private function executeAroundAdvice(object $target, string $method, array $args, int $index): mixed
{
if ($index >= count($this->aroundAdvice)) {
return $target->$method(...$args);
}
$advice = $this->aroundAdvice[$index];
$next = fn() => $this->executeAroundAdvice($target, $method, $args, $index + 1);
return $advice($target, $method, $args, $next);
}
}

View File

@@ -0,0 +1,166 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Aop;
/**
* AOP连接点类
* 用于封装方法调用的上下文信息
*/
class JoinPoint
{
private object $target;
private string $method;
private array $arguments;
private ?object $result = null;
private ?\Throwable $exception = null;
private bool $proceed = true;
public function __construct(object $target, string $method, array $arguments = [])
{
$this->target = $target;
$this->method = $method;
$this->arguments = $arguments;
}
/**
* 获取目标对象
*/
public function getTarget(): object
{
return $this->target;
}
/**
* 获取方法名
*/
public function getMethod(): string
{
return $this->method;
}
/**
* 获取方法参数
*/
public function getArguments(): array
{
return $this->arguments;
}
/**
* 获取指定参数
*/
public function getArgument(int $index): mixed
{
return $this->arguments[$index] ?? null;
}
/**
* 设置参数
*/
public function setArgument(int $index, mixed $value): void
{
$this->arguments[$index] = $value;
}
/**
* 获取执行结果
*/
public function getResult(): mixed
{
return $this->result;
}
/**
* 设置执行结果
*/
public function setResult(mixed $result): void
{
$this->result = $result;
}
/**
* 获取异常
*/
public function getException(): ?\Throwable
{
return $this->exception;
}
/**
* 设置异常
*/
public function setException(?\Throwable $exception): void
{
$this->exception = $exception;
}
/**
* 是否继续执行
*/
public function canProceed(): bool
{
return $this->proceed;
}
/**
* 设置是否继续执行
*/
public function setProceed(bool $proceed): void
{
$this->proceed = $proceed;
}
/**
* 执行目标方法
*/
public function proceed(): mixed
{
if (!$this->proceed) {
return $this->result;
}
try {
$result = call_user_func_array([$this->target, $this->method], $this->arguments);
$this->result = $result;
return $result;
} catch (\Throwable $e) {
$this->exception = $e;
throw $e;
}
}
/**
* 获取方法签名
*/
public function getSignature(): string
{
return get_class($this->target) . '::' . $this->method;
}
/**
* 获取目标类名
*/
public function getTargetClass(): string
{
return get_class($this->target);
}
/**
* 是否有异常
*/
public function hasException(): bool
{
return $this->exception !== null;
}
/**
* 清理状态
*/
public function clear(): void
{
$this->result = null;
$this->exception = null;
$this->proceed = true;
}
}

View File

@@ -0,0 +1,240 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Aop;
/**
* AOP切点类
* 定义连接点的匹配规则
*/
class Pointcut
{
private string $expression;
private array $patterns = [];
private string $type;
public function __construct(string $expression, string $type = 'method')
{
$this->expression = $expression;
$this->type = $type;
$this->parseExpression();
}
/**
* 解析表达式
*/
private function parseExpression(): void
{
// 支持多种切点表达式格式
// 1. 注解切点: @annotation(Fendx\Core\Annotation\Cacheable)
// 2. 方法名切点: execution(* UserService.*(..))
// 3. 类名切点: within(Fendx\Service.*)
// 4. 路由切点: @route(/api/*)
if (strpos($this->expression, '@annotation(') === 0) {
$this->parseAnnotationExpression();
} elseif (strpos($this->expression, 'execution(') === 0) {
$this->parseExecutionExpression();
} elseif (strpos($this->expression, 'within(') === 0) {
$this->parseWithinExpression();
} elseif (strpos($this->expression, '@route(') === 0) {
$this->parseRouteExpression();
} else {
// 简单方法名匹配
$this->patterns[] = $this->expression;
}
}
/**
* 解析注解表达式
*/
private function parseAnnotationExpression(): void
{
$annotation = str_replace(['@annotation(', ')'], '', $this->expression);
$this->patterns[] = ['type' => 'annotation', 'value' => $annotation];
}
/**
* 解析执行表达式
*/
private function parseExecutionExpression(): void
{
$pattern = str_replace(['execution(', ')'], '', $this->expression);
// 转换通配符为正则表达式
$pattern = str_replace('*', '.*', $pattern);
$pattern = str_replace('..', '.*', $pattern);
$this->patterns[] = ['type' => 'execution', 'pattern' => '/^' . $pattern . '$/'];
}
/**
* 解析类内表达式
*/
private function parseWithinExpression(): void
{
$className = str_replace(['within(', ')'], '', $this->expression);
$className = str_replace('*', '.*', $className);
$this->patterns[] = ['type' => 'within', 'pattern' => '/^' . $className . '$/'];
}
/**
* 解析路由表达式
*/
private function parseRouteExpression(): void
{
$route = str_replace(['@route(', ')'], '', $this->expression);
$route = str_replace('*', '.*', $route);
$this->patterns[] = ['type' => 'route', 'pattern' => '/^' . $route . '$/'];
}
/**
* 匹配连接点
*/
public function matches(object $target, string $method, array $context = []): bool
{
foreach ($this->patterns as $pattern) {
if ($this->matchPattern($pattern, $target, $method, $context)) {
return true;
}
}
return false;
}
/**
* 匹配单个模式
*/
private function matchPattern(array $pattern, object $target, string $method, array $context): bool
{
$type = $pattern['type'] ?? 'simple';
switch ($type) {
case 'annotation':
return $this->matchAnnotation($pattern['value'], $target, $method);
case 'execution':
return $this->matchExecution($pattern['pattern'], $target, $method);
case 'within':
return $this->matchWithin($pattern['pattern'], $target);
case 'route':
return $this->matchRoute($pattern['pattern'], $context);
default:
return $this->matchSimple($pattern, $target, $method);
}
}
/**
* 匹配注解
*/
private function matchAnnotation(string $annotation, object $target, string $method): bool
{
$reflection = new \ReflectionClass($target);
$methodReflection = $reflection->getMethod($method);
// 检查方法注解
$methodAnnotations = $this->getAnnotations($methodReflection);
if (in_array($annotation, $methodAnnotations)) {
return true;
}
// 检查类注解
$classAnnotations = $this->getAnnotations($reflection);
return in_array($annotation, $classAnnotations);
}
/**
* 匹配执行表达式
*/
private function matchExecution(string $pattern, object $target, string $method): bool
{
$signature = get_class($target) . '::' . $method;
return preg_match($pattern, $signature);
}
/**
* 匹配类内表达式
*/
private function matchWithin(string $pattern, object $target): bool
{
$className = get_class($target);
return preg_match($pattern, $className);
}
/**
* 匹配路由表达式
*/
private function matchRoute(string $pattern, array $context): bool
{
if (!isset($context['route'])) {
return false;
}
return preg_match($pattern, $context['route']);
}
/**
* 简单匹配
*/
private function matchSimple(string $pattern, object $target, string $method): bool
{
// 支持通配符匹配
if (strpos($pattern, '*') !== false) {
$regex = str_replace('*', '.*', $pattern);
return preg_match('/^' . $regex . '$/', $method);
}
return $pattern === $method;
}
/**
* 获取注解
*/
private function getAnnotations(\ReflectionClass|\ReflectionMethod $reflection): array
{
$annotations = [];
// 这里需要根据实际使用的注解库来实现
// 例如使用PHP8的Attributes或其他注解库
if (method_exists($reflection, 'getAttributes')) {
$attributes = $reflection->getAttributes();
foreach ($attributes as $attribute) {
$annotations[] = $attribute->getName();
}
}
// 支持PHPDoc注释解析
$docComment = $reflection->getDocComment();
if ($docComment) {
preg_match_all('/@(\w+)/', $docComment, $matches);
$annotations = array_merge($annotations, $matches[1]);
}
return array_unique($annotations);
}
/**
* 获取表达式
*/
public function getExpression(): string
{
return $this->expression;
}
/**
* 获取类型
*/
public function getType(): string
{
return $this->type;
}
/**
* 获取模式
*/
public function getPatterns(): array
{
return $this->patterns;
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Config;
use Fendx\Common\Exception\BusinessException;
final class Config
{
private static array $data = [];
private static array $cache = [];
public static function load(array $config): void
{
self::$data = $config;
self::$cache = [];
}
public static function get(string $key, mixed $default = null): mixed
{
if ($key === '') {
return $default;
}
// 使用缓存提高性能
if (isset(self::$cache[$key])) {
return self::$cache[$key];
}
$parts = explode('.', $key);
$cur = self::$data;
foreach ($parts as $part) {
if (!is_array($cur) || !array_key_exists($part, $cur)) {
self::$cache[$key] = $default;
return $default;
}
$cur = $cur[$part];
}
self::$cache[$key] = $cur;
return $cur;
}
public static function set(string $key, mixed $value): void
{
$parts = explode('.', $key);
$cur = &self::$data;
foreach ($parts as $part) {
if (!is_array($cur)) {
$cur = [];
}
if (!array_key_exists($part, $cur)) {
$cur[$part] = [];
}
$cur = &$cur[$part];
}
$cur = $value;
// 清除相关缓存
foreach (array_keys(self::$cache) as $cacheKey) {
if (str_starts_with($cacheKey, $key)) {
unset(self::$cache[$cacheKey]);
}
}
}
public static function has(string $key): bool
{
return self::get($key) !== null;
}
public static function all(): array
{
return self::$data;
}
public static function clear(): void
{
self::$data = [];
self::$cache = [];
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Fendx\Core;
final class Config
{
private static array $data = [];
public static function load(array $config): void
{
self::$data = $config;
}
public static function get(string $key, mixed $default = null): mixed
{
if ($key === '') {
return $default;
}
$parts = explode('.', $key);
$cur = self::$data;
foreach ($parts as $part) {
if (!is_array($cur) || !array_key_exists($part, $cur)) {
return $default;
}
$cur = $cur[$part];
}
return $cur;
}
}

View File

@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Container;
use Fendx\Common\Exception\BusinessException;
use ReflectionClass;
use ReflectionException;
final class Container
{
private static ?Container $instance = null;
private array $bindings = [];
private array $instances = [];
private array $singletons = [];
private function __construct()
{
}
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function bind(string $abstract, mixed $concrete = null, bool $singleton = false): void
{
if ($concrete === null) {
$concrete = $abstract;
}
$this->bindings[$abstract] = $concrete;
$this->singletons[$abstract] = $singleton;
}
public function singleton(string $abstract, mixed $concrete = null): void
{
$this->bind($abstract, $concrete, true);
}
public function make(string $abstract, array $parameters = []): mixed
{
// 如果已存在实例且为单例
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
$concrete = $this->bindings[$abstract] ?? $abstract;
// 如果是闭包
if ($concrete instanceof \Closure) {
$object = $concrete($this, $parameters);
} else {
$object = $this->build($concrete, $parameters);
}
// 如果是单例,缓存实例
if (isset($this->singletons[$abstract]) && $this->singletons[$abstract]) {
$this->instances[$abstract] = $object;
}
return $object;
}
private function build(string $concrete, array $parameters): mixed
{
try {
$reflector = new ReflectionClass($concrete);
} catch (ReflectionException $e) {
throw new BusinessException(500, 'CLASS_NOT_FOUND', ['class' => $concrete]);
}
if (!$reflector->isInstantiable()) {
throw new BusinessException(500, 'CLASS_NOT_INSTANTIABLE', ['class' => $concrete]);
}
$constructor = $reflector->getConstructor();
if ($constructor === null) {
return new $concrete();
}
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies, $parameters);
return $reflector->newInstanceArgs($instances);
}
private function getDependencies(array $parameters, array $primitives): array
{
$dependencies = [];
foreach ($parameters as $parameter) {
$type = $parameter->getType();
if ($type === null || $type->isBuiltin()) {
$name = $parameter->getName();
$dependencies[] = $primitives[$name] ?? $parameter->getDefaultValue();
} else {
$dependencies[] = $this->make($type->getName());
}
}
return $dependencies;
}
public function has(string $abstract): bool
{
return isset($this->bindings[$abstract]);
}
public function forget(string $abstract): void
{
unset($this->bindings[$abstract], $this->instances[$abstract], $this->singletons[$abstract]);
}
public function flush(): void
{
$this->bindings = [];
$this->instances = [];
$this->singletons = [];
}
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Context;
final class Context
{
private static ?string $traceId = null;
private static array $data = [];
private static array $user = [];
public static function init(): void
{
self::$traceId = self::generateTraceId();
self::$data = [];
self::$user = [];
}
public static function getTraceId(): string
{
return self::$traceId ?? self::generateTraceId();
}
public static function setTraceId(string $traceId): void
{
self::$traceId = $traceId;
}
public static function set(string $key, mixed $value): void
{
self::$data[$key] = $value;
}
public static function get(string $key, mixed $default = null): mixed
{
return self::$data[$key] ?? $default;
}
public static function has(string $key): bool
{
return isset(self::$data[$key]);
}
public static function remove(string $key): void
{
unset(self::$data[$key]);
}
public static function setUser(array $user): void
{
self::$user = $user;
}
public static function getUser(): array
{
return self::$user;
}
public static function getUserId(): mixed
{
return self::$user['id'] ?? null;
}
public static function getUsername(): string
{
return self::$user['username'] ?? '';
}
public static function clear(): void
{
self::$traceId = null;
self::$data = [];
self::$user = [];
}
public static function all(): array
{
return [
'traceId' => self::getTraceId(),
'data' => self::$data,
'user' => self::$user,
];
}
private static function generateTraceId(): string
{
return uniqid('trace_', true) . '_' . bin2hex(random_bytes(8));
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Event;
use Fendx\Core\Container\Container;
final class EventDispatcher
{
private static ?self $instance = null;
private array $listeners = [];
private Container $container;
private function __construct(Container $container)
{
$this->container = $container;
}
public static function getInstance(Container $container): self
{
if (self::$instance === null) {
self::$instance = new self($container);
}
return self::$instance;
}
public function listen(string $event, mixed $listener): void
{
$this->listeners[$event][] = $listener;
}
public function dispatch(object $event): void
{
$eventName = get_class($event);
if (!isset($this->listeners[$eventName])) {
return;
}
foreach ($this->listeners[$eventName] as $listener) {
if ($listener instanceof \Closure) {
$listener($event);
} elseif (is_string($listener)) {
$instance = $this->container->make($listener);
if (method_exists($instance, 'handle')) {
$instance->handle($event);
}
} elseif (is_object($listener) && method_exists($listener, 'handle')) {
$listener->handle($event);
}
}
}
public function hasListeners(string $event): bool
{
return isset($this->listeners[$event]) && !empty($this->listeners[$event]);
}
public function getListeners(string $event): array
{
return $this->listeners[$event] ?? [];
}
public function removeListener(string $event, mixed $listener): void
{
if (!isset($this->listeners[$event])) {
return;
}
$this->listeners[$event] = array_filter(
$this->listeners[$event],
fn($l) => $l !== $listener
);
}
public function clear(): void
{
$this->listeners = [];
}
}

View File

@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace Fendx\Core\Scanner;
use ReflectionClass;
use ReflectionException;
use Fendx\Core\Annotation\Controller;
use Fendx\Core\Annotation\Service;
use Fendx\Core\Annotation\Dao;
use Fendx\Core\Container\Container;
final class AnnotationScanner
{
private Container $container;
private array $scannedClasses = [];
public function __construct(Container $container)
{
$this->container = $container;
}
public function scan(string $scanPath): void
{
$this->scanDirectory($scanPath);
$this->processDependencies();
}
private function scanDirectory(string $path): void
{
if (!is_dir($path)) {
return;
}
$files = glob($path . '/**/*.php');
foreach ($files as $file) {
$this->scanFile($file);
}
}
private function scanFile(string $file): void
{
$className = $this->getClassNameFromFile($file);
if ($className === null || class_exists($className) === false) {
return;
}
$this->scannedClasses[] = $className;
$this->processClassAnnotations($className);
}
private function getClassNameFromFile(string $file): ?string
{
$content = file_get_contents($file);
if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
$namespace = trim($matches[1]);
$className = basename($file, '.php');
return $namespace . '\\' . $className;
}
return null;
}
private function processClassAnnotations(string $className): void
{
try {
$reflection = new ReflectionClass($className);
// 处理Controller注解
$controllerAttributes = $reflection->getAttributes(Controller::class);
if (!empty($controllerAttributes)) {
$this->container->singleton($className);
$this->processControllerProperties($reflection);
}
// 处理Service注解
$serviceAttributes = $reflection->getAttributes(Service::class);
if (!empty($serviceAttributes)) {
$attribute = $serviceAttributes[0]->newInstance();
$beanName = $attribute->name ?: $className;
$this->container->singleton($beanName, $className);
$this->processServiceProperties($reflection);
}
// 处理Dao注解
$daoAttributes = $reflection->getAttributes(Dao::class);
if (!empty($daoAttributes)) {
$attribute = $daoAttributes[0]->newInstance();
$beanName = $attribute->name ?: $className;
$this->container->singleton($beanName, $className);
$this->processDaoProperties($reflection);
}
} catch (ReflectionException $e) {
// 记录错误但继续处理其他类
error_log("Failed to scan class $className: " . $e->getMessage());
}
}
private function processControllerProperties(ReflectionClass $reflection): void
{
$properties = $reflection->getProperties();
foreach ($properties as $property) {
$injectAttributes = $property->getAttributes(\Fendx\Core\Annotation\Inject::class);
if (!empty($injectAttributes)) {
$attribute = $injectAttributes[0]->newInstance();
$this->registerPropertyInjection($reflection->getName(), $property->getName(), $attribute->name);
}
}
}
private function processServiceProperties(ReflectionClass $reflection): void
{
$this->processControllerProperties($reflection);
}
private function processDaoProperties(ReflectionClass $reflection): void
{
$this->processControllerProperties($reflection);
}
private function registerPropertyInjection(string $className, string $propertyName, ?string $beanName): void
{
// 这里可以注册属性注入信息,后续在实例化时处理
// 暂时使用容器的make方法来处理依赖注入
}
private function processDependencies(): void
{
// 处理所有扫描到的类的依赖关系
foreach ($this->scannedClasses as $className) {
try {
$this->container->make($className);
} catch (\Exception $e) {
// 某些类可能需要延迟初始化
}
}
}
public function getScannedClasses(): array
{
return $this->scannedClasses;
}
}