Requests
Requests represent individual API endpoint calls. They define the HTTP method, endpoint, headers, query parameters, and body.
Creating a Request
Section titled “Creating a Request”Extend the Request class and add an HTTP method attribute:
<?php
namespace App\Http\Requests;
use Cline\Relay\Support\Attributes\Methods\Get;use Cline\Relay\Core\Request;
#[Get]class GetUsersRequest extends Request{ public function endpoint(): string { return '/users'; }}HTTP Method Attributes
Section titled “HTTP Method Attributes”use Cline\Relay\Support\Attributes\Methods\Get;use Cline\Relay\Support\Attributes\Methods\Post;use Cline\Relay\Support\Attributes\Methods\Put;use Cline\Relay\Support\Attributes\Methods\Patch;use Cline\Relay\Support\Attributes\Methods\Delete;use Cline\Relay\Support\Attributes\Methods\Head;use Cline\Relay\Support\Attributes\Methods\Options;
#[Get]class GetUserRequest extends Request {}
#[Post]class CreateUserRequest extends Request {}
#[Put]class ReplaceUserRequest extends Request {}
#[Patch]class UpdateUserRequest extends Request {}
#[Delete]class DeleteUserRequest extends Request {}Dynamic Endpoints
Section titled “Dynamic Endpoints”#[Get]class GetUserRequest extends Request{ public function __construct( private readonly int $userId, ) {}
public function endpoint(): string { return "/users/{$this->userId}"; }}
// Usage$connector->send(new GetUserRequest(123));Multiple Parameters
Section titled “Multiple Parameters”#[Get]class GetRepositoryRequest extends Request{ public function __construct( private readonly string $owner, private readonly string $repo, ) {}
public function endpoint(): string { return "/repos/{$this->owner}/{$this->repo}"; }}
$connector->send(new GetRepositoryRequest('laravel', 'laravel'));Query Parameters
Section titled “Query Parameters”#[Get]class ListUsersRequest extends Request{ public function __construct( private readonly int $page = 1, private readonly int $perPage = 20, private readonly ?string $sort = null, ) {}
public function endpoint(): string { return '/users'; }
public function query(): array { return array_filter([ 'page' => $this->page, 'per_page' => $this->perPage, 'sort' => $this->sort, ]); }}
// Sends GET /users?page=2&per_page=50$connector->send(new ListUsersRequest(page: 2, perPage: 50));Adding Query Parameters Dynamically
Section titled “Adding Query Parameters Dynamically”$request = new ListUsersRequest();$request = $request->withQuery('filter', 'active');
$connector->send($request);Request Body
Section titled “Request Body”use Cline\Relay\Support\Attributes\ContentTypes\Json;
#[Post]#[Json]class CreateUserRequest extends Request{ public function __construct( private readonly string $name, private readonly string $email, private readonly ?string $role = 'user', ) {}
public function endpoint(): string { return '/users'; }
public function body(): array { return [ 'name' => $this->name, 'email' => $this->email, 'role' => $this->role, ]; }}Content Type Attributes
Section titled “Content Type Attributes”use Cline\Relay\Support\Attributes\ContentTypes\Json;
#[Post]#[Json]class CreateUserRequest extends Request{ public function body(): array { return ['name' => 'John']; }}Form Data
Section titled “Form Data”use Cline\Relay\Support\Attributes\ContentTypes\Form;
#[Post]#[Form]class LoginRequest extends Request{ public function body(): array { return ['username' => $this->username, 'password' => $this->password]; }}Multipart Form Data
Section titled “Multipart Form Data”use Cline\Relay\Support\Attributes\ContentTypes\Multipart;
#[Post]#[Multipart]class UploadFileRequest extends Request{ public function body(): array { return [ 'file' => fopen('/path/to/file.pdf', 'r'), 'name' => 'document.pdf', ]; }}use Cline\Relay\Support\Attributes\ContentTypes\Xml;
#[Post]#[Xml]class CreateOrderRequest extends Request {}Headers
Section titled “Headers”#[Get]class GetUserRequest extends Request{ public function headers(): array { return [ 'X-Custom-Header' => 'custom-value', 'Accept-Language' => 'en-US', ]; }}Adding Headers Dynamically
Section titled “Adding Headers Dynamically”$request = new GetUserRequest(1);$request = $request->withHeader('X-Request-ID', 'abc123');$request = $request->withHeaders([ 'X-Trace-ID' => 'trace-123', 'X-Span-ID' => 'span-456',]);
$connector->send($request);Authentication Helpers
Section titled “Authentication Helpers”// Bearer Token$request = (new GetUserRequest(1))->withBearerToken('your-token');
// Basic Auth$request = (new GetUserRequest(1))->withBasicAuth('username', 'password');Idempotency
Section titled “Idempotency”use Cline\Relay\Support\Attributes\Idempotent;
#[Post]#[Json]#[Idempotent]class CreatePaymentRequest extends Request {}
// Custom header name#[Idempotent(header: 'X-Request-ID')]class CreatePaymentRequest extends Request {}
// Custom key generation#[Idempotent(keyMethod: 'generateKey')]class CreatePaymentRequest extends Request{ public function generateKey(): string { return hash('sha256', $this->orderId . $this->amount); }}Manual Idempotency Keys
Section titled “Manual Idempotency Keys”$request = (new CreatePaymentRequest()) ->withIdempotencyKey('unique-key-123');Lifecycle Hooks
Section titled “Lifecycle Hooks”#[Get]class GetUserRequest extends Request{ protected function boot(): void { // Called before the request is sent }}Response Transformation
Section titled “Response Transformation”use Cline\Relay\Core\Response;
#[Get]class GetUserRequest extends Request{ public function transformResponse(Response $response): Response { return $response->withJsonKey('processed_at', now()->toIso8601String()); }}Accessing Resource and Connector
Section titled “Accessing Resource and Connector”#[Get]class GetUserRequest extends Request{ protected function boot(): void { $resource = $this->resource(); $connector = $this->connector();
if ($connector) { $baseUrl = $connector->baseUrl(); } }}Cloning Requests
Section titled “Cloning Requests”Requests are immutable:
$request = new GetUserRequest(1);$requestWithHeader = $request->withHeader('X-Custom', 'value');
// $request is unchanged// $requestWithHeader has the new headerThrow on Error
Section titled “Throw on Error”use Cline\Relay\Support\Attributes\ThrowOnError;
#[Get]#[ThrowOnError]class GetUserRequest extends Request {}
// Or be specific#[ThrowOnError(clientErrors: true, serverErrors: false)]class GetUserRequest extends Request {}Accessing Attributes
Section titled “Accessing Attributes”$request = new CreatePaymentRequest();
if ($request->hasAttribute(Idempotent::class)) { // ...}
$idempotent = $request->getAttribute(Idempotent::class);$method = $request->method();$contentType = $request->contentType();$isIdempotent = $request->isIdempotent();Debugging
Section titled “Debugging”$request = new CreateUserRequest('John', 'john@example.com');
$request->dump(); // Dump and continue$request->dd(); // Dump and dieMacros
Section titled “Macros”use Cline\Relay\Core\Request;
Request::macro('withCorrelationId', function (string $id) { return $this->withHeader('X-Correlation-ID', $id);});
$request = (new GetUserRequest(1))->withCorrelationId('abc-123');Full Example
Section titled “Full Example”<?php
namespace App\Http\Requests;
use Cline\Relay\Support\Attributes\ContentTypes\Json;use Cline\Relay\Support\Attributes\Idempotent;use Cline\Relay\Support\Attributes\Methods\Post;use Cline\Relay\Support\Attributes\ThrowOnError;use Cline\Relay\Core\Request;use Cline\Relay\Core\Response;
#[Post]#[Json]#[Idempotent(keyMethod: 'generateIdempotencyKey')]#[ThrowOnError]class CreateOrderRequest extends Request{ public function __construct( private readonly string $customerId, private readonly array $items, private readonly string $currency = 'USD', ) {}
public function endpoint(): string { return "/customers/{$this->customerId}/orders"; }
public function headers(): array { return ['X-Api-Version' => '2024-01']; }
public function body(): array { return [ 'items' => $this->items, 'currency' => $this->currency, 'created_at' => now()->toIso8601String(), ]; }
public function generateIdempotencyKey(): string { return hash('sha256', json_encode([ $this->customerId, $this->items, date('Y-m-d'), ])); }
protected function boot(): void { if (empty($this->items)) { throw new InvalidArgumentException('Order must have at least one item'); } }
public function transformResponse(Response $response): Response { return $response->withJsonKey('processed', true); }}