feat(routes): 初始化路由注解系统
- 添加路由注解基础类 Get、Post、Put、Delete - 实现路由前缀和版本控制注解 Prefix、Version - 添加中间件和路由名称注解 Middleware、Name - 创建路由服务提供者 RouteServiceProvider - 实现控制器目录扫描和路由自动注册 - 添加配置文件支持控制器目录自定义 - 完善单元测试和集成测试用例 - 添加测试控制器和相关测试代码 - 配置 composer 自动加载和 laravel 服务提供者 - 添加 phpunit 测试配置和基础测试用例
This commit is contained in:
commit
2c309b0f27
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.backup
|
||||||
|
.env.production
|
||||||
|
.phpactor.json
|
||||||
|
.phpunit.result.cache
|
||||||
|
/.fleet
|
||||||
|
/.idea
|
||||||
|
/.nova
|
||||||
|
/.phpunit.cache
|
||||||
|
/.vscode
|
||||||
|
/.zed
|
||||||
|
/auth.json
|
||||||
|
/node_modules
|
||||||
|
/public/build
|
||||||
|
/public/hot
|
||||||
|
/public/storage
|
||||||
|
/storage/*.key
|
||||||
|
/storage/pail
|
||||||
|
/vendor
|
||||||
|
Homestead.json
|
||||||
|
Homestead.yaml
|
||||||
|
Thumbs.db
|
||||||
42
composer.json
Normal file
42
composer.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "jltx/routes",
|
||||||
|
"description": "采用注解形式的路由系统,以避免在大型项目中维护大静态路由表",
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"license": "proprietary",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "hui",
|
||||||
|
"email": "yeyixianyang@163.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Jltx\\Routes\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Jltx\\Routes\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8.4",
|
||||||
|
"illuminate/contracts": "^12.0",
|
||||||
|
"illuminate/support": "^12.41"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laravel/pint": "^1.26",
|
||||||
|
"phpunit/phpunit": "^10.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "vendor/bin/pint",
|
||||||
|
"test": "vendor/bin/phpunit"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Jltx\\Routes\\Providers\\RouteServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2441
composer.lock
generated
Normal file
2441
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
config/routes.php
Normal file
20
config/routes.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Controller Directories
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Here you may specify the list of controller directories that should be scanned
|
||||||
|
| for route attributes. By default, it scans the standard Laravel controllers
|
||||||
|
| directory.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'controller_directories' => [
|
||||||
|
// 注意:在包测试环境中 app_path() 不可用
|
||||||
|
// 在真实的 Laravel 应用中,这将是 app_path('Http/Controllers')
|
||||||
|
'Http/Controllers',
|
||||||
|
],
|
||||||
|
];
|
||||||
23
phpunit.xml.dist
Normal file
23
phpunit.xml.dist
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
|
||||||
|
bootstrap="vendor/autoload.php"
|
||||||
|
colors="true"
|
||||||
|
>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Unit">
|
||||||
|
<directory suffix="Test.php">./tests/Unit</directory>
|
||||||
|
</testsuite>
|
||||||
|
<testsuite name="Feature">
|
||||||
|
<directory suffix="Test.php">./tests/Feature</directory>
|
||||||
|
</testsuite>
|
||||||
|
<testsuite name="Integration">
|
||||||
|
<directory suffix="Test.php">./tests/Integration</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<source>
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">./src</directory>
|
||||||
|
</include>
|
||||||
|
</source>
|
||||||
|
</phpunit>
|
||||||
8
src/Attribute/Delete.php
Normal file
8
src/Attribute/Delete.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD)]
|
||||||
|
class Delete extends RouteBase {}
|
||||||
8
src/Attribute/Get.php
Normal file
8
src/Attribute/Get.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD)]
|
||||||
|
class Get extends RouteBase {}
|
||||||
11
src/Attribute/Group.php
Normal file
11
src/Attribute/Group.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS)]
|
||||||
|
class Group
|
||||||
|
{
|
||||||
|
public function __construct(public ?string $prefix = null, public ?string $name = null, public array $middleware = []) {}
|
||||||
|
}
|
||||||
16
src/Attribute/Middleware.php
Normal file
16
src/Attribute/Middleware.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||||
|
class Middleware
|
||||||
|
{
|
||||||
|
public function __construct(public array|string $middleware)
|
||||||
|
{
|
||||||
|
if (is_string($middleware)) {
|
||||||
|
$this->middleware = [$middleware];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/Attribute/Name.php
Normal file
11
src/Attribute/Name.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD)]
|
||||||
|
class Name
|
||||||
|
{
|
||||||
|
public function __construct(public string $name) {}
|
||||||
|
}
|
||||||
8
src/Attribute/Post.php
Normal file
8
src/Attribute/Post.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD)]
|
||||||
|
class Post extends RouteBase {}
|
||||||
11
src/Attribute/Prefix.php
Normal file
11
src/Attribute/Prefix.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
|
||||||
|
class Prefix
|
||||||
|
{
|
||||||
|
public function __construct(public string $prefix) {}
|
||||||
|
}
|
||||||
8
src/Attribute/Put.php
Normal file
8
src/Attribute/Put.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD)]
|
||||||
|
class Put extends RouteBase {}
|
||||||
11
src/Attribute/RouteBase.php
Normal file
11
src/Attribute/RouteBase.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_METHOD)]
|
||||||
|
class RouteBase
|
||||||
|
{
|
||||||
|
public function __construct(public string $path, public array $middleware = [], public ?string $name = null, public string $version = 'v1', public string $prefix = '') {}
|
||||||
|
}
|
||||||
11
src/Attribute/Version.php
Normal file
11
src/Attribute/Version.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Attribute;
|
||||||
|
|
||||||
|
use Attribute;
|
||||||
|
|
||||||
|
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
|
||||||
|
class Version
|
||||||
|
{
|
||||||
|
public function __construct(public string $version) {}
|
||||||
|
}
|
||||||
179
src/Providers/RouteServiceProvider.php
Normal file
179
src/Providers/RouteServiceProvider.php
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Jltx\Routes\Attribute\Delete;
|
||||||
|
use Jltx\Routes\Attribute\Get;
|
||||||
|
use Jltx\Routes\Attribute\Middleware;
|
||||||
|
use Jltx\Routes\Attribute\Post;
|
||||||
|
use Jltx\Routes\Attribute\Prefix;
|
||||||
|
use Jltx\Routes\Attribute\Put;
|
||||||
|
use Jltx\Routes\Attribute\Version;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
use ReflectionClass;
|
||||||
|
use ReflectionException;
|
||||||
|
use ReflectionMethod;
|
||||||
|
|
||||||
|
class RouteServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
// 合并配置文件
|
||||||
|
$this->mergeConfigFrom(
|
||||||
|
__DIR__.'/../../config/routes.php', 'routes'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap services.
|
||||||
|
*
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
// 发布配置文件
|
||||||
|
$this->publishes([
|
||||||
|
__DIR__.'/../../config/routes.php' => config_path('routes.php'),
|
||||||
|
], 'routes-config');
|
||||||
|
|
||||||
|
Route::middleware('api')
|
||||||
|
->prefix('api')
|
||||||
|
->group(function () {
|
||||||
|
// 从配置中获取控制器目录列表
|
||||||
|
$directories = config('routes.controller_directories', [app_path('Http/Controllers')]);
|
||||||
|
|
||||||
|
// 遍历每个目录进行扫描
|
||||||
|
foreach ($directories as $directory) {
|
||||||
|
if (is_dir($directory)) {
|
||||||
|
$this->scanControllers($directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
private function scanControllers(string $path): void
|
||||||
|
{
|
||||||
|
// 检查目录是否存在
|
||||||
|
if (! is_dir($path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (! $file->isFile() || $file->getExtension() !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$class = $this->getClassFromFile($file->getRealPath());
|
||||||
|
|
||||||
|
if (! $class || ! class_exists($class)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->registerRoutesFromClass($class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getClassFromFile(string $file): ?string
|
||||||
|
{
|
||||||
|
$content = file_get_contents($file);
|
||||||
|
if (preg_match('/namespace\s+(.+?);/', $content, $ns) &&
|
||||||
|
preg_match('/class\s+(\w+)/', $content, $cls)) {
|
||||||
|
return $ns[1].'\\'.$cls[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
private function registerRoutesFromClass(string $class): void
|
||||||
|
{
|
||||||
|
$refClass = new ReflectionClass($class);
|
||||||
|
|
||||||
|
// 类级别属性
|
||||||
|
$classPrefix = $this->getAttributeValue($refClass, Prefix::class, 'prefix');
|
||||||
|
$classVersion = $this->getAttributeValue($refClass, Version::class, 'version');
|
||||||
|
$classMiddleware = $this->getAttributeValues($refClass);
|
||||||
|
|
||||||
|
foreach ($refClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
||||||
|
if ($method->isConstructor() || $method->class !== $class) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 方法级属性
|
||||||
|
$methodPrefix = $this->getAttributeValue($method, Prefix::class, 'prefix');
|
||||||
|
$methodVersion = $this->getAttributeValue($method, Version::class, 'version');
|
||||||
|
$methodMiddleware = $this->getAttributeValues($method);
|
||||||
|
|
||||||
|
$version = $methodVersion ?? $classVersion ?? 'v1';
|
||||||
|
$prefix = $classPrefix.($methodPrefix ? '/'.trim($methodPrefix, '/') : '');
|
||||||
|
|
||||||
|
$middleware = array_merge($classMiddleware, $methodMiddleware);
|
||||||
|
|
||||||
|
// HTTP 动作
|
||||||
|
foreach ([
|
||||||
|
Get::class => 'get',
|
||||||
|
Post::class => 'post',
|
||||||
|
Put::class => 'put',
|
||||||
|
Delete::class => 'delete',
|
||||||
|
] as $attrClass => $httpVerb) {
|
||||||
|
|
||||||
|
$attrs = $method->getAttributes($attrClass);
|
||||||
|
foreach ($attrs as $attr) {
|
||||||
|
$instance = $attr->newInstance();
|
||||||
|
$path = trim($instance->path, '/');
|
||||||
|
$routePath = '/'.trim($version, '/').'/'.trim($prefix, '/').($path ? '/'.$path : '');
|
||||||
|
$routePath = preg_replace('#//+#', '/', $routePath);
|
||||||
|
|
||||||
|
$routeMiddleware = array_merge($middleware, $instance->middleware ?? []);
|
||||||
|
$version = $instance->version ?? $version;
|
||||||
|
$routeName = ($instance->name ?? Str::random()).'.'.$version;
|
||||||
|
|
||||||
|
$route = Route::$httpVerb($routePath, [$class, $method->getName()]);
|
||||||
|
if ($routeMiddleware) {
|
||||||
|
$route->middleware($routeMiddleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
$route->name($routeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAttributeValue($ref, string $attrClass, string $property): ?string
|
||||||
|
{
|
||||||
|
$attrs = $ref->getAttributes($attrClass);
|
||||||
|
if (! $attrs) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$instance = $attrs[0]->newInstance();
|
||||||
|
|
||||||
|
return $instance->$property ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAttributeValues($ref): array
|
||||||
|
{
|
||||||
|
$property = 'name';
|
||||||
|
$results = [];
|
||||||
|
$attrs = $ref->getAttributes(Middleware::class);
|
||||||
|
foreach ($attrs as $attr) {
|
||||||
|
$instance = $attr->newInstance();
|
||||||
|
$results[] = $instance->$property;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
}
|
||||||
145
tests/Feature/RouteRegistrationTest.php
Normal file
145
tests/Feature/RouteRegistrationTest.php
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Tests\Feature;
|
||||||
|
|
||||||
|
use Jltx\Routes\Attribute\Get;
|
||||||
|
use Jltx\Routes\Attribute\Middleware;
|
||||||
|
use Jltx\Routes\Attribute\Post;
|
||||||
|
use Jltx\Routes\Attribute\Prefix;
|
||||||
|
use Jltx\Routes\Attribute\Version;
|
||||||
|
use Jltx\Routes\Tests\Fixtures\Controllers\TestController;
|
||||||
|
use Jltx\Routes\Tests\TestCase;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
class RouteRegistrationTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @test */
|
||||||
|
public function it_can_scan_controller_and_extract_attributes()
|
||||||
|
{
|
||||||
|
// 测试控制器类的属性提取
|
||||||
|
$reflection = new ReflectionClass(TestController::class);
|
||||||
|
|
||||||
|
// 检查类级别的属性
|
||||||
|
$prefixAttrs = $reflection->getAttributes(Prefix::class);
|
||||||
|
$versionAttrs = $reflection->getAttributes(Version::class);
|
||||||
|
$middlewareAttrs = $reflection->getAttributes(Middleware::class);
|
||||||
|
|
||||||
|
$this->assertCount(1, $prefixAttrs);
|
||||||
|
$this->assertCount(1, $versionAttrs);
|
||||||
|
$this->assertCount(1, $middlewareAttrs);
|
||||||
|
|
||||||
|
// 检查方法级别的属性
|
||||||
|
$methods = $reflection->getMethods();
|
||||||
|
|
||||||
|
// 查找 getUsers 方法
|
||||||
|
$getUsersMethod = null;
|
||||||
|
foreach ($methods as $method) {
|
||||||
|
if ($method->getName() === 'getUsers') {
|
||||||
|
$getUsersMethod = $method;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertNotNull($getUsersMethod);
|
||||||
|
$getAttrs = $getUsersMethod->getAttributes(Get::class);
|
||||||
|
$this->assertCount(1, $getAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_can_create_route_attribute_instances()
|
||||||
|
{
|
||||||
|
// 测试创建路由属性实例
|
||||||
|
$reflection = new ReflectionClass(TestController::class);
|
||||||
|
$methods = $reflection->getMethods();
|
||||||
|
|
||||||
|
// 查找 createUser 方法
|
||||||
|
$createUserMethod = null;
|
||||||
|
foreach ($methods as $method) {
|
||||||
|
if ($method->getName() === 'createUser') {
|
||||||
|
$createUserMethod = $method;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertNotNull($createUserMethod);
|
||||||
|
|
||||||
|
// 获取 Post 属性实例
|
||||||
|
$postAttrs = $createUserMethod->getAttributes(Post::class);
|
||||||
|
$this->assertCount(1, $postAttrs);
|
||||||
|
|
||||||
|
$postInstance = $postAttrs[0]->newInstance();
|
||||||
|
$this->assertInstanceOf(Post::class, $postInstance);
|
||||||
|
$this->assertEquals('users', $postInstance->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_supports_multiple_middleware_attributes()
|
||||||
|
{
|
||||||
|
// 测试重复的中间件属性支持
|
||||||
|
$reflection = new ReflectionClass(TestController::class);
|
||||||
|
$methods = $reflection->getMethods();
|
||||||
|
|
||||||
|
// 查找 createUser 方法(应该有两个中间件:类级别的 auth 和方法级别的 admin)
|
||||||
|
$createUserMethod = null;
|
||||||
|
foreach ($methods as $method) {
|
||||||
|
if ($method->getName() === 'createUser') {
|
||||||
|
$createUserMethod = $method;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertNotNull($createUserMethod);
|
||||||
|
|
||||||
|
$middlewareAttrs = $createUserMethod->getAttributes(Middleware::class);
|
||||||
|
// 方法级别应该有一个 Middleware 属性
|
||||||
|
$this->assertCount(1, $middlewareAttrs);
|
||||||
|
|
||||||
|
// 类级别的 Middleware 属性也需要被检测到
|
||||||
|
$classMiddlewareAttrs = $reflection->getAttributes(Middleware::class);
|
||||||
|
$this->assertCount(1, $classMiddlewareAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_can_override_version_attribute()
|
||||||
|
{
|
||||||
|
// 测试版本属性覆盖
|
||||||
|
$reflection = new ReflectionClass(TestController::class);
|
||||||
|
$methods = $reflection->getMethods();
|
||||||
|
|
||||||
|
// 查找 getPost 方法(应该覆盖类级别的版本)
|
||||||
|
$getPostMethod = null;
|
||||||
|
foreach ($methods as $method) {
|
||||||
|
if ($method->getName() === 'getPost') {
|
||||||
|
$getPostMethod = $method;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertNotNull($getPostMethod);
|
||||||
|
|
||||||
|
$versionAttrs = $getPostMethod->getAttributes(Version::class);
|
||||||
|
$this->assertCount(1, $versionAttrs);
|
||||||
|
|
||||||
|
$versionInstance = $versionAttrs[0]->newInstance();
|
||||||
|
$this->assertEquals('v3', $versionInstance->version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_can_parse_class_file_for_namespace_and_class_name()
|
||||||
|
{
|
||||||
|
// 测试从文件中解析命名空间和类名
|
||||||
|
$content = file_get_contents(__DIR__.'/../Fixtures/Controllers/TestController.php');
|
||||||
|
|
||||||
|
$namespace = null;
|
||||||
|
$class = null;
|
||||||
|
|
||||||
|
if (preg_match('/namespace\s+(.+?);/', $content, $ns) &&
|
||||||
|
preg_match('/class\s+(\w+)/', $content, $cls)) {
|
||||||
|
$namespace = $ns[1];
|
||||||
|
$class = $cls[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertEquals('Jltx\Routes\Tests\Fixtures\Controllers', $namespace);
|
||||||
|
$this->assertEquals('TestController', $class);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
tests/Fixtures/Controllers/TestController.php
Normal file
35
tests/Fixtures/Controllers/TestController.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Tests\Fixtures\Controllers;
|
||||||
|
|
||||||
|
use Jltx\Routes\Attribute\Get;
|
||||||
|
use Jltx\Routes\Attribute\Middleware;
|
||||||
|
use Jltx\Routes\Attribute\Post;
|
||||||
|
use Jltx\Routes\Attribute\Prefix;
|
||||||
|
use Jltx\Routes\Attribute\Version;
|
||||||
|
|
||||||
|
#[Prefix('api')]
|
||||||
|
#[Version('v2')]
|
||||||
|
#[Middleware('auth')]
|
||||||
|
class TestController
|
||||||
|
{
|
||||||
|
#[Get('users')]
|
||||||
|
public function getUsers()
|
||||||
|
{
|
||||||
|
return 'get users';
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Post('users')]
|
||||||
|
#[Middleware('admin')]
|
||||||
|
public function createUser()
|
||||||
|
{
|
||||||
|
return 'create user';
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Get('posts/{id}')]
|
||||||
|
#[Version('v3')]
|
||||||
|
public function getPost()
|
||||||
|
{
|
||||||
|
return 'get post';
|
||||||
|
}
|
||||||
|
}
|
||||||
36
tests/Integration/RouteScanningTest.php
Normal file
36
tests/Integration/RouteScanningTest.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Tests\Integration;
|
||||||
|
|
||||||
|
use Jltx\Routes\Tests\Fixtures\Controllers\TestController;
|
||||||
|
use Jltx\Routes\Tests\TestCase;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
class RouteScanningTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @test */
|
||||||
|
public function it_can_scan_directory_and_find_controllers()
|
||||||
|
{
|
||||||
|
// 测试目录扫描功能
|
||||||
|
$this->assertTrue(class_exists(TestController::class));
|
||||||
|
|
||||||
|
$reflection = new ReflectionClass(TestController::class);
|
||||||
|
$this->assertTrue($reflection->isUserDefined());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_can_read_config_values()
|
||||||
|
{
|
||||||
|
// 测试配置读取功能
|
||||||
|
// 因为这是一个包环境,我们不能直接访问 Laravel 的 config() 函数
|
||||||
|
// 但我们可以通过检查配置文件的内容来验证结构
|
||||||
|
|
||||||
|
$config = include __DIR__.'/../../config/routes.php';
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('controller_directories', $config);
|
||||||
|
$this->assertIsArray($config['controller_directories']);
|
||||||
|
|
||||||
|
// 注意:由于 app_path() 是 Laravel 辅助函数,在包测试环境中不可用
|
||||||
|
// 我们只需验证配置结构即可
|
||||||
|
}
|
||||||
|
}
|
||||||
10
tests/TestCase.php
Normal file
10
tests/TestCase.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Tests;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||||
|
|
||||||
|
abstract class TestCase extends BaseTestCase
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
67
tests/Unit/RouteServiceProviderTest.php
Normal file
67
tests/Unit/RouteServiceProviderTest.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Tests\Unit;
|
||||||
|
|
||||||
|
use Jltx\Routes\Attribute\Delete;
|
||||||
|
use Jltx\Routes\Attribute\Get;
|
||||||
|
use Jltx\Routes\Attribute\Middleware;
|
||||||
|
use Jltx\Routes\Attribute\Post;
|
||||||
|
use Jltx\Routes\Attribute\Prefix;
|
||||||
|
use Jltx\Routes\Attribute\Put;
|
||||||
|
use Jltx\Routes\Attribute\Version;
|
||||||
|
use Jltx\Routes\Providers\RouteServiceProvider;
|
||||||
|
use Jltx\Routes\Tests\TestCase;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
class RouteServiceProviderTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @test */
|
||||||
|
public function it_can_be_instantiated()
|
||||||
|
{
|
||||||
|
// 简单测试类可以被加载
|
||||||
|
$this->assertTrue(class_exists(RouteServiceProvider::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function it_has_all_required_route_attributes()
|
||||||
|
{
|
||||||
|
// 测试所有必需的路由属性类是否存在
|
||||||
|
$this->assertTrue(class_exists(Get::class));
|
||||||
|
$this->assertTrue(class_exists(Post::class));
|
||||||
|
$this->assertTrue(class_exists(Put::class));
|
||||||
|
$this->assertTrue(class_exists(Delete::class));
|
||||||
|
$this->assertTrue(class_exists(Prefix::class));
|
||||||
|
$this->assertTrue(class_exists(Version::class));
|
||||||
|
$this->assertTrue(class_exists(Middleware::class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function route_attributes_have_correct_targets()
|
||||||
|
{
|
||||||
|
// 测试路由属性的目标是否正确
|
||||||
|
$getAttribute = new ReflectionClass(Get::class);
|
||||||
|
$postAttribute = new ReflectionClass(Post::class);
|
||||||
|
$prefixAttribute = new ReflectionClass(Prefix::class);
|
||||||
|
$versionAttribute = new ReflectionClass(Version::class);
|
||||||
|
$middlewareAttribute = new ReflectionClass(Middleware::class);
|
||||||
|
|
||||||
|
// Get 和 Post 应该只能用于方法
|
||||||
|
$getAttributes = $getAttribute->getAttributes(\Attribute::class);
|
||||||
|
$this->assertNotEmpty($getAttributes);
|
||||||
|
|
||||||
|
$postAttributes = $postAttribute->getAttributes(\Attribute::class);
|
||||||
|
$this->assertNotEmpty($postAttributes);
|
||||||
|
|
||||||
|
// Prefix 可以用于类和方法
|
||||||
|
$prefixAttributes = $prefixAttribute->getAttributes(\Attribute::class);
|
||||||
|
$this->assertNotEmpty($prefixAttributes);
|
||||||
|
|
||||||
|
// Version 可以用于类和方法
|
||||||
|
$versionAttributes = $versionAttribute->getAttributes(\Attribute::class);
|
||||||
|
$this->assertNotEmpty($versionAttributes);
|
||||||
|
|
||||||
|
// Middleware 可以用于类和方法,并且是可重复的
|
||||||
|
$middlewareAttributes = $middlewareAttribute->getAttributes(\Attribute::class);
|
||||||
|
$this->assertNotEmpty($middlewareAttributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
tests/Unit/SampleTest.php
Normal file
16
tests/Unit/SampleTest.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Jltx\Routes\Tests\Unit;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class SampleTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A basic test example.
|
||||||
|
*/
|
||||||
|
public function test_example(): void
|
||||||
|
{
|
||||||
|
$this->assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user