Repository navigation
2.0.0
This major release redesigns the attributes to follow common PHP conventions, makes route discovery stricter and fixes several bugs of the route loader. It contains breaking changes: see the upgrade guide below.
⚠️ Breaking changes
Attributes renamed
| 1.x | 2.0 |
|---|---|
#[Controller] |
#[AsController] |
#[ApiController] |
removed |
#[BaseController] |
removed |
#[HttpGet] |
#[Get] |
#[HttpPost] |
#[Post] |
#[HttpPut] |
#[Put] |
#[HttpPatch] |
#[Patch] |
#[HttpDelete] |
#[Delete] |
#[HttpHead] |
#[Head] |
#[HttpOptions] |
#[Options] |
HttpMethod (abstract) |
#[Route] (concrete) |
Controllerconflicted with the baseControllerclass provided by many frameworks: importing both in the same namespace made controllers silently extend the attribute.AsControllerfollows theAs*naming convention used by Symfony.ApiControllerandBaseControllerwere plain aliases.BaseControlleralso conflicted with CodeIgniter's base class. Create your own attribute extendingAsControllerinstead (see below).- The
Http*prefix came from ASP.NET; short names (Get,Post, …) are the common convention in PHP libraries.
Stricter route discovery
#[AsController]is now required. Classes without#[AsController](or an attribute extending it) are ignored, even if their methods have route attributes.#[AsController]on an interface, a trait, an enum or an abstract class throws aLogicException, as only concrete classes can be controllers. PHP attributes are not inherited: each concrete controller must have its own#[AsController]. Routes of abstract parents and traits are still inherited by the concrete controllers extending or using them, with the prefix of the concrete controller.- A route attribute on a non-public method throws a
LogicException, as no dispatcher can call it. AttributeRouteLoaderthrows anInvalidArgumentExceptionwhen the given path is not an existing directory (instead of anUnexpectedValueExceptionfromRecursiveDirectoryIterator).
Attributes API
Route(formerlyHttpMethod) constructor signature is nowRoute(string|array $methods, string $path = '', string $name = '', int $priority = 0).- HTTP methods are normalized to uppercase (
'get'becomes'GET'), and an empty list of methods throws anInvalidArgumentException. - The properties of
AsControllerandRouteare nowreadonly.
✨ New features
#[Route]for several HTTP methods on the same action:#[Route(['GET', 'POST'], '/search', name: 'users.search')]
- Optional path:
#[Get(name: 'users.index')]on a controller prefixed with/usersmaps to/users. - Custom attributes:
AsControllerandRoutecan be extended, for instance to define a controller attribute with a predefined prefix:#[Attribute(Attribute::TARGET_CLASS)] class AsApiController extends AsController { public function __construct(int $priority = 0) { parent::__construct('/api', $priority); } }
🐛 Bug fixes
- The priority of a controller leaked to the routes of the classes loaded after it when they had no controller attribute.
- Routes defined on
privateandprotectedmethods were registered. - Routes defined in abstract classes and enums were registered with a non-instantiable handler.
- The class name was built with
str_replace()on the whole file path, which could alter paths containing the base directory or.phpmore than once.
📚 Documentation
- New sections: discovery rules, route priority, name conflicts with frameworks, custom controller attributes.
🧰 Tooling
declare(strict_types=1)in every file.- PHPUnit test suite (100% line coverage):
composer test. - PHPStan at level max:
composer analyse. - GitHub Actions CI running
composer validate, PHPStan and PHPUnit on PHP 8.3, 8.4 and 8.5.
⬆️ Upgrade guide
- Replace the imports and attributes following the table above:
-use Routing\Attribute\{Controller, HttpGet, HttpPost}; +use Routing\Attribute\{AsController, Get, Post}; -#[Controller(prefix: '/users')] +#[AsController(prefix: '/users')] class UserController { - #[HttpGet('/{id}', name: 'users.show')] + #[Get('/{id}', name: 'users.show')] public function show(int $id): void {} }
- Replace
#[ApiController]and#[BaseController]with#[AsController], or with your own attribute extendingAsController. - Add
#[AsController]to every controller that did not have a controller attribute, otherwise its routes will no longer be loaded. - Make public the methods holding route attributes, or remove their attributes.
- Move
#[AsController]from abstract classes to their concrete controllers. - If you extended
HttpMethod, extendRouteinstead and update the call to the parent constructor:parent::__construct($methods, $path, $name, $priority). - If a short attribute name conflicts with a class of your application (e.g.
Options), import the namespace with an alias:use Routing\Attribute as Http; #[Http\Options('/users')]
Full Changelog: 1.1.0...2.0.0