Download the PHP package meraki/http-router without Composer
On this page you can find all versions of the php package meraki/http-router. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download meraki/http-router
More information about meraki/http-router
Files in meraki/http-router
Package http-router
Short Description Class-driven HTTP router for PHP 8.4+ — the action class name encodes intent (Collection / Item / Action), with no inflection, no route files, and pluggable casters for typed parameters.
License MIT
Homepage https://github.com/merakiframework/http-router
Informations about the package http-router
http-router
Maps HTTP requests to HTTP responses in PHP 8.4+.
Features
Routing model
- Root path
/mapping (configurable sub-namespace, defaultHome) - RESTful route types from the class name alone — no inflection, no route files:
GetAllAction(Collection),GetOneAction(Item),GetAction(Action / verb / static) - Nested resources with parameter inheritance from the parent route (e.g.
/states/{state}/suburbs/{suburb}) - Disambiguation of static (
Action) and RESTful routes at the same namespace —/users/createresolves toUsers\Create\GetAction, notUsers\GetOneAction('create') - Variadic routing (trailing parameters absorbed by
...$args) - Configurable action prefix, suffix, and singular/plural indicators via
Router\Config
HTTP methods
GET,POST,PUT,PATCH,DELETEmapped from method-prefixed class namesHEADauto-derived fromGET(body stripped at the SAPI layer)OPTIONSauto-synthesised (204+Allow:) listing every method available at the URL- Additional methods (WebDAV, etc.) via
Config::withAdditionalMethods('propfind', …) - Allowed-methods list returned on
405; method discovery is poisoning-proof (one misconfigured method doesn't hide the others)
Parameters (via the Caster system)
- Required, optional, and variadic parameters
- Built-in types:
string(universal — never fails),int,float,array(CSV), enums (backed by value; pure enums by case name, case-insensitive),UuidInterface(requiresramsey/uuid) - Union types (e.g.
int|string) — first matching type wins; the universalstringcaster takes precedence in a union - Value-object parameters (constructor-driven, arbitrary nesting —
Date(Year, Month, Day)consumes one segment per leaf) — opt-in viaConfig::withCaster(new ValueObjectCaster()) - Custom parameter types: register your own
CasterviaConfig::withCaster()
Status semantics
400URL too short for a required parameter (or a value object's required ctor params)404no route of that shape (including too-many segments)405method not allowed, withAllow:populated422route matched but a value couldn't cast to its parameter's type204auto-synthesisedOPTIONSresponse- Misconfigured handler trees throw (
UnallowedVariadicParameter,SignatureMismatch) instead of silently mis-routing — surface the developer error
Extensibility
- Custom PSR-3 logger via
Config::withLogger() Configis immutable; all extension points are pluggableCasterimplementations orwith*()config methods
Roadmap
What's coming after alpha, grouped roughly by theme:
Content negotiation (planned for the negotiation milestone)
- Pluggable media-type / language negotiator
406 Not Acceptablewith the accepted types in the response
Observability
- Built-in logging hooks at route-resolution boundaries (the custom logger already plugs in via
Config::withLogger(); the router doesn't emit log events yet)
Performance
- Optional route/reflection cache (route resolution today does
class_existsper candidate + reflection per matched handler — a request-scoped cache is the obvious win)
Routing extras
- Prevent alternative root-path aliasing (so
/and/homearen't both routable toHome\GetAction) - Ignore non-URL handler parameters — e.g. let a handler typed
__invoke(Request $request, int $id)skip$requestfor routing purposes and only consume$idfrom the URL
Tooling
- Reverse routing (build a URL from a handler class + arguments)
- Route dumper (enumerate every routable URL from the handler tree)
- Route-handler generator (scaffold a handler class for a given URL shape)
Runtime
- Concurrency support for Swoole (the router is stateless — likely already works, but needs verification + a documented setup)
Installation
Usage
Instantiate the router, pass the request method and request target, then handle the result.
route() deliberately takes the method and request target as strings, not a request object, so the router doesn't depend on any particular HTTP framework. To support content negotiation later, a small request-context object may be added.
See examples README catalogues each URL with what it demonstrates.
Intentions and design decisions
-
A nested RESTful child needs a parent handler that can address it. The child inherits the matched parent's signature.
At each non-terminal namespace level in the URL, the router needs some handler whose signature consumes the local args — that handler defines the parameter chain the nested child inherits. By default the router tries the request method first; if that finds nothing, it falls back to the methods in
Config::$addressingFallbackMethods(defaults to['get']—GET /resource/{id}is the canonical REST way to address an item). The matched parent is used only for signature inheritance and is never invoked — only the deepest (primary) match is dispatched.For example, this request:
resolves to
Project\Http\Persons\Dependents\PostAllAction(string $id)as long asProject\Http\Persons\GetOneAction(string $id)exists to address the person — you don't need to define a phantomProject\Http\Persons\PostOneAction(which would be semantically wrong;POST /persons/{id}has no natural REST meaning).If you want the stricter pre-feature behaviour (parent must match the request method exactly), clear the fallback list:
Config::create($ns)->withAddressingFallbackMethods().(This applies to RESTful types only —
Actionroutes are standalone; see decision 5.) -
No PSR-7 dependency. The library does not rely on PSR-7 for request/response objects, for the greatest compatibility between HTTP implementations.
-
Convention over configuration. The URL structure and HTTP method imply the handler's fully-qualified class name and method signature. There are no route files and no attributes on handlers — both of which are more error-prone and less performant.
-
The class name encodes intent — there is no inflection, pluralization, or URL guessing. The class-name suffix alone determines the route type (
GetAllAction→ Collection,GetOneAction→ Item,GetAction→ Action). A compound resource likeRegisteredBusinessesneeds no special inflection rule. Because the mapping is mechanical and total, handler names are deterministic and reversible (a prerequisite for future reverse-routing). -
Action(static) routes are standalone and take priority. AnActionroute binds only the segments that follow its own namespace; it never inherits a parent's arguments, and it wins over a RESTful handler at the same namespace. This makes its URL fully deterministic. Reach for it for verbs or fixed overrides — e.g. a static/users/createliving alongside a dynamic/users/{id}, or a vanity/users/{username}. (Seeexamples/Http/Music/TrackInfo/GetAction.phpfor the ambiguity that mixing static and RESTful routing can create.) -
stringis the universal segment type; only narrowing types can fail. Astringparameter accepts any URL segment and never produces a422. Only narrowing types (int,float, enums, value-objects) can reject a value as unprocessable. -
HTTP methods are handled deliberately.
CONNECTandTRACEare never application methods (always405).OPTIONSis auto-synthesised (204) advertising the allowed methods, andHEADis derived fromGET. The supported-method set is configurable viaConfig::withAdditionalMethods()for WebDAV (PROPFIND,MKCOL, …) or other extensions. -
Method discovery is "poisoning-proof." When building the
Allowlist for a405/OPTIONS, a single misconfigured handler for one method does not hide the other valid methods available at the same URL. -
Misconfigurations surface as exceptions, not silent mis-routes. A variadic parent that would permanently shadow a child route throws
UnallowedVariadicParameter; an unreachable nested RESTful handler throwsSignatureMismatch. These are developer errors in the handler tree, so they are raised rather than quietly returning a404. -
Status codes are precise.
400= the URL is too short for a required parameter (structurally malformed);404= no route of that shape (including too many segments);422= a route matched but a value can't cast to its parameter's type. - URLs are matched case-insensitively. The whole request target is lower-cased before matching, so
/Users/1and/users/1resolve to the same handler. The lower-cased segment is also what reaches the caster, so a pure (unbacked) enum's case-name match (e.g.Month::August) is intentionally case-insensitive. Use lower-case for string-backed enum values and URL-friendly value-object inputs; if you need original case preserved in a bound value, type the parameter asstring(universal) rather than relying on case for matching.
How routing works
Take $router->route('GET', '/states/qld/suburbs/emerald'):
-
Normalise. The method is lower-cased and the request target is parsed and split into segments —
['states', 'qld', 'suburbs', 'emerald']. An empty path (/or"") targets the root resource (rootPathSubNamespace, defaultHome). -
Check the method. If it isn't in the supported set, short-circuit to
405(or404if the URL has no handlers at all). -
Walk the segments, left to right. For each segment the router tries to extend the current namespace by appending the class-name form of the segment. If an action class exists at that extended namespace, the segment is a namespace component and begins a new level; otherwise it is an argument collected against the current level:
segment extends to role statesStatesnamespace qldStates\Qld? (none)argument of the StateslevelsuburbsStates\Suburbsnamespace emeraldStates\Suburbs\Emerald? (none)argument of the States\Suburbslevel -
Match a handler at each level. Candidate class suffixes are tried in priority order —
Action→ Collection (AllAction) → Item (OneAction) — and the first whose signature fits the bound arguments wins. TryingActionfirst is what lets/persons/schemaresolve toPersons\Schema\GetActioneven whenPersons\Schema\GetOneActionalso exists. -
Resolve the arguments — in this order:
- Inherited first (RESTful only). A Collection/Item child starts with the parameters and bound arguments inherited from its matched parent (
qldflows down toStates\Suburbs\GetOneAction). AnActionroute inherits nothing. - Then local segments. The level's own segments fill the remaining parameters, left to right. Each segment is cast to the parameter's declared type; if no declared type accepts it, the candidate becomes a
422candidate. - Then the variadic. Any leftover segments must be absorbed by a trailing variadic parameter (
...$args); if there's no variadic, the candidate doesn't fit.
So
/states/qld/suburbs/emeraldbinds['qld', 'emerald']toStates\Suburbs\GetOneAction(string $state, string $suburb)—qldinherited from the parent,emeraldfrom the local segment. - Inherited first (RESTful only). A Collection/Item child starts with the parameters and bound arguments inherited from its matched parent (
- Build the result. A matched chain becomes a
200carrying the primary (deepest) handler and its bound arguments. A failure becomes400/404/422; method discovery produces405or an auto-synthesised204forOPTIONS.
Route types and their intentions
| Type | Class suffix | Intent | Inherits parent args? |
|---|---|---|---|
| Collection | GetAllAction |
Operate on a whole collection (list, create-into). | Yes |
| Item | GetOneAction |
Operate on a single addressed member of a collection; consumes the id segment. | Yes |
| Action | GetAction |
A verb / static / vanity route that bypasses RESTful semantics. Binds only its own trailing segments and wins over RESTful handlers at the same namespace. | No |
Collection and Item are the RESTful types: they model resources and chain together (/states/{state}/suburbs/{suburb}), each inheriting its parent's identifying arguments. Action is the escape hatch: a fixed route that behaves "as if the file existed at that path," for cases the RESTful conventions shouldn't own.
Contributing
See CONTRIBUTING.md.