willdurand/hateoas
Hateoas is a PHP library for building HATEOAS-friendly REST representations. Configure links and embedded resources via annotations/attributes, XML or YAML, with expression language support, URL generators, and serializers (HAL JSON/XML) for rich hypermedia APIs.
Installation
composer require willdurand/hateoas:^3.14.0
Ensure compatibility with PHP 8.5+ by updating your composer.json constraints:
"require": {
"php": "^8.1 || ^8.2 || ^8.3 || ^8.4 || ^8.5",
"willdurand/hateoas": "^3.14.0"
}
First Use Case: Embedding Links in a Resource
use WillDurand\Hateoas\Rest\Resource;
use WillDurand\Hateoas\Link;
$resource = new Resource('https://api.example.com/users/1');
$resource->addLink(new Link('self', '/users/1'));
$resource->addLink(new Link('collection', '/users'));
return $resource->toArray(); // Returns HAL+JSON structure
Where to Look First
src/ directory for core classes (Resource, Link, Collection).CHANGELOG.md for PHP 8.5 fixes (e.g., SplObjectStorage deprecation resolved in PR #346).examples/ folder (if added) for 3.14.0 patterns.tests/ directory.$user = new Resource('/users/1');
$user->addLink('self', '/users/1');
$user->addLink('edit', '/users/1/edit', [], 'PATCH');
return $user->toArray(); // HAL+JSON output (PHP 8.5 verified)
$users = new Collection('/users');
$users->addLink('self', '/users');
$users->addLink('create', '/users', [], 'POST');
foreach ($userData as $user) {
$users->addItem(new Resource('/users/' . $user['id']));
}
return $users->toArray();
Use closures for runtime resolution (fully compatible in 3.14.0):
$resource->addLink('self', function () use ($userId) {
return "/users/{$userId}";
});
Note: Avoid SplObjectStorage in custom logic to prevent legacy warnings.
use App\Http\Resources\UserResource;
use WillDurand\Hateoas\Rest\Resource as HateoasResource;
public function show(User $user)
{
$resource = new HateoasResource('/users/' . $user->id);
$resource->addLink('self', route('users.show', $user));
$resource->addLink('edit', route('users.edit', $user), [], 'PATCH');
return new UserResource($user, $resource->toArray());
}
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->isSuccessful() && $request->wantsJson()) {
$resource = new Resource($request->url());
$resource->addLink('self', $request->url());
$response->setData(array_merge($response->getData(), [
'_links' => $resource->getLinks()
]));
}
return $response;
}
$user = new Resource('/users/1');
$user->addLink('self', '/users/1');
$user->addEmbedded('posts', new Collection('/users/1/posts'));
Tip: Use lazy-loading to avoid SplObjectStorage issues:
$user->addEmbedded('posts', function () {
return (new Collection("/users/1/posts"))->setMaxDepth(1);
});
PHP 8.5 SplObjectStorage Deprecation (RESOLVED)
^3.14.0 (includes PR #346).SplObjectStorage entirely. Use ArrayObject or arrays.Link Relativization (Unchanged)
Links default to absolute URLs. Use Laravel’s route() or url() helpers:
$link = new Link('self', route('users.show', $user)); // Laravel-aware
Circular References in Embedded Resources
$user->addEmbedded('posts', function () {
return (new Collection("/users/1/posts"))->setMaxDepth(1);
});
HTTP Method Conflicts
DELETE) may not render in all HAL+JSON parsers.$links = $resource->getLinks();
foreach ($links as $rel => $link) {
error_log("Link [$rel]: " . $link->getHref());
}
json_encode($resource->toArray(), JSON_PRETTY_PRINT) to catch serialization issues.href values resolve in Laravel’s router:
if (!Route::has($resource->getLink('self')->getHref())) {
throw new \RuntimeException("Route not found");
}
Custom Link Types (PHP 8.5 Features) Leverage PHP 8.5’s named arguments:
class ApiLink extends Link
{
public function __construct(
string $rel,
string $href,
array $attributes = [],
?string $method = null,
?string $name = null,
?string $title = null,
bool $isTemplated = false,
) {
parent::__construct($rel, $href, $attributes, $method, $name, $title);
$this->isTemplated = $isTemplated;
}
}
Laravel Service Provider Binding Bind the updated package:
public function register()
{
$this->app->singleton('hateoas.resource', function () {
return new Resource('');
});
}
Format-Specific Rendering
Override Resource::toArray() for JSON:API:
$resource->setData(['id' => 1, 'name' => 'John']);
$resource->toJsonApiArray(); // Custom method
Caching Links with RouteServiceProvider Pre-generate links to avoid runtime resolution:
$linkGenerator = app()->make(\Illuminate\Routing\Router::class);
$selfLink = $linkGenerator->to('users.show', ['user' => $user->id]);
$resource->addLink('self', $selfLink);
PHP 8.5 Performance Tip
Use match expressions for link type validation:
$method = match ($link->getMethod()) {
'DELETE' => 'delete',
'PATCH' => 'update',
default => 'get',
};
How can I help you explore Laravel packages today?