first commit
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
# AnnoRoute Attribute Routing
|
||||
|
||||
AnnoRoute is a PHP 8 Attribute-based route registration module built into XinAdmin. Routes are declared via controller annotations, with automatic Sanctum authentication and permission verification integration.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Route Composition
|
||||
|
||||
The final route URL is: `RequestAttribute.routePrefix` + `methodAttribute.route`
|
||||
|
||||
```php
|
||||
#[RequestAttribute('/system/user', 'system.user')]
|
||||
class SysUserController extends BaseController
|
||||
{
|
||||
#[GetRoute('/role', 'role')]
|
||||
public function role(): JsonResponse { }
|
||||
}
|
||||
// Final route: GET /system/user/role
|
||||
```
|
||||
|
||||
The final permission string is: `RequestAttribute.abilitiesPrefix` + `.` + `methodAttribute.authorize`
|
||||
|
||||
```php
|
||||
// abilitiesPrefix = "system.user", authorize = "role"
|
||||
// Final ability: "system.user.role"
|
||||
```
|
||||
|
||||
### Route Registration
|
||||
|
||||
Routes are registered by scanning directories for `*Controller.php` files. In your ServiceProvider:
|
||||
|
||||
```php
|
||||
use Modules\AnnoRoute\AnnoRoute;
|
||||
|
||||
public function boot(AnnoRoute $annoRoute): void
|
||||
{
|
||||
$annoRoute->register(base_path('modules/YourModule/Http/Controllers'));
|
||||
}
|
||||
```
|
||||
|
||||
The scanner reads each controller file, extracts namespace + class name, then uses reflection to find and register attributes. Only classes with `#[RequestAttribute]` are registered.
|
||||
|
||||
## Attribute Reference
|
||||
|
||||
### Class-Level: `#[RequestAttribute]`
|
||||
|
||||
Defines the shared prefix and auth configuration for all routes within the controller.
|
||||
|
||||
```php
|
||||
use Modules\AnnoRoute\Attribute\RequestAttribute;
|
||||
|
||||
#[RequestAttribute(
|
||||
routePrefix: '/admin/user',
|
||||
abilitiesPrefix: 'admin.user',
|
||||
middleware: 'log', // string or array — additional middleware for all routes
|
||||
authGuard: 'admin', // optional — Sanctum guard provider
|
||||
)]
|
||||
class UserController { }
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-------------------|------------------|---------|--------------------------------------------------|
|
||||
| `routePrefix` | `string` | `''` | URL prefix shared by all routes in this controller |
|
||||
| `abilitiesPrefix` | `string` | `''` | Prefix for permission ability strings |
|
||||
| `middleware` | `string\|array` | `''` | Additional middleware applied to every route |
|
||||
| `authGuard` | `?string` | `null` | Sanctum auth guard provider name |
|
||||
|
||||
### Method-Level Attributes
|
||||
|
||||
#### `#[GetRoute]` / `#[PostRoute]` / `#[PutRoute]` / `#[DeleteRoute]`
|
||||
|
||||
All method attributes share an identical constructor signature:
|
||||
|
||||
```php
|
||||
use Modules\AnnoRoute\Attribute\{GetRoute, PostRoute, PutRoute, DeleteRoute};
|
||||
|
||||
#[GetRoute(
|
||||
route: '/{id}',
|
||||
authorize: 'update',
|
||||
middleware: 'throttle:10,1',
|
||||
where: ['id' => '[0-9]+'],
|
||||
)]
|
||||
public function show(int $id): JsonResponse { }
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|--------------|------------------|---------|----------------------------------------------------------------|
|
||||
| `route` | `string` | `''` | Route path appended to the class `routePrefix` |
|
||||
| `authorize` | `string\|bool` | `true` | Permission ability string; `false` disables auth entirely |
|
||||
| `middleware` | `string\|array` | `''` | Route-specific middleware |
|
||||
| `where` | `array` | `[]` | Regex constraints for route parameters, e.g. `['id' => '[0-9]+']` |
|
||||
|
||||
### Authorization Behavior
|
||||
|
||||
When `authorize` is not `false` or empty, these middleware are automatically added:
|
||||
|
||||
1. `auth:sanctum` — Sanctum authentication
|
||||
2. `authGuard:{guard}` — guard check (or `authGuard` without a specific guard)
|
||||
3. `abilities:{prefix}.{authorize}` — permission check
|
||||
|
||||
When `authorize` is `false`, no auth middleware is applied (public route).
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic CRUD Controller
|
||||
|
||||
```php
|
||||
use Modules\AnnoRoute\Attribute\{RequestAttribute, GetRoute, PostRoute, PutRoute, DeleteRoute};
|
||||
|
||||
#[RequestAttribute('/system/dict', 'system.dict')]
|
||||
class SysDictController extends BaseController
|
||||
{
|
||||
#[GetRoute(authorize: 'query')]
|
||||
public function query(Request $request): JsonResponse
|
||||
{
|
||||
// GET /system/dict — ability: system.dict.query
|
||||
return $this->success($data);
|
||||
}
|
||||
|
||||
#[PostRoute(authorize: 'create')]
|
||||
public function create(FormRequest $request): JsonResponse
|
||||
{
|
||||
// POST /system/dict — ability: system.dict.create
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
#[PutRoute(route: '/{id}', authorize: 'update', where: ['id' => '[0-9]+'])]
|
||||
public function update(int $id, FormRequest $request): JsonResponse
|
||||
{
|
||||
// PUT /system/dict/123 — ability: system.dict.update
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
#[DeleteRoute(route: '/{id}', authorize: 'delete', where: ['id' => '[0-9]+'])]
|
||||
public function delete(int $id): JsonResponse
|
||||
{
|
||||
// DELETE /system/dict/123 — ability: system.dict.delete
|
||||
return $this->success();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Public Routes (No Auth)
|
||||
|
||||
```php
|
||||
#[GetRoute('/public-data', authorize: false)]
|
||||
public function publicData(): JsonResponse
|
||||
{
|
||||
return $this->success($data);
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Route Path
|
||||
|
||||
When the method route is empty string (default), the controller routePrefix is the full route:
|
||||
|
||||
```php
|
||||
#[RequestAttribute('/dashboard', 'dashboard')]
|
||||
class DashboardController extends BaseController
|
||||
{
|
||||
#[GetRoute(authorize: 'index')]
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
// GET /dashboard — ability: dashboard.index
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Additional Middleware
|
||||
|
||||
```php
|
||||
#[GetRoute('/export', 'export', middleware: 'throttle:5,1')]
|
||||
public function export(): JsonResponse { }
|
||||
|
||||
#[PostRoute('/batch', 'batch', middleware: ['log', 'transaction'])]
|
||||
public function batchProcess(): JsonResponse { }
|
||||
```
|
||||
|
||||
### Route with Multiple Parameters
|
||||
|
||||
```php
|
||||
#[GetRoute(
|
||||
route: '/{deptId}/user/{userId}',
|
||||
authorize: 'detail',
|
||||
where: ['deptId' => '[0-9]+', 'userId' => '[0-9]+'],
|
||||
)]
|
||||
public function detail(int $deptId, int $userId): JsonResponse { }
|
||||
```
|
||||
|
||||
## Key Rules
|
||||
|
||||
- Namespace: `Modules\AnnoRoute\Attribute\`
|
||||
- Controller classes MUST use `#[RequestAttribute]` to be discovered; methods are only registered when the class has this attribute
|
||||
- Method attributes: `GetRoute`, `PostRoute`, `PutRoute`, `DeleteRoute`
|
||||
- Route registration: `AnnoRoute->register(path)` in ServiceProvider `boot()`
|
||||
- Scanner only looks for `*Controller.php` files
|
||||
- All auth middleware is auto-assembled — only declare `authorize` strings, not auth middleware directly
|
||||
- Inherit `BaseController` for the `success()` / `error()` response helpers
|
||||
- Controllers must return `Illuminate\Http\JsonResponse`
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
### Forgetting #[RequestAttribute] on the Class
|
||||
|
||||
If the class attribute is missing, no routes from that controller will be registered — regardless of method attributes.
|
||||
|
||||
### Duplicate Route Prefix
|
||||
|
||||
The final route is `routePrefix + route`. Convention is leading slash on both — they concatenate directly (no double slash).
|
||||
|
||||
### authorize vs abilitiesPrefix
|
||||
|
||||
The full permission string is `abilitiesPrefix.authorize`. If `abilitiesPrefix` is empty, the raw `authorize` value is used. Omitting `abilitiesPrefix` means you must pass the full ability string in each method's `authorize`.
|
||||
Reference in New Issue
Block a user