Skip to content

Repository files navigation

Ray.WebQuery

Web API access mapping framework for Ray.MediaQuery.

Installation

composer require ray/web-query

Note: This package builds on ray/media-query, which provides the core query infrastructure (parameter injection, logging, etc.).

Usage

Define an interface

Annotate the methods with #[WebQuery], giving each one a query ID:

<?phpuseRay\MediaQuery\Annotation\WebQuery;
interface UserApiInterface
{
#[WebQuery('user_item')]
publicfunctionget(string$id): array;
}

Create a web query configuration file

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}"}
]
}

Install the module

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/123

URI 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).

Response types

The return type of the interface method selects how the HTTP response is handled:

Return typeResult
arrayJSON body decoded to an array
stringRaw response body
PSR-7 MessageInterfaceThe HTTP message object

Mapping responses to a domain 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:

typeJSON responseResult
'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::__construct

Composing results with PostFetch

To 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).

Features

  • 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 PostFetch composition
  • Parameter Injection: Automatic parameter conversion and injection
  • HTTP Client Integration: Built on the Guzzle HTTP client

Requirements

  • PHP 8.2+
  • ray/media-query ^1.0

About

Web API query abstraction for Ray.MediaQuery framework

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages