handcraftedinthealps/rest-routing-bundle
Symfony bundle that restores FOSRestBundle-style automatic REST route generation. Supports format options and method/name prefixing, and includes a command to dump/convert type: rest routes into standard Symfony routes for migration or opt-out.
Installation:
composer require handcraftedinthealps/rest-routing-bundle
Enable the bundle in config/bundles.php:
HandcraftedInTheAlps\RestRoutingBundle\RestRoutingBundle::class => ['all' => true],
Configure in config/packages/handcraftedinthealps_rest_routing.yaml:
handcraftedinthealps_rest_routing:
routing_loader:
default_format: 'json'
prefix_methods: true
include_format: true
Annotate a Controller:
use HandcraftedInTheAlps\RestRoutingBundle\Controller\Annotations\RouteResource;
use HandcraftedInTheAlps\RestRoutingBundle\Routing\ClassResourceInterface;
#[RouteResource]
class PostController implements ClassResourceInterface
{
// Methods will auto-generate routes (e.g., `getItem`, `cudCollection`)
}
Test by accessing endpoints (e.g., GET /api/posts for getCollection).
Replace FOS annotations with the bundle’s equivalents:
- use FOS\RestBundle\Controller\Annotations\RouteResource;
+ use HandcraftedInTheAlps\RestRoutingBundle\Controller\Annotations\RouteResource;
Run the migration command to dump routes:
bin/console fos:rest:routing:dump-symfony-routes
Use annotations to define RESTful routes:
#[RouteResource("api", namePrefix: "api_")]
class UserController implements ClassResourceInterface
{
#[NoRoute] // Exclude from auto-routing
public function customAction(): Response { ... }
#[Version("v1")] // Route under `/api/v1/users`
public function getCollection(): Response { ... }
}
Override defaults via annotations:
#[RouteResource(
collectionOperations: ["get" => false], // Disable GET /users
itemOperations: ["put" => ["method" => "PATCH"]] // Customize PUT
)]
class ProductController implements ClassResourceInterface { ... }
Exclude specific controllers from auto-routing:
# config/packages/handcraftedinthealps_rest_routing.yaml
handcraftedinthealps_rest_routing:
routing_loader:
ignored_classes:
- App\Controller\Admin\DashboardController
If using both bundles, ensure configurations align:
# Merge formats if needed
handcraftedinthealps_rest_routing:
routing_loader:
formats:
json: true
xml: true
Use Symfony’s RouterInterface or UrlGeneratorInterface:
$url = $this->router->generate('api_post_collection_get');
$this->assertEquals('/api/posts', $url);
Annotation Conflicts:
@Route (Symfony) with @RouteResource. Use @NoRoute to exclude methods.ClassResourceInterface.Route Naming Collisions:
namePrefix) must be unique across controllers.api_user_, admin_product_).Deprecated FOSRestBundle:
RequestContext or middleware for formats.PHP 8+ Deprecations:
?Response) may trigger warnings.>=1.1.2 or type-hint strictly (e.g., Response).Dump Generated Routes:
bin/console debug:router | grep "api_"
Or use the dump-symfony-routes command to preview routes before migration.
Check Annotations:
bin/console debug:container --tag="rest.controller" to verify annotated controllers.Log Route Loading: Enable debug mode to log route generation:
# config/packages/dev/handcraftedinthealps_rest_routing.yaml
handcraftedinthealps_rest_routing:
routing_loader:
debug: true
Custom Route Naming:
Override RestActionReader to modify route names dynamically:
class CustomActionReader extends RestActionReader
{
protected function getRouteName(string $namePrefix, string $methodName): string
{
return $namePrefix . '_custom_' . $methodName;
}
}
Register as a service:
services:
HandcraftedInTheAlps\RestRoutingBundle\Routing\RestActionReader:
class: App\Routing\CustomActionReader
Add Route Attributes:
Extend RouteResource to support custom attributes:
#[Attribute]
class Cacheable
{
public function __construct(public bool $enabled = true) {}
}
#[RouteResource]
#[Cacheable]
class PostController { ... }
Hook: Implement a compiler pass to process attributes.
Conditional Route Loading:
Use Symfony’s CompilerPass to load routes only in specific environments:
public function process(ConfigCache $cache)
{
if (!$cache->isFresh()) {
$this->container->get('router')->getRouteCollection()->addCollection(
$this->container->get('rest_routing.loader')->load(__DIR__.'/routes.yaml')
);
}
}
bin/console cache:clear
@NoRoute for non-REST methods to reduce parsing overhead.How can I help you explore Laravel packages today?