Skip to content

Commit 795976e

Browse files
committed
attributes
1 parent ca53b43 commit 795976e

10 files changed

Lines changed: 830 additions & 0 deletions

File tree

README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,159 @@ several routes including one with a variable.
8181
If present, the extra element is merged into the parameters array
8282
before it is passed to the routes closure.
8383

84+
## Attribute-Based Routing
85+
86+
Modern PHP 8+ attribute-based routing allows you to define routes directly on controller methods using PHP attributes, providing a modern alternative to YAML configuration files.
87+
88+
### Basic Usage
89+
90+
#### Simple Route
91+
92+
```php
93+
use Neuron\Routing\Attributes\Get;
94+
95+
class HomeController
96+
{
97+
#[Get('/')]
98+
public function index()
99+
{
100+
return 'Hello World';
101+
}
102+
}
103+
```
104+
105+
#### HTTP Method Attributes
106+
107+
```php
108+
use Neuron\Routing\Attributes\Get;
109+
use Neuron\Routing\Attributes\Post;
110+
use Neuron\Routing\Attributes\Put;
111+
use Neuron\Routing\Attributes\Delete;
112+
113+
class UsersController
114+
{
115+
#[Get('/users')]
116+
public function index() { }
117+
118+
#[Get('/users/:id')]
119+
public function show(int $id) { }
120+
121+
#[Post('/users')]
122+
public function store() { }
123+
124+
#[Put('/users/:id')]
125+
public function update(int $id) { }
126+
127+
#[Delete('/users/:id')]
128+
public function destroy(int $id) { }
129+
}
130+
```
131+
132+
#### Route Names and Filters
133+
134+
```php
135+
#[Get('/admin/users', name: 'admin.users.index', filters: ['auth'])]
136+
public function index() { }
137+
138+
#[Post('/admin/users', name: 'admin.users.store', filters: ['auth', 'csrf'])]
139+
public function store() { }
140+
```
141+
142+
#### Route Groups
143+
144+
Apply common settings to all routes in a controller:
145+
146+
```php
147+
use Neuron\Routing\Attributes\RouteGroup;
148+
use Neuron\Routing\Attributes\Get;
149+
use Neuron\Routing\Attributes\Post;
150+
151+
#[RouteGroup(prefix: '/admin', filters: ['auth'])]
152+
class AdminController
153+
{
154+
#[Get('/dashboard')] // Becomes /admin/dashboard with 'auth' filter
155+
public function dashboard() { }
156+
157+
#[Post('/users', filters: ['csrf'])] // Becomes /admin/users with ['auth', 'csrf'] filters
158+
public function createUser() { }
159+
}
160+
```
161+
162+
#### Multiple Routes on Same Method
163+
164+
```php
165+
#[Get('/api/v1/users')]
166+
#[Get('/api/v2/users')]
167+
public function getUsers()
168+
{
169+
// Handle both API versions
170+
}
171+
```
172+
173+
### Configuration
174+
175+
#### Enable Attribute Routing in MVC Application
176+
177+
Add controller paths to your `config/neuron.yaml`:
178+
179+
```yaml
180+
routing:
181+
controller_paths:
182+
- path: 'src/Controllers'
183+
namespace: 'App\Controllers'
184+
- path: 'src/Admin/Controllers'
185+
namespace: 'App\Admin\Controllers'
186+
```
187+
188+
#### Hybrid Approach (YAML + Attributes)
189+
190+
You can use both YAML routes and attribute routes together:
191+
192+
- **YAML routes**: Legacy routes, package-provided routes
193+
- **Attribute routes**: New application routes
194+
195+
The MVC Application will load both automatically.
196+
197+
### Benefits
198+
199+
- **Co-location**: Routes live with controller logic
200+
- **Type Safety**: IDE autocomplete and validation
201+
- **Refactor-Friendly**: Routes update when controllers change
202+
- **No Sync Issues**: Can't have orphaned routes
203+
- **Modern Standard**: Used by Symfony, Laravel, ASP.NET, Spring Boot
204+
- **Self-Documenting**: Route definition IS the documentation
205+
206+
### Performance
207+
208+
Route scanning uses PHP Reflection, which could be slow. For production:
209+
210+
1. Routes are scanned once during application initialization
211+
2. The Router caches RouteMap objects in memory
212+
3. No reflection happens during request handling
213+
4. Future: Add route caching to file for zero-cost production routing
214+
215+
### Migration from YAML
216+
217+
**Before (YAML):**
218+
```yaml
219+
# routes.yaml
220+
home:
221+
method: GET
222+
route: /
223+
controller: App\Controllers\Home@index
224+
```
225+
226+
**After (Attributes):**
227+
```php
228+
class Home
229+
{
230+
#[Get('/', name: 'home')]
231+
public function index() { }
232+
}
233+
```
234+
235+
See `tests/unit/RouteScannerTest.php` for working examples of basic route definition, route groups with prefixes, filter composition, and multiple routes per method.
236+
84237
## Rate Limiting
85238

86239
The routing component includes a powerful rate limiting system with multiple storage backends and flexible configuration options.

src/Routing/Attributes/Delete.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Neuron\Routing\Attributes;
4+
5+
use Attribute;
6+
7+
/**
8+
* DELETE route attribute for defining DELETE HTTP routes on controller methods.
9+
*
10+
* @package Neuron\Routing\Attributes
11+
*
12+
* @example
13+
* ```php
14+
* #[Delete('/users/:id', filters: ['auth', 'csrf'])]
15+
* public function destroy(int $id) { }
16+
* ```
17+
*/
18+
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
19+
class Delete extends Route
20+
{
21+
/**
22+
* @param string $path The route path (e.g., '/users/:id')
23+
* @param string|null $name Optional route name for URL generation
24+
* @param array $filters Array of filter names to apply to this route
25+
*/
26+
public function __construct(
27+
string $path,
28+
?string $name = null,
29+
array $filters = []
30+
)
31+
{
32+
parent::__construct( $path, 'DELETE', $name, $filters );
33+
}
34+
}

src/Routing/Attributes/Get.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Neuron\Routing\Attributes;
4+
5+
use Attribute;
6+
7+
/**
8+
* GET route attribute for defining GET HTTP routes on controller methods.
9+
*
10+
* @package Neuron\Routing\Attributes
11+
*
12+
* @example
13+
* ```php
14+
* #[Get('/users')]
15+
* public function index() { }
16+
*
17+
* #[Get('/users/:id', name: 'users.show', filters: ['auth'])]
18+
* public function show(int $id) { }
19+
* ```
20+
*/
21+
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
22+
class Get extends Route
23+
{
24+
/**
25+
* @param string $path The route path (e.g., '/users/:id')
26+
* @param string|null $name Optional route name for URL generation
27+
* @param array $filters Array of filter names to apply to this route
28+
*/
29+
public function __construct(
30+
string $path,
31+
?string $name = null,
32+
array $filters = []
33+
)
34+
{
35+
parent::__construct( $path, 'GET', $name, $filters );
36+
}
37+
}

src/Routing/Attributes/Post.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Neuron\Routing\Attributes;
4+
5+
use Attribute;
6+
7+
/**
8+
* POST route attribute for defining POST HTTP routes on controller methods.
9+
*
10+
* @package Neuron\Routing\Attributes
11+
*
12+
* @example
13+
* ```php
14+
* #[Post('/users', filters: ['csrf'])]
15+
* public function store() { }
16+
* ```
17+
*/
18+
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
19+
class Post extends Route
20+
{
21+
/**
22+
* @param string $path The route path (e.g., '/users')
23+
* @param string|null $name Optional route name for URL generation
24+
* @param array $filters Array of filter names to apply to this route
25+
*/
26+
public function __construct(
27+
string $path,
28+
?string $name = null,
29+
array $filters = []
30+
)
31+
{
32+
parent::__construct( $path, 'POST', $name, $filters );
33+
}
34+
}

src/Routing/Attributes/Put.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Neuron\Routing\Attributes;
4+
5+
use Attribute;
6+
7+
/**
8+
* PUT route attribute for defining PUT HTTP routes on controller methods.
9+
*
10+
* @package Neuron\Routing\Attributes
11+
*
12+
* @example
13+
* ```php
14+
* #[Put('/users/:id', filters: ['auth', 'csrf'])]
15+
* public function update(int $id) { }
16+
* ```
17+
*/
18+
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
19+
class Put extends Route
20+
{
21+
/**
22+
* @param string $path The route path (e.g., '/users/:id')
23+
* @param string|null $name Optional route name for URL generation
24+
* @param array $filters Array of filter names to apply to this route
25+
*/
26+
public function __construct(
27+
string $path,
28+
?string $name = null,
29+
array $filters = []
30+
)
31+
{
32+
parent::__construct( $path, 'PUT', $name, $filters );
33+
}
34+
}

src/Routing/Attributes/Route.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace Neuron\Routing\Attributes;
4+
5+
use Attribute;
6+
7+
/**
8+
* Base route attribute for defining HTTP routes on controller methods.
9+
*
10+
* This attribute allows routes to be defined directly on controller methods,
11+
* providing a modern alternative to YAML-based route configuration.
12+
*
13+
* @package Neuron\Routing\Attributes
14+
*
15+
* @example
16+
* ```php
17+
* #[Route('/users/:id', method: 'GET', name: 'users.show', filters: ['auth'])]
18+
* public function show(int $id) {
19+
* // Implementation
20+
* }
21+
* ```
22+
*/
23+
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
24+
class Route
25+
{
26+
/**
27+
* @param string $path The route path (e.g., '/users/:id')
28+
* @param string $method HTTP method (GET, POST, PUT, DELETE)
29+
* @param string|null $name Optional route name for URL generation
30+
* @param array $filters Array of filter names to apply to this route
31+
*/
32+
public function __construct(
33+
public readonly string $path,
34+
public readonly string $method = 'GET',
35+
public readonly ?string $name = null,
36+
public readonly array $filters = []
37+
)
38+
{
39+
}
40+
41+
/**
42+
* Get the route path
43+
*/
44+
public function getPath(): string
45+
{
46+
return $this->path;
47+
}
48+
49+
/**
50+
* Get the HTTP method
51+
*/
52+
public function getMethod(): string
53+
{
54+
return strtoupper( $this->method );
55+
}
56+
57+
/**
58+
* Get the route name
59+
*/
60+
public function getName(): ?string
61+
{
62+
return $this->name;
63+
}
64+
65+
/**
66+
* Get the filters
67+
*/
68+
public function getFilters(): array
69+
{
70+
return $this->filters;
71+
}
72+
}

0 commit comments

Comments
 (0)