pollora/wp-rest
Pollora WP REST adds Laravel-style REST API routing to WordPress: register routes and controllers with an expressive syntax, group endpoints, apply middleware, and return clean JSON responses while staying compatible with the native WordPress REST API.
This package is a small, framework-agnostic slice of Pollora: it turns PHP 8 attributes on controller classes into WordPress REST API routes. It is the piece you reach for when you want attribute-driven register_rest_route() calls without pulling in the whole Pollora framework.
Install and wiring (v0.1.0)
composer require pollora/wp-rest (pin the version — this is a 0.x release explicitly tracking the next Laravel/framework release, so treat the API as provisional).Pollora\WpRest\WpRestDiscovery, hand it a PSR-11 container and a set of directories to scan, and run it (typically from a mu-plugin, a service provider's boot(), or a small bootstrap file loaded on plugins_loaded).src/WpRest/ first. It is a direct lift from pollora/framework (src/WpRest) with class names unchanged, so any existing Pollora documentation or blog posts about WP REST controllers apply verbatim, with the namespace swapped to Pollora\WpRest\*.Pollora\Support\WpGlobals ships alongside it for WordPress global access; check whether you already have an equivalent helper before adopting it.First use case
Add a controller class under a scanned directory, annotate the class with the package's route attribute and its methods with the method/args attributes, and let discovery register it. The fastest sanity check is a single GET route returning a static array: hit it via wp-json/<namespace>/<route> to confirm the scan directories, container resolution, and namespace/version are all correct before adding auth callbacks or argument schemas.
Because v0.1.0 can run without Laravel's application, verify early whether you are in a Laravel context or a bare WordPress one — the bootstrap differs (container source) but the controller code does not.
Attribute-driven controllers. Keep one controller per resource. Class-level attribute sets the namespace/route base; method-level attributes set HTTP verb, path, and args. This mirrors how register_rest_route() arguments map, so anything you can express in the array form you can express here.
Constructor injection, resolved by discovery. Controllers are built by the discovery's instance resolver, not by WordPress. That means constructor dependencies (repositories, services, a logger) work as long as your PSR-11 container can resolve them — and they are resolved at registration time, not per request. Keep constructors cheap and side-effect-light; do expensive work lazily inside the handler.
Two integration modes.
Psr\Container\ContainerInterface, pass it to WpRestDiscovery, and let it construct controllers. Ideal for mu-plugins and non-Laravel sites.Custom HandlesAttributes attributes. This is the extension seam. Implement HandlesAttributes on your own attribute class to introduce project-specific metadata (feature flags, capability requirements, cache TTLs, OpenAPI hints). The handler receives the PSR-11 container, so it can act on the attribute at discovery time — a good place to auto-register permission_callbacks derived from a #[RequiresCapability('manage_options')]-style attribute rather than repeating them per method.
Workflow that pays off. Centralise cross-cutting REST concerns (nonce/permission checks, response shaping, error envelopes) in base controller classes or in a HandlesAttributes implementation, and keep endpoint classes to argument definition plus a thin handler. Register the discovery once on rest_api_init-adjacent timing and cache the scan in production if directory walking shows up in profiles.
0.1.0 is a staging release for a forthcoming framework release; 1.0.0 will land with it. Expect namespace or bootstrap changes at 1.0.0 — wrap your bootstrap in one file so the upgrade touches one place.rest_api_init consumers. Running discovery too late silently yields 404s (rest_no_route) with no error. If a route "doesn't exist", check timing before checking attributes.var_dump inside the resolver while bringing up a new scan root.app(), or config() inside controllers if you want the bare-WordPress path to keep working. The architecture test enforcing "attributes, discovery, PSR contracts and WordPress" only constrains the package, not your code — you can still accidentally couple yourself to Laravel.Pollora\Support\WpGlobals is a static-ish helper over WordPress globals. Useful, but be aware of the usual caveats: it is not mockable in unit tests without extra work, and globals differ under multisite and WP-CLI. Prefer passing values in via the container where practical.permission_callback isn't returning a WP_Error (a 401/403 is often a permissions bug, not a routing bug), (5) args validation isn't rejecting the payload with a 400.How can I help you explore Laravel packages today?