Skip to content

Commit dc368fe

Browse files
committed
Document BDR factory and PostFetch in README
1 parent 05a60b7 commit dc368fe

1 file changed

Lines changed: 118 additions & 0 deletions

File tree

README.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,129 @@ The return type of the interface method selects how the HTTP response is handled
7878
| `string` | Raw response body |
7979
| PSR-7 `MessageInterface`| The HTTP message object |
8080

81+
### Mapping responses to a domain object
82+
83+
Instead of a raw array, a method can return typed, immutable domain objects.
84+
Give `#[WebQuery]` a `factory` (and a `type`) — this is the same factory
85+
mechanism `ray/media-query` provides for `#[DbQuery]`, applied to HTTP
86+
responses.
87+
88+
```php
89+
<?php
90+
use Ray\MediaQuery\Annotation\WebQuery;
91+
92+
interface ProductApiInterface
93+
{
94+
#[WebQuery('product_item', type: 'row', factory: ProductFactory::class)]
95+
public function get(string $id): Product;
96+
97+
#[WebQuery('product_list', factory: ProductFactory::class)]
98+
/** @return array<Product> */
99+
public function list(string $status): array;
100+
}
101+
```
102+
103+
The factory is resolved through the DI injector, so it can depend on domain
104+
services and apply business logic while building the object:
105+
106+
```php
107+
<?php
108+
final class ProductFactory
109+
{
110+
public function __construct(
111+
private TaxCalculator $tax,
112+
) {
113+
}
114+
115+
public function factory(string $name, int $price): Product
116+
{
117+
return new Product($name, $this->tax->applyTax($price));
118+
}
119+
}
120+
121+
final class Product
122+
{
123+
public function __construct(
124+
public readonly string $name,
125+
public readonly int $price,
126+
) {
127+
}
128+
}
129+
```
130+
131+
The decoded JSON is passed to the factory method as **named arguments**: each
132+
JSON key is matched to a parameter by name, unknown keys are ignored, and a
133+
missing required argument throws `InvalidWebFactoryKeyException`. (This is the
134+
web counterpart of media-query's positional `PDO::FETCH_FUNC` binding.)
135+
136+
`type` selects single object vs. list:
137+
138+
| `type` | JSON response | Result |
139+
|--------------|--------------------------|-----------------|
140+
| `'row'` | object `{...}` | one object |
141+
| `'row_list'` | array `[{...}, {...}]` | `array<Object>` |
142+
143+
`type` defaults to `'row_list'`. A `'row'` method whose response is a list
144+
takes the first element; a `'row_list'` method whose response is a single
145+
object wraps it into a one-element list.
146+
147+
You can also map straight to an entity **without** a factory: when the return
148+
type (or the `@return array<Entity>` docblock) is a class, each response is
149+
hydrated through the entity constructor — or through public properties if the
150+
class has no constructor.
151+
152+
```php
153+
#[WebQuery('product_item', type: 'row')]
154+
public function get(string $id): Product; // built via Product::__construct
155+
```
156+
157+
### Composing results with PostFetch
158+
159+
To wrap or aggregate the fetched objects into another type (totals, metadata,
160+
…), let the return type implement `PostFetchInterface`. Its static
161+
`fromContext()` receives the fetch result and returns the final object. It runs
162+
after the factory, carries no dependencies by design, and is the web analogue
163+
of media-query's `PostQueryInterface` (named *PostFetch* because a web call is a
164+
single fetch, with no multi-statement query context to span).
165+
166+
```php
167+
<?php
168+
use Ray\MediaQuery\PostFetchContext;
169+
use Ray\MediaQuery\PostFetchInterface;
170+
171+
final class ProductList implements PostFetchInterface
172+
{
173+
/** @param array<Product> $items */
174+
public function __construct(
175+
public readonly array $items,
176+
public readonly int $total,
177+
) {
178+
}
179+
180+
public static function fromContext(PostFetchContext $context): static
181+
{
182+
/** @var array<Product> $items */
183+
$items = is_array($context->result) ? $context->result : [];
184+
185+
return new self($items, count($items));
186+
}
187+
}
188+
```
189+
190+
```php
191+
#[WebQuery('product_list', factory: ProductFactory::class)]
192+
public function listAggregate(string $status): ProductList;
193+
```
194+
195+
`PostFetchContext` exposes the fetch `result`, the original method arguments
196+
(`query`), and the `#[WebQuery]` annotation (`webQuery`).
197+
81198
## Features
82199

83200
- **Web API Queries**: Execute HTTP requests via interface methods
84201
- **URI Template Support**: Dynamic URL parameter binding with `{param}` syntax
85202
- **Multiple Response Types**: JSON array, string, or PSR-7 message
203+
- **Domain Object Mapping (BDR)**: Map responses to typed domain objects via an injectable factory, with optional `PostFetch` composition
86204
- **Parameter Injection**: Automatic parameter conversion and injection
87205
- **HTTP Client Integration**: Built on the Guzzle HTTP client
88206

0 commit comments

Comments
 (0)