Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,159 @@ several routes including one with a variable.
If present, the extra element is merged into the parameters array
before it is passed to the routes closure.

## Attribute-Based Routing

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.

### Basic Usage

#### Simple Route

```php
use Neuron\Routing\Attributes\Get;

class HomeController
{
#[Get('/')]
public function index()
{
return 'Hello World';
}
}
```

#### HTTP Method Attributes

```php
use Neuron\Routing\Attributes\Get;
use Neuron\Routing\Attributes\Post;
use Neuron\Routing\Attributes\Put;
use Neuron\Routing\Attributes\Delete;

class UsersController
{
#[Get('/users')]
public function index() { }

#[Get('/users/:id')]
public function show(int $id) { }

#[Post('/users')]
public function store() { }

#[Put('/users/:id')]
public function update(int $id) { }

#[Delete('/users/:id')]
public function destroy(int $id) { }
}
```

#### Route Names and Filters

```php
#[Get('/admin/users', name: 'admin.users.index', filters: ['auth'])]
public function index() { }

#[Post('/admin/users', name: 'admin.users.store', filters: ['auth', 'csrf'])]
public function store() { }
```

#### Route Groups

Apply common settings to all routes in a controller:

```php
use Neuron\Routing\Attributes\RouteGroup;
use Neuron\Routing\Attributes\Get;
use Neuron\Routing\Attributes\Post;

#[RouteGroup(prefix: '/admin', filters: ['auth'])]
class AdminController
{
#[Get('/dashboard')] // Becomes /admin/dashboard with 'auth' filter
public function dashboard() { }

#[Post('/users', filters: ['csrf'])] // Becomes /admin/users with ['auth', 'csrf'] filters
public function createUser() { }
}
```

#### Multiple Routes on Same Method

```php
#[Get('/api/v1/users')]
#[Get('/api/v2/users')]
public function getUsers()
{
// Handle both API versions
}
```

### Configuration

#### Enable Attribute Routing in MVC Application

Add controller paths to your `config/neuron.yaml`:

```yaml
routing:
controller_paths:
- path: 'src/Controllers'
namespace: 'App\Controllers'
- path: 'src/Admin/Controllers'
namespace: 'App\Admin\Controllers'
```

#### Hybrid Approach (YAML + Attributes)

You can use both YAML routes and attribute routes together:

- **YAML routes**: Legacy routes, package-provided routes
- **Attribute routes**: New application routes

The MVC Application will load both automatically.

### Benefits

- **Co-location**: Routes live with controller logic
- **Type Safety**: IDE autocomplete and validation
- **Refactor-Friendly**: Routes update when controllers change
- **No Sync Issues**: Can't have orphaned routes
- **Modern Standard**: Used by Symfony, Laravel, ASP.NET, Spring Boot
- **Self-Documenting**: Route definition IS the documentation

### Performance

Route scanning uses PHP Reflection, which could be slow. For production:

1. Routes are scanned once during application initialization
2. The Router caches RouteMap objects in memory
3. No reflection happens during request handling
4. Future: Add route caching to file for zero-cost production routing

### Migration from YAML

**Before (YAML):**
```yaml
# routes.yaml
home:
method: GET
route: /
controller: App\Controllers\Home@index
```

**After (Attributes):**
```php
class Home
{
#[Get('/', name: 'home')]
public function index() { }
}
```

See `tests/unit/RouteScannerTest.php` for working examples of basic route definition, route groups with prefixes, filter composition, and multiple routes per method.

## Rate Limiting

The routing component includes a powerful rate limiting system with multiple storage backends and flexible configuration options.
Expand Down
34 changes: 34 additions & 0 deletions src/Routing/Attributes/Delete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Neuron\Routing\Attributes;

use Attribute;

/**
* DELETE route attribute for defining DELETE HTTP routes on controller methods.
*
* @package Neuron\Routing\Attributes
*
* @example
* ```php
* #[Delete('/users/:id', filters: ['auth', 'csrf'])]
* public function destroy(int $id) { }
* ```
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Delete extends Route
{
/**
* @param string $path The route path (e.g., '/users/:id')
* @param string|null $name Optional route name for URL generation
* @param array $filters Array of filter names to apply to this route
*/
public function __construct(
string $path,
?string $name = null,
array $filters = []
)
{
parent::__construct( $path, 'DELETE', $name, $filters );
}
}
37 changes: 37 additions & 0 deletions src/Routing/Attributes/Get.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace Neuron\Routing\Attributes;

use Attribute;

/**
* GET route attribute for defining GET HTTP routes on controller methods.
*
* @package Neuron\Routing\Attributes
*
* @example
* ```php
* #[Get('/users')]
* public function index() { }
*
* #[Get('/users/:id', name: 'users.show', filters: ['auth'])]
* public function show(int $id) { }
* ```
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Get extends Route
{
/**
* @param string $path The route path (e.g., '/users/:id')
* @param string|null $name Optional route name for URL generation
* @param array $filters Array of filter names to apply to this route
*/
public function __construct(
string $path,
?string $name = null,
array $filters = []
)
{
parent::__construct( $path, 'GET', $name, $filters );
}
}
34 changes: 34 additions & 0 deletions src/Routing/Attributes/Post.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Neuron\Routing\Attributes;

use Attribute;

/**
* POST route attribute for defining POST HTTP routes on controller methods.
*
* @package Neuron\Routing\Attributes
*
* @example
* ```php
* #[Post('/users', filters: ['csrf'])]
* public function store() { }
* ```
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Post extends Route
{
/**
* @param string $path The route path (e.g., '/users')
* @param string|null $name Optional route name for URL generation
* @param array $filters Array of filter names to apply to this route
*/
public function __construct(
string $path,
?string $name = null,
array $filters = []
)
{
parent::__construct( $path, 'POST', $name, $filters );
}
}
34 changes: 34 additions & 0 deletions src/Routing/Attributes/Put.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Neuron\Routing\Attributes;

use Attribute;

/**
* PUT route attribute for defining PUT HTTP routes on controller methods.
*
* @package Neuron\Routing\Attributes
*
* @example
* ```php
* #[Put('/users/:id', filters: ['auth', 'csrf'])]
* public function update(int $id) { }
* ```
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Put extends Route
{
/**
* @param string $path The route path (e.g., '/users/:id')
* @param string|null $name Optional route name for URL generation
* @param array $filters Array of filter names to apply to this route
*/
public function __construct(
string $path,
?string $name = null,
array $filters = []
)
{
parent::__construct( $path, 'PUT', $name, $filters );
}
}
72 changes: 72 additions & 0 deletions src/Routing/Attributes/Route.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Neuron\Routing\Attributes;

use Attribute;

/**
* Base route attribute for defining HTTP routes on controller methods.
*
* This attribute allows routes to be defined directly on controller methods,
* providing a modern alternative to YAML-based route configuration.
*
* @package Neuron\Routing\Attributes
*
* @example
* ```php
* #[Route('/users/:id', method: 'GET', name: 'users.show', filters: ['auth'])]
* public function show(int $id) {
* // Implementation
* }
* ```
*/
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Route
{
/**
* @param string $path The route path (e.g., '/users/:id')
* @param string $method HTTP method (GET, POST, PUT, DELETE)
* @param string|null $name Optional route name for URL generation
* @param array $filters Array of filter names to apply to this route
*/
public function __construct(
public readonly string $path,
public readonly string $method = 'GET',
public readonly ?string $name = null,
public readonly array $filters = []
)
{
}

/**
* Get the route path
*/
public function getPath(): string
{
return $this->path;
}

/**
* Get the HTTP method
*/
public function getMethod(): string
{
return strtoupper( $this->method );
}

/**
* Get the route name
*/
public function getName(): ?string
{
return $this->name;
}

/**
* Get the filters
*/
public function getFilters(): array
{
return $this->filters;
}
}
Loading