pollora / wp-rest
WordPress REST API routes declared with PHP attributes: a controller class, #[WpRestRoute] and #[Method], permissions as classes. Runs with or without Laravel.
Requires
- php: ^8.3
- pollora/attributes: ^0.1 || ^1.0
- pollora/discovery: ^0.1 || ^1.0
- psr/container: ^2.0
- psr/log: ^3.0
Requires (Dev)
- antecedent/patchwork: ^2.2
- brain/monkey: ^2.7
- laravel/pint: ^1.22.1
- mockery/mockery: ^1.6.12
- pestphp/pest: ^3.8.2
- phpstan/phpstan: ^2.1.16
- szepeviktor/phpstan-wordpress: ^2.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
WordPress REST API routes written as a controller class: #[WpRestRoute] on the class gives the namespace and route, #[Method] on a method gives its HTTP verbs, and a permission class decides who may call it. No register_rest_route() call, no rest_api_init callback to write.
The controller is built with its constructor dependencies, its methods receive the route parameters they name and the WP_REST_Request when they ask for it.
Part of Pollora, the Laravel framework for WordPress, where it is installed and wired for you: see REST API. It runs without Laravel too, in a WordPress plugin, a Bedrock site or any PHP project.
Installation
composer require pollora/wp-rest
Requires PHP 8.3+. It builds on pollora/attributes and pollora/discovery.
Declaring routes
use Pollora\Attributes\WpRestRoute; use Pollora\Attributes\WpRestRoute\Method; use Pollora\WpRest\Permissions\Can; use Pollora\WpRest\Permissions\IsLoggedIn; #[WpRestRoute('app/v1', 'documents/(?P<documentId>\\d+)', permissionCallback: IsLoggedIn::class)] final class DocumentApi { public function __construct(private readonly DocumentRepository $documents) {} #[Method('GET')] public function show(int $documentId): array { return $this->documents->find($documentId); } #[Method('DELETE', permissionCallback: new Can('delete_post', parameter: 'documentId'))] public function destroy(WP_REST_Request $request, int $documentId): array { return ['deleted' => $this->documents->delete($documentId)]; } }
GET wp-json/app/v1/documents/18callsshow(18). A parameter typedWP_REST_Requestreceives the request; the others are read from the route by name.#[Method]acceptsGET,POST,PUT,DELETEandPATCH, alone or as a list; with any other verb the route is not registered, and the logger says why.- A method's permission replaces the route's. Without either, the route is public.
Permissions
| Class | Allows |
|---|---|
IsLoggedIn |
any logged-in user |
IsAdmin |
users who can manage_options |
new Can('edit_posts') |
users with a capability; parameter: passes a request parameter for a meta capability such as edit_post |
A permission class of your own implements Pollora\Attributes\WpRestRoute\Permission: allow(WP_REST_Request $request): bool|WP_Error.
Wiring it outside Pollora
Point the discovery engine at your code and add WpRestDiscovery; routes are registered on rest_api_init.
use Pollora\Discovery\Domain\Models\DiscoveryLocation; use Pollora\Discovery\Infrastructure\Services\DiscoveryEngine; use Pollora\WpRest\Infrastructure\Services\WpRestDiscovery; (new DiscoveryEngine) ->addLocation(new DiscoveryLocation('MyPlugin\\', __DIR__.'/src')) ->addDiscovery('wp_rest_routes', new WpRestDiscovery) ->run();
Controllers are built by the engine's instance resolver: by reflection by default (constructor parameters typed with a class are built in turn), by your container if you give the engine a ContainerInstanceResolver (PSR-11). WpRestDiscovery takes a PSR-3 logger, which records the routes it could not register.
Testing
composer test # unit tests, PHPStan, Pint
License
MIT. See LICENSE.