Web API access mapping framework for Ray.MediaQuery.
composer require ray/web-queryNote: This package builds on ray/media-query, which provides the core query infrastructure (parameter injection, logging, etc.).
Annotate the methods with #[WebQuery], giving each one a query ID:
<?phpuseRay\MediaQuery\Annotation\WebQuery;
interface UserApiInterface
{
#[WebQuery('user_item')]
publicfunctionget(string$id): array;
}web_query.json maps each query ID to an HTTP method and a URI template:
{
"webQuery": [
{"id": "user_item", "method": "GET", "path": "https://{domain}/users/{id}"}
]
}MediaQueryWebModule is installed into MediaQueryBaseModule. The interfaces are
registered with Queries::fromClasses():
<?phpuseRay\Di\Injector;
useRay\MediaQuery\MediaQueryBaseModule;
useRay\MediaQuery\MediaQueryWebModule;
useRay\MediaQuery\Queries;
useRay\MediaQuery\WebQueryConfig;
$queries = Queries::fromClasses([UserApiInterface::class]);
$webConfig = newWebQueryConfig('web_query.json', ['domain' => 'api.example.com']);
$module = newMediaQueryBaseModule($queries);
$module->install(newMediaQueryWebModule($webConfig));
$injector = newInjector($module);
$api = $injector->getInstance(UserApiInterface::class);
$user = $api->get('123'); // GET https://api.example.com/users/123URI template variables are filled from the method arguments and the bindings
passed to WebQueryConfig (here {domain} comes from the bindings and {id}
from the get() argument).
The return type of the interface method selects how the HTTP response is handled:
| Return type | Result |
|---|---|
array | JSON body decoded to an array |
string | Raw response body |
PSR-7 MessageInterface | The HTTP message object |
Instead of a raw array, a method can return typed, immutable domain objects.
Give #[WebQuery] a factory (and a type) — this is the same factory
mechanism ray/media-query provides for #[DbQuery], applied to HTTP
responses.
<?phpuseRay\MediaQuery\Annotation\WebQuery;
interface ProductApiInterface
{
#[WebQuery('product_item', type: 'row', factory: ProductFactory::class)]
publicfunctionget(string$id): Product;
#[WebQuery('product_list', factory: ProductFactory::class)]
/** @return array<Product> */publicfunctionlist(string$status): array;
}The factory is resolved through the DI injector, so it can depend on domain services and apply business logic while building the object:
<?phpfinalclass ProductFactory
{
publicfunction__construct(
privateTaxCalculator$tax,
) {
}
publicfunctionfactory(string$name, int$price): Product
{
returnnewProduct($name, $this->tax->applyTax($price));
}
}
finalclass Product
{
publicfunction__construct(
publicreadonlystring$name,
publicreadonlyint$price,
) {
}
}The decoded JSON is passed to the factory method as named arguments: each
JSON key is matched to a parameter by name, unknown keys are ignored, and a
missing required argument throws InvalidWebFactoryKeyException. (This is the
web counterpart of media-query's positional PDO::FETCH_FUNC binding.)
type selects single object vs. list:
type | JSON response | Result |
|---|---|---|
'row' | object {...} | one object |
'row_list' | array [{...}, {...}] | array<Object> |
type defaults to 'row_list'. A 'row' method whose response is a list
takes the first element; a 'row_list' method whose response is a single
object wraps it into a one-element list.
You can also map straight to an entity without a factory: when the return
type (or the @return array<Entity> docblock) is a class, each response is
hydrated through the entity constructor.
#[WebQuery('product_item', type: 'row')]
publicfunction get(string$id): Product; // built via Product::__constructTo wrap or aggregate the fetched objects into another type (totals, metadata,
…), let the return type implement PostFetchInterface. Its static
fromContext() receives the fetch result and returns the final object. It runs
after the factory, carries no dependencies by design, and is the web analogue
of media-query's PostQueryInterface (named PostFetch because a web call is a
single fetch, with no multi-statement query context to span).
<?phpuseRay\MediaQuery\PostFetchContext;
useRay\MediaQuery\PostFetchInterface;
finalclass ProductList implements PostFetchInterface
{
/** @param array<Product> $items */publicfunction__construct(
publicreadonlyarray$items,
publicreadonlyint$total,
) {
}
publicstaticfunctionfromContext(PostFetchContext$context): static
{
/** @var array<Product> $items */$items = is_array($context->result) ? $context->result : [];
returnnewself($items, count($items));
}
}#[WebQuery('product_list', factory: ProductFactory::class)]
publicfunction listAggregate(string$status): ProductList;PostFetchContext exposes the fetch result, the original method arguments
(query), and the #[WebQuery] annotation (webQuery).
- Web API Queries: Execute HTTP requests via interface methods
- URI Template Support: Dynamic URL parameter binding with
{param}syntax - Multiple Response Types: JSON array, string, or PSR-7 message
- Domain Object Mapping (BDR): Map responses to typed domain objects via an injectable factory, with optional
PostFetchcomposition - Parameter Injection: Automatic parameter conversion and injection
- HTTP Client Integration: Built on the Guzzle HTTP client
- PHP 8.2+
- ray/media-query ^1.0