Routing
From #[Route] to a REST route
An RPC procedure method gets into REST when it has the Symfony\Component\Routing\Attribute\Route attribute. The attribute on the class is optional and sets a common path and name. Methods without #[Route] stay RPC-only.
| Part | Rule |
|---|---|
| Path | class path + / + method path; an empty method path gives the class path, both empty give / |
| Name | ufo_rest_api_ + class name + _ + method name |
| Class name | name from the class attribute; without it, the procedure alias from #[RPC\Info] in snake_case (shopItems → shop_items) |
| Method name | name from the method attribute; without it, the PHP method name in snake_case (createItem → create_item) |
| HTTP methods | methods from the method attribute; without them, POST |
Other #[Route] options |
requirements, defaults, host etc. are not used: only path, name and methods are read |
#[RPC\Info('catalog')]
#[Route('/catalog', name: 'catalog')]
class CatalogApi implements IRpcService
{
#[Route('/items/{id}', name: 'item', methods: ['GET'])] // GET /rest/catalog/items/{id}
public function item(int $id): array {}
#[Route('/items')] // POST /rest/catalog/items
public function createItem(string $title): array {}
}
Path variables ({id}) are passed to the method as parameters with the same name; see Request parameters.
The path is not derived from the RPC method name automatically: without #[Route] the method is not available via REST, and an unknown path returns 404.
Generated routes
For each REST method and API version two Symfony routes are created:
| Route | Path | HTTP methods | Purpose |
|---|---|---|---|
<name>_<version> |
<path>, for versions other than v1, /<version><path> |
from the attribute | Method call |
<name>_<version>_method_not_allowed |
the same | any | Priority -10: a wrong HTTP method gets 405 rather than 404 |
Routes from the bundle's config/router.yaml:
| Route | Path | Purpose |
|---|---|---|
ufo_rest_doc |
the import prefix | OpenAPI, GET |
ufo_rest_doc_ver |
/{ver} (v\d+) |
OpenAPI for a version |
ufo_rest_doc_postman |
/postman |
Postman |
ufo_rest_doc_postman_ver |
/{ver}/postman |
Postman for a version |
ufo_rest_api_entrypoint |
/{path} |
Catch-all entry point, priority -100: an unknown path returns 404 in the error format |
ufo_rest_api_entrypoint_ver |
/{ver}/{path} |
The same for versions, priority -90 |
HEAD is handled by GET routes. OPTIONS is not handled separately: for a method without OPTIONS in methods the response is 405.
Versions
Versions are defined by JsonRpcBundle: #[RPC\Info(version: 2, removedMethods: [...])] (API versions). A version 2 procedure inherits the version 1 methods it does not override or remove.
| Version | Call | Documentation | Route name |
|---|---|---|---|
v1 (default) |
/rest/catalog/items/1 |
/rest, /rest/postman |
..._v1 |
v2 |
/rest/v2/catalog/items/1 |
/rest/v2, /rest/v2/postman |
..._v2 |
An unknown version returns 404.
API groups and multiple entry points
Import config/router.yaml separately for each entry point. defaults.api_group selects the API group; without it, the group is general. The URL prefix does not depend on the group name.
# config/routes/ufo_rest_api.yaml
rest:
resource: ../../vendor/ufo-tech/rest-bundle/config/router.yaml
prefix: /rest
trailing_slash_on_root: false
rest_internal:
resource: ../../vendor/ufo-tech/rest-bundle/config/router.yaml
prefix: /internal
defaults: { api_group: internal }
trailing_slash_on_root: false
rest_partners:
resource: ../../vendor/ufo-tech/rest-bundle/config/router.yaml
prefix: /partners
name_prefix: partners_
defaults: { api_group: internal }
trailing_slash_on_root: false
#[RPC\Info('catalog')]
#[RPC\APIGroups(['internal', 'public'], false)]
#[Route('/catalog', name: 'catalog')]
class CatalogApi implements IRpcService
{
#[Route('/list', name: 'list', methods: ['GET'])]
public function list(): array { return []; }
#[RPC\APIGroups(['internal'], false)]
#[Route('/price', name: 'price', methods: ['POST'])]
public function price(int $price): int { return $price; }
}
- Group membership of a method is defined by
#[RPC\APIGroups]: the method attribute replaces the class attribute,withDefault: trueaddsgeneral.#[RPC\IgnoreApi]excludes the method completely. For a method to appear in REST,#[Route]is required anyway. - Each entry point has its own methods, versions, documentation and token provider.
- Route names get the group prefix:
internal_ufo_rest_doc,internal_ufo_rest_api_catalog_list_v1.name_prefixgoes after it:internal_partners_ufo_rest_doc. - Routes of the
generalgroup also keep the old names as aliases:ufo_rest_doc→general_ufo_rest_doc. - Only the route defines the group: an
api_groupfield in the query or body does not change it.
Alongside RPC and other adapters
REST routes are registered through Ufo\JsonRpcBundle\Routing\IApiRouteImport (the RestRouteImport class), so groups and names are handled by the JsonRpcBundle route loader. RPC routes, REST routes and routes of other adapters implementing the same interface can be imported for the same groups in one file without name conflicts.
In 1.1–1.2 this was done by the bundle's own RestYamlFileLoader; it was removed in 1.3 (see the Changelog). Applications that only import config/router.yaml are not affected.
Troubleshooting
- The method is not in REST: check
#[Route]on the method, the API group of the import,#[RPC\IgnoreApi]and the version'sremovedMethods. 405for an existing method: the HTTP method is not in the attribute'smethods(onlyPOSTby default).- Route names:
bin/console debug:router | grep ufo_rest.