{"id":13519635,"url":"https://github.com/willdurand/Hateoas","last_synced_at":"2025-03-31T14:30:46.113Z","repository":{"id":4309181,"uuid":"5441790","full_name":"willdurand/Hateoas","owner":"willdurand","description":"A PHP library to support implementing representations for HATEOAS REST web services.","archived":false,"fork":false,"pushed_at":"2024-11-23T13:06:48.000Z","size":1032,"stargazers_count":1041,"open_issues_count":32,"forks_count":119,"subscribers_count":37,"default_branch":"master","last_synced_at":"2025-03-27T21:00:48.342Z","etag":null,"topics":["hateoas","json","php","rest","serializer"],"latest_commit_sha":null,"homepage":"https://williamdurand.fr/Hateoas/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/willdurand.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2012-08-16T16:56:17.000Z","updated_at":"2025-03-20T14:13:31.000Z","dependencies_parsed_at":"2024-06-18T10:50:49.522Z","dependency_job_id":"f22895a4-8553-4e31-8fe3-a52d827dd2d7","html_url":"https://github.com/willdurand/Hateoas","commit_stats":{"total_commits":486,"total_committers":54,"mean_commits":9.0,"dds":0.6193415637860082,"last_synced_commit":"0381e62e39d9bb63f557f8d595a599936a4674ad"},"previous_names":[],"tags_count":51,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willdurand%2FHateoas","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willdurand%2FHateoas/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willdurand%2FHateoas/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/willdurand%2FHateoas/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/willdurand","download_url":"https://codeload.github.com/willdurand/Hateoas/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246482870,"owners_count":20784792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hateoas","json","php","rest","serializer"],"created_at":"2024-08-01T05:02:01.409Z","updated_at":"2025-03-31T14:30:45.656Z","avatar_url":"https://github.com/willdurand.png","language":"PHP","funding_links":[],"categories":["PHP"," REST API","Servers","目录","Table of Contents","REST和API","REST and API"],"sub_categories":["PHP","API","Library"],"readme":"Hateoas\n=======\n\n[![GitHub Actions](https://github.com/willdurand/hateoas/workflows/CI/badge.svg)](https://github.com/willdurand/hateoas/actions?query=workflow%3A%22CI%22+branch%3Amaster)\n[![GitHub Actions](https://github.com/willdurand/hateoas/workflows/Coding%20Standards/badge.svg)](https://github.com/willdurand/hateoas/actions?query=workflow%3A%22Coding%20Standards%22+branch%3Amaster)\n[![Latest Stable\nVersion](https://poser.pugx.org/willdurand/hateoas/v/stable.png)](https://packagist.org/packages/willdurand/hateoas)\n[![PHP Version Require](https://poser.pugx.org/willdurand/hateoas/require/php)](https://packagist.org/packages/willdurand/hateoas)\n\nA PHP library to support implementing representations for HATEOAS REST web\nservices.\n\n\n* [Installation](#installation)\n  - [Working With Symfony](#working-with-symfony)\n* [Usage](#usage)\n  - [Introduction](#introduction)\n  - [Configuring Links](#configuring-links)\n  - [Embedding Resources](#embedding-resources)\n  - [Dealing With Collections](#dealing-with-collections)\n  - [Representations](#representations)\n    - [VndErrorRepresentation](#vnderrorrepresentation)\n  - [The Expression Language](#the-expression-language)\n    - [Context](#context)\n    - [Adding Your Own Context Variables](#adding-your-own-context-variables)\n    - [Expression Functions](#expression-functions)\n  - [URL Generators](#url-generators)\n  - [Helpers](#helpers)\n    - [LinkHelper](#linkhelper)\n  - [Twig Extensions](#twig-extensions)\n    - [LinkExtension](#linkextension)\n  - [Serializers \u0026 Formats](#serializers--formats)\n    - [The JsonHalSerializer](#the-jsonhalserializer)\n    - [The XmlSerializer](#the-xmlserializer)\n    - [The XmlHalSerializer](#the-xmlhalserializer)\n    - [Adding New Serializers](#adding-new-serializers)\n  - [The HateoasBuilder](#the-hateoasbuilder)\n    - [XML Serializer](#xml-serializer)\n    - [JSON Serializer](#json-serializer)\n    - [URL Generator](#url-generator)\n    - [Expression Evaluator/Expression Language](#expression-evaluatorexpression-language)\n    - [Relation Provider](#relationprovider)\n    - [(JMS) Serializer Specific](#jms-serializer-specific)\n    - [Others](#others)\n  - [Configuring a Cache Directory](#configuring-a-cache-directory)\n  - [Configuring Metadata Locations](#configuring-metadata-locations)\n  - [Extending The Library](#extending-the-library)\n* [Reference](#reference)\n  - [XML](#xml)\n  - [YAML](#yaml)\n  - [Annotations/Attributes](#annotations)\n    - [@Relation](#relation)\n    - [@Route](#route)\n    - [@Embedded](#embedded)\n    - [@Exclusion](#exclusion)\n    - [@RelationProvider](#relationprovider)\n* [Internals](#internals)\n* [Versioning](#versioning)\n\n\nInstallation\n------------\n\nThe recommended way to install Hateoas is through\n[Composer](http://getcomposer.org/). Require the `willdurand/hateoas` package\nby running the following command:\n\n```sh\ncomposer require willdurand/hateoas\n```\n\nThis will resolve the latest stable version.\n\nOtherwise, install the library and setup the autoloader yourself.\n\nIf you want to use [**annotations**](#annotations) for configuration you need\nto install the `doctrine/annotations` package:\n\n```sh\ncomposer require doctrine/annotations\n```\n\nIf your app uses PHP 8.1 or higher it is recommended to use native PHP\nattributes.\nIn this case you don't need to install the Doctrine package.\n\n### Working With Symfony\n\nThere is a bundle for that! Install the\n[BazingaHateoasBundle](https://github.com/willdurand/BazingaHateoasBundle), and\nenjoy!\n\n\nUsage\n-----\n\n\u003e **Important:** \n\u003e\n\u003e For those who use the `1.0` version, you can \n\u003e [jump to this documentation page](https://github.com/willdurand/Hateoas/blob/1.0/README.md#readme).\n\u003e \n\u003eFor those who use the `2.0` version, you can \n\u003e [jump to this documentation page](https://github.com/willdurand/Hateoas/blob/2.0/README.md#readme).\n\u003e\n\u003e The following documentation has been written for **Hateoas 3.0** and above.\n\n### Introduction\n\n**Hateoas** leverages the [Serializer](https://github.com/schmittjoh/serializer)\nlibrary to provide a nice way to build HATEOAS REST web services. HATEOAS stands\nfor **Hypermedia as the Engine of Application State**,\nand adds **hypermedia links** to your **representations** (i.e. your API\nresponses). [HATEOAS is about the discoverability of actions on a\nresource](http://timelessrepo.com/haters-gonna-hateoas).\n\nFor instance, let's say you have a User API which returns a **representation**\nof a single _user_ as follow:\n\n```json\n{\n    \"user\": {\n        \"id\": 123,\n        \"first_name\": \"John\",\n        \"last_name\": \"Doe\"\n    }\n}\n```\n\nIn order to tell your API consumers how to retrieve the data for this specific\nuser, you have to add your very first **link** to this representation, let's\ncall it `self` as it is the URI for this particular user:\n\n```json\n{\n    \"user\": {\n        \"id\": 123,\n        \"first_name\": \"John\",\n        \"last_name\": \"Doe\",\n        \"_links\": {\n            \"self\": { \"href\": \"http://example.com/api/users/123\" }\n        }\n    }\n}\n```\n\nLet's dig into Hateoas now.\n\n\n### Configuring Links\n\nIn Hateoas terminology, **links** are seen as **relations** added to resources.\nIt is worth mentioning that **relations** also refer to **embedded resources**\ntoo, but this topic will be covered in the [Embedding\nResources](#embedding-resources) section.\n\nA link is a relation which is identified by a `name` (e.g. `self`) and that\nhas an `href` parameter:\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse JMS\\Serializer\\Annotation as Serializer;\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n* @Serializer\\XmlRoot(\"user\")\n*\n* @Hateoas\\Relation(\"self\", href = \"expr('/api/users/' ~ object.getId())\")\n*/\nclass User\n{\n    /** @Serializer\\XmlAttribute */\n    private $id;\n    private $firstName;\n    private $lastName;\n\n    public function getId() {}\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse JMS\\Serializer\\Annotation as Serializer;\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Serializer\\XmlRoot('user')]\n#[Hateoas\\Relation('self', href: \"expr('/api/users/' ~ object.getId())\")]\nclass User\n{\n    #[Serializer\\XmlAttribute]\n    private $id;\n    private $firstName;\n    private $lastName;\n\n    public function getId() {}\n}\n```\n\n\u003c/details\u003e\n\n\nIn the example above, we configure a `self` relation that is a link because of\nthe `href` parameter. Its value, which may look weird at first glance, will be\nextensively covered in [The Expression Language](#the-expression-language)\nsection. This special value is used to generate a URI.\n\nIn this section, [**annotations/attributes**](#annotations) are used to configure Hateoas.\n[**XML**](#xml) and [**YAML**](#yaml) formats are also supported. If you wish,\nyou can use plain PHP too.\n\n**Important:** you must configure both the Serializer and Hateoas the same way. E.g.\nif you use YAML for configuring Serializer, use YAML for configuring Hateoas.\n\nThe easiest way to try HATEOAS is with the `HateoasBuilder`. The builder has\nnumerous methods to configure the Hateoas serializer, but we won't dig into\nthem right now (see [The HateoasBuilder](#the-hateoasbuilder)).\nEverything works fine out of the box:\n\n```php\nuse Hateoas\\HateoasBuilder;\n\n$hateoas = HateoasBuilder::create()-\u003ebuild();\n\n$user = new User(42, 'Adrien', 'Brault');\n$json = $hateoas-\u003eserialize($user, 'json');\n$xml  = $hateoas-\u003eserialize($user, 'xml');\n```\n\nThe `$hateoas` object is an instance of `JMS\\Serializer\\SerializerInterface`,\ncoming from the Serializer library. Hateoas does not come with its own\nserializer, it hooks into the JMS Serializer.\n\nBy default, Hateoas uses the [Hypertext Application\nLanguage](http://stateless.co/hal_specification.html) (HAL) for JSON\nserialization. This specifies the _structure_ of the response (e.g. that\n\"links\" should live under a `_links` key):\n\n```json\n{\n    \"id\": 42,\n    \"first_name\": \"Adrien\",\n    \"last_name\": \"Brault\",\n    \"_links\": {\n        \"self\": {\n            \"href\": \"/api/users/42\"\n        }\n    }\n}\n```\n\nFor XML, [Atom Links](http://tools.ietf.org/search/rfc4287#section-4.2.7)\nare used by default:\n\n```xml\n\u003cuser id=\"42\"\u003e\n    \u003cfirst_name\u003e\u003c![CDATA[Adrien]]\u003e\u003c/first_name\u003e\n    \u003clast_name\u003e\u003c![CDATA[Brault]]\u003e\u003c/last_name\u003e\n    \u003clink rel=\"self\" href=\"/api/users/42\"/\u003e\n\u003c/user\u003e\n```\n\nIt is worth mentioning that these formats are the **default ones**, not the\nonly available ones. You can use [different formats through different\nserializers, and even add your owns](#serializers--formats).\n\nNow that you know how to add **links**, let's see how to add **embedded\nresources**.\n\n### Embedding Resources\n\nSometimes, it's more efficient to embed related resources rather than\nlink to them, as it prevents clients from having to make extra requests to\nfetch those resources.\n\nAn **embedded resource** is a named **relation** that contains data, represented\nby the `embedded` parameter.\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse JMS\\Serializer\\Annotation as Serializer;\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n * ...\n *\n * @Hateoas\\Relation(\n *     \"manager\",\n *     href = \"expr('/api/users/' ~ object.getManager().getId())\",\n *     embedded = \"expr(object.getManager())\",\n *     exclusion = @Hateoas\\Exclusion(excludeIf = \"expr(object.getManager() === null)\")\n * )\n */\nclass User\n{\n    ...\n\n    /** @Serializer\\Exclude */\n    private $manager;\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse JMS\\Serializer\\Annotation as Serializer;\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Hateoas\\Relation(\n     'manager',\n     href: \"expr('/api/users/' ~ object.getManager().getId())\",\n     embedded: \"expr(object.getManager())\",\n     exclusion: new Hateoas\\Exclusion(excludeif: \"expr(object.getManager() === null)\"),\n )]\nclass User\n{\n    ...\n\n    #[Serializer\\Exclude]\n    private $manager;\n}\n```\n\n\u003c/details\u003e\n\n**Note:** You will need to exclude the manager property from the serialization,\notherwise both the serializer and Hateoas will serialize it.\nYou will also have to exclude the manager relation when the manager is `null`,\nbecause otherwise an error will occur when creating the `href` link (calling\n`getId()` on `null`).\n\n**Tip:** If the manager property is an object that already has a `_self`\nlink, you can re-use that value for the `href` instead of repeating it here.\nSee [LinkHelper](#linkhelper).\n\n```php\n$hateoas = HateoasBuilder::create()-\u003ebuild();\n\n$user = new User(42, 'Adrien', 'Brault', new User(23, 'Will', 'Durand'));\n$json = $hateoas-\u003eserialize($user, 'json');\n$xml  = $hateoas-\u003eserialize($user, 'xml');\n```\n\nFor `json`, the HAL representation places these embedded relations inside\nan `_embedded` key:\n\n```json\n{\n    \"id\": 42,\n    \"first_name\": \"Adrien\",\n    \"last_name\": \"Brault\",\n    \"_links\": {\n        \"self\": {\n            \"href\": \"/api/users/42\"\n        },\n        \"manager\": {\n            \"href\": \"/api/users/23\"\n        }\n    },\n    \"_embedded\": {\n        \"manager\": {\n            \"id\": 23,\n            \"first_name\": \"Will\",\n            \"last_name\": \"Durand\",\n            \"_links\": {\n                \"self\": {\n                    \"href\": \"/api/users/23\"\n                }\n            }\n        }\n    }\n}\n```\n\nIn XML, serializing `embedded` relations will create new elements:\n\n```xml\n\u003cuser id=\"42\"\u003e\n    \u003cfirst_name\u003e\u003c![CDATA[Adrien]]\u003e\u003c/first_name\u003e\n    \u003clast_name\u003e\u003c![CDATA[Brault]]\u003e\u003c/last_name\u003e\n    \u003clink rel=\"self\" href=\"/api/users/42\"/\u003e\n    \u003clink rel=\"manager\" href=\"/api/users/23\"/\u003e\n    \u003cmanager rel=\"manager\" id=\"23\"\u003e\n        \u003cfirst_name\u003e\u003c![CDATA[Will]]\u003e\u003c/first_name\u003e\n        \u003clast_name\u003e\u003c![CDATA[Durand]]\u003e\u003c/last_name\u003e\n        \u003clink rel=\"self\" href=\"/api/users/23\"/\u003e\n    \u003c/manager\u003e\n\u003c/user\u003e\n```\n\nThe tag name of an embedded resource is inferred from the\n[`@XmlRoot`](http://jmsyst.com/libs/serializer/master/reference/annotations#xmlroot)\nannotation (`xml_root_name` in YAML, `xml-root-name` in XML) coming from the\nSerializer configuration.\n\n### Dealing With Collections\n\nThe library provides several classes in the `Hateoas\\Representation\\*`\nnamespace to help you with common tasks. These are simple classes configured\nwith the library's annotations.\n\nThe `PaginatedRepresentation`, `OffsetRepresentation` and `CollectionRepresentation` classes are\nprobably the most interesting ones. These are helpful when your resource is\nactually a collection of resources (e.g. `/users` is a collection of users).\nThese help you represent the collection and add pagination and limits:\n\n```php\nuse Hateoas\\Representation\\PaginatedRepresentation;\nuse Hateoas\\Representation\\CollectionRepresentation;\n\n$paginatedCollection = new PaginatedRepresentation(\n    new CollectionRepresentation(array($user1, $user2, ...)),\n    'user_list', // route\n    array(), // route parameters\n    1,       // page number\n    20,      // limit\n    4,       // total pages\n    'page',  // page route parameter name, optional, defaults to 'page'\n    'limit', // limit route parameter name, optional, defaults to 'limit'\n    false,   // generate relative URIs, optional, defaults to `false`\n    75       // total collection size, optional, defaults to `null`\n);\n\n$json = $hateoas-\u003eserialize($paginatedCollection, 'json');\n$xml  = $hateoas-\u003eserialize($paginatedCollection, 'xml');\n```\n\nThe `CollectionRepresentation` offers a basic representation of an embedded collection.\n\nThe `PaginatedRepresentation` is designed to add `self`, `first`, and when\npossible `last`, `next`, and `previous` links.\n\nThe `OffsetRepresentation` works just like `PaginatedRepresentation` but is useful\nwhen pagination is expressed by `offset`, `limit` and `total`.\n\nThe `RouteAwareRepresentation` adds a `self` relation based on a given route.\n\nYou can generate **absolute URIs** by setting the `absolute` parameter to `true`\nin both the `PaginatedRepresentation` and the `RouteAwareRepresentation`.\n\nThe Hateoas library also provides a `PagerfantaFactory` to easily build\n`PaginatedRepresentation` from a\n[Pagerfanta](https://github.com/BabDev/Pagerfanta) instance. If you use\nthe Pagerfanta library, this is an easier way to create the collection\nrepresentations:\n\n```php\nuse Hateoas\\Configuration\\Route;\nuse Hateoas\\Representation\\Factory\\PagerfantaFactory;\n\n$pagerfantaFactory   = new PagerfantaFactory(); // you can pass the page,\n                                                // and limit parameters name\n$paginatedCollection = $pagerfantaFactory-\u003ecreateRepresentation(\n    $pager,\n    new Route('user_list', array())\n);\n\n$json = $hateoas-\u003eserialize($paginatedCollection, 'json');\n$xml  = $hateoas-\u003eserialize($paginatedCollection, 'xml');\n```\n\nYou would get the following JSON content:\n\n```json\n{\n    \"page\": 1,\n    \"limit\": 10,\n    \"pages\": 1,\n    \"_links\": {\n        \"self\": {\n            \"href\": \"/api/users?page=1\u0026limit=10\"\n        },\n        \"first\": {\n            \"href\": \"/api/users?page=1\u0026limit=10\"\n        },\n        \"last\": {\n            \"href\": \"/api/users?page=1\u0026limit=10\"\n        }\n    },\n    \"_embedded\": {\n        \"items\": [\n            { \"id\": 123 },\n            { \"id\": 456 }\n        ]\n    }\n}\n```\n\nAnd the following XML content:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003ccollection page=\"1\" limit=\"10\" pages=\"1\"\u003e\n    \u003centry id=\"123\"\u003e\u003c/entry\u003e\n    \u003centry id=\"456\"\u003e\u003c/entry\u003e\n    \u003clink rel=\"self\" href=\"/api/users?page=1\u0026amp;limit=10\" /\u003e\n    \u003clink rel=\"first\" href=\"/api/users?page=1\u0026amp;limit=10\" /\u003e\n    \u003clink rel=\"last\" href=\"/api/users?page=1\u0026amp;limit=10\" /\u003e\n\u003c/collection\u003e\n```\n\nIf you want to customize the inlined `CollectionRepresentation`, pass one as\nthird argument of the `createRepresentation()` method:\n\n```php\nuse Hateoas\\Representation\\Factory\\PagerfantaFactory;\n\n$pagerfantaFactory   = new PagerfantaFactory(); // you can pass the page and limit parameters name\n$paginatedCollection = $pagerfantaFactory-\u003ecreateRepresentation(\n    $pager,\n    new Route('user_list', array()),\n    new CollectionRepresentation($pager-\u003egetCurrentPageResults())\n);\n\n$json = $hateoas-\u003eserialize($paginatedCollection, 'json');\n$xml  = $hateoas-\u003eserialize($paginatedCollection, 'xml');\n```\n\nIf you want to change the xml root name of the collection, create a new\nclass with the xml root configured and use the inline mechanism:\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse JMS\\Serializer\\Annotation as Serializer;\n\n/**\n * @Serializer\\XmlRoot(\"users\")\n */\nclass UsersRepresentation\n{\n    /**\n     * @Serializer\\Inline\n     */\n    private $inline;\n\n    public function __construct($inline)\n    {\n        $this-\u003einline = $inline;\n    }\n}\n\n$paginatedCollection = ...;\n$paginatedCollection = new UsersRepresentation($paginatedCollection);\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse JMS\\Serializer\\Annotation as Serializer;\n\n#[Serializer\\XmlRoot('users')]\nclass UsersRepresentation\n{\n    #[Serializer\\Inline]\n    private $inline;\n\n    public function __construct($inline)\n    {\n        $this-\u003einline = $inline;\n    }\n}\n\n$paginatedCollection = ...;\n$paginatedCollection = new UsersRepresentation($paginatedCollection);\n```\n\n\u003c/details\u003e\n\n### Representations\n\nAs mentionned in the previous section, **representations** are classes configured\nwith the library's annotations in order to help you with common tasks. The\n**collection representations** are described in [Dealing With\nCollection](#dealing-with-collections).\n\n#### VndErrorRepresentation\n\nThe `VndErrorRepresentation` allows you to describe an error response following\nthe [`vnd.error` specification](https://github.com/blongden/vnd.error).\n\n```php\n$error = new VndErrorRepresentation(\n    'Validation failed',\n    42,\n    'http://.../',\n    'http://.../'\n);\n```\n\nSerializing such a representation in XML and JSON would give you the following\noutputs:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n    \u003cresource logref=\"42\"\u003e\n    \u003cmessage\u003e\u003c![CDATA[Validation failed]]\u003e\u003c/message\u003e\n    \u003clink rel=\"help\" href=\"http://.../\"/\u003e\n    \u003clink rel=\"describes\" href=\"http://.../\"/\u003e\n\u003c/resource\u003e\n```\n\n```json\n{\n    \"message\": \"Validation failed\",\n    \"logref\": 42,\n    \"_links\": {\n        \"help\": {\n            \"href\": \"http://.../\"\n        },\n        \"describes\": {\n            \"href\": \"http://.../\"\n        }\n    }\n}\n```\n\n**Hint:** it is recommended to create your own error classes that extend the\n`VndErrorRepresentation` class.\n\n### The Expression Language\n\nHateoas relies on the powerful Symfony\n[ExpressionLanguage](http://symfony.com/doc/current/components/expression_language/introduction.html)\ncomponent to retrieve values such as links, ids or objects to embed.\n\nEach time you fill in a value (e.g. a Relation `href` in annotations or YAML),\nyou can either pass a **hardcoded value** or an **expression**.\nIn order to use the Expression Language, you have to use the `expr()` notation:\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n * @Hateoas\\Relation(\"self\", href = \"expr('/api/users/' ~ object.getId())\")\n */\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Hateoas\\Relation('self', href: \"expr('/api/users/' ~ object.getId())\")]\n```\n\n\u003c/details\u003e\n\nYou can learn more about the Expression Syntax by reading the official\ndocumentation: [The Expression\nSyntax](http://symfony.com/doc/current/components/expression_language/syntax.html).\n\n#### Context\n\nNatively, a special variable named `object` is available in each expression, and\nrepresents the current object:\n\n```\nexpr(object.getId())\n```\n\nWe call such a variable a **context variable**.\n\nYou can add your own context variables to the Expression Language context by\nadding them to the expression evaluator.\n\n##### Adding Your Own Context Variables\n\nUsing the `HateoasBuilder`, call the `setExpressionContextVariable()` method to add\nnew context variables:\n\n```php\nuse Hateoas\\HateoasBuilder;\n\n$hateoas = HateoasBuilder::create()\n    -\u003esetExpressionContextVariable('foo', new Foo())\n    -\u003ebuild();\n```\n\nThe `foo` variable is now available:\n\n```\nexpr(foo !== null)\n```\n\n##### Expression Functions\n\nFor more info on how to add functions to the expression language, please refer to \n[https://symfony.com/doc/current/components/expression_language/extending.html](https://symfony.com/doc/current/components/expression_language/extending.html)\n\n### URL Generators\n\nSince you can use the [Expression Language](#the-expression-language) to define\nthe relations links (`href` key), you can do a lot by default. However if you\nare using a framework, chances are that you will want to use routes to build\nlinks.\n\nYou will first need to configure an `UrlGenerator` on the builder. You can\neither implement the `Hateoas\\UrlGenerator\\UrlGeneratorInterface`, or use the\n`Hateoas\\UrlGenerator\\CallableUrlGenerator`:\n\n```php\nuse Hateoas\\UrlGenerator\\CallableUrlGenerator;\n\n$hateoas = HateoasBuilder::create()\n    -\u003esetUrlGenerator(\n        null, // By default all links uses the generator configured with the null name\n        new CallableUrlGenerator(function ($route, array $parameters, $absolute) use ($myFramework) {\n            return $myFramework-\u003egenerateTheUrl($route, $parameters, $absolute);\n        })\n    )\n    -\u003ebuild()\n;\n```\n\nYou will then be able to use the [@Route](#route) annotation:\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n * @Hateoas\\Relation(\n *      \"self\",\n *      href = @Hateoas\\Route(\n *          \"user_get\",\n *          parameters = {\n *              \"id\" = \"expr(object.getId())\"\n *          }\n *      )\n * )\n */\nclass User\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Hateoas\\Relation(\n    'self',\n    href: new Hateoas\\Route(\n        'user_get',\n        parameters: [\n            'id' =\u003e 'expr(object.getId())',\n        ],\n    )\n)]\nclass User\n```\n\n\u003c/details\u003e\n\n```json\n{\n    \"id\": 42,\n    \"first_name\": \"Adrien\",\n    \"last_name\": \"Brault\",\n    \"_links\": {\n        \"self\": {\n            \"href\": \"/api/users/42\"\n        }\n    }\n}\n```\n\nNote that the library comes with a `SymfonyUrlGenerator`. For example, to use it\nin Silex:\n\n```php\nuse Hateoas\\UrlGenerator\\SymfonyUrlGenerator;\n\n$hateoas = HateoasBuilder::create()\n    -\u003esetUrlGenerator(null, new SymfonyUrlGenerator($app['url_generator']))\n    -\u003ebuild()\n;\n```\n\n### Helpers\n\nHateoas provides a set of helpers to ease the process of building APIs.\n\n#### LinkHelper\n\nThe `LinkHelper` class provides a `getLinkHref($object, $rel, $absolute = false)`\nmethod that allows you to get the _href_ value of any object, for any given\nrelation name. It is able to generate a URI (either absolute or relative) from\nany **link** relation:\n\n```php\n$user = new User(123, 'William', 'Durand');\n\n$linkHelper-\u003egetLinkHref($user, 'self');\n// /api/users/123\n\n$linkHelper-\u003egetLinkHref($user, 'self', true);\n// http://example.com/api/users/123\n```\n\n##### The `link` Function\n\nThe feature above is also available in your expressions (cf. [The Expression\nLanguage](#the-expression-language)) through the `link(object, rel, absolute)`\n**function**:\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\n/**\n * @Hateoas\\Relation(\n *     \"self\",\n *     href = @Hateoas\\Route(\"post_get\", parameters = {\"id\" = \"expr(object.getId())\"})\n * )\n */\nclass Post {}\n\n/**\n * @Hateoas\\Relation(\n *     \"self\",\n *     href = @Hateoas\\Route(\"user_get\", parameters = {\"id\" = \"expr(object.getId())\"})\n * )\n * @Hateoas\\Relation(\n *     \"post\",\n *     href = \"expr(link(object.getPost(), 'self', true))\"\n * )\n * @Hateoas\\Relation(\n *     \"relative\",\n *     href = \"expr(link(object.getRelativePost(), 'self'))\"\n * )\n */\nclass User\n{\n    ...\n\n    public function getPost()\n    {\n        return new Post(456);\n    }\n\n    public function getRelativePost()\n    {\n        return new Post(789);\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\n#[Hateoas\\Relation(\n    'self',\n    href: new Hateoas\\Route(\n        'post_get',\n        parameters: [\n            'id' =\u003e 'expr(object.getId())',\n        ],\n    ),\n)]\nclass Post {}\n\n#[Hateoas\\Relation(\n    'self',\n    href: new Hateoas\\Route(\n        'user_get',\n        parameters: [\n            'id' =\u003e 'expr(object.getId())',\n        ],\n    ),\n)]\n#[Hateoas\\Relation(\n    'post',\n    href: \"expr(link(object.getPost(), 'self', true))\",\n)]\n#[Hateoas\\Relation(\n    'relative',\n    href: \"expr(link(object.getRelativePost(), 'self'))\",\n)]\nclass User\n{\n    ...\n\n    public function getPost()\n    {\n        return new Post(456);\n    }\n\n    public function getRelativePost()\n    {\n        return new Post(789);\n    }\n}\n```\n\n\u003c/details\u003e\n\nPay attention to the `href` expressions for the `post` and `relative` relations,\nas well as their corresponding values in the following JSON content:\n\n```json\n{\n    \"user\": {\n        \"id\": 123,\n        \"first_name\": \"William\",\n        \"last_name\": \"Durand\",\n        \"_links\": {\n            \"self\": { \"href\": \"http://example.com/api/users/123\" },\n            \"post\": { \"href\": \"http://example.com/api/posts/456\" },\n            \"relative\": { \"href\": \"/api/posts/789\" }\n        }\n    }\n}\n```\n\nIt is worth mentioning that you can **force** whether you want an absolute or\nrelative URI by using the third argument in both the `getLinkHref()` method and\nthe `link` function.\n\n**Important:** by default, all URIs will be **relative**, even those which are\ndefined as **absolute** in their configuration.\n\n```php\n$linkHelper-\u003egetLinkHref($user, 'post');\n// /api/posts/456\n\n$linkHelper-\u003egetLinkHref($user, 'post', true);\n// http://example.com/api/posts/456\n\n$linkHelper-\u003egetLinkHref($user, 'relative');\n// /api/posts/789\n\n$linkHelper-\u003egetLinkHref($user, 'relative', true);\n// http://example.com/api/posts/789\n```\n\n### Twig Extensions\n\nHateoas also provides a set of [Twig](http://twig.sensiolabs.org) extensions.\n\n#### LinkExtension\n\nThe `LinkExtension` allows you to use the [LinkHelper](#linkhelper) into your\nTwig templates, so that you can generate links in your HTML templates for\ninstance.\n\nThis extension exposes the `getLinkHref()` helper's method through the\n`link_href` Twig function:\n\n```html+jinja\n{{ link_href(user, 'self') }}\n{# will generate: /users/123 #}\n\n{{ link_href(will, 'self', false) }}\n{# will generate: /users/123 #}\n\n{{ link_href(will, 'self', true) }}\n{# will generate: http://example.com/users/123 #}\n```\n\n### Serializers \u0026 Formats\n\nHateoas provides a set of **serializers**. Each **serializer** allows you to\ngenerate either XML or JSON content following a specific **format**, such as\n[HAL](http://stateless.co/hal_specification.html), or [Atom\nLinks](http://tools.ietf.org/search/rfc4287#section-4.2.7) for instance.\n\n#### The JsonHalSerializer\n\nThe `JsonHalSerializer` allows you to generate HAL compliant relations in JSON.\nIt is the default JSON serializer in Hateoas.\n\nHAL provides its linking capability with a convention which says that a resource\nobject has a reserved property called `_links`. This property is an object that\ncontains links. These links are key'ed by their link relation.\n\nHAL also describes another convention which says that a resource may have\nanother reserved property named `_embedded`. This property is similar to `_links`\nin that embedded resources are key'ed by relation name. The main difference is\nthat rather than being links, the values are resource objects.\n\n![](http://stateless.co/info-model.png)\n\n```json\n{\n    \"message\": \"Hello, World!\",\n    \"_links\": {\n        \"self\": {\n            \"href\": \"/notes/0\"\n        }\n    },\n    \"_embedded\": {\n        \"associated_events\": [\n            {\n                \"name\": \"SymfonyCon\",\n                \"date\": \"2013-12-12T00:00:00+0100\"\n            }\n        ]\n    }\n}\n```\n\n#### The XmlSerializer\n\nThe `XmlSerializer` allows you to generate [Atom\nLinks](http://tools.ietf.org/search/rfc4287#section-4.2.7) into your XML\ndocuments. It is the default XML serializer.\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cnote\u003e\n    \u003cmessage\u003e\u003c![CDATA[Hello, World!]]\u003e\u003c/message\u003e\n    \u003clink rel=\"self\" href=\"/notes/0\" /\u003e\n    \u003cevents rel=\"associated_events\"\u003e\n        \u003cevent\u003e\n            \u003cname\u003e\u003c![CDATA[SymfonyCon]]\u003e\u003c/name\u003e\n            \u003cdate\u003e\u003c![CDATA[2013-12-12T00:00:00+0100]]\u003e\u003c/date\u003e\n        \u003c/event\u003e\n    \u003c/events\u003e\n\u003c/note\u003e\n```\n\n#### The XmlHalSerializer\n\nThe `XmlHalSerializer` allows you to generate HAL compliant relations in XML.\n\nHAL in XML is similar to [HAL in JSON](#the-jsonhalserializer) in the sense that\nit describes `link` tags and `resource` tags.\n\n**Note:** the `self` relation will actually become an attribute of the main\nresource instead of being a `link` tag. Other links will be generated as `link`\ntags.\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cnote href=\"/notes/0\"\u003e\n    \u003cmessage\u003e\u003c![CDATA[Hello, World!]]\u003e\u003c/message\u003e\n\n    \u003cresource rel=\"associated_events\"\u003e\n        \u003cname\u003e\u003c![CDATA[SymfonyCon]]\u003e\u003c/name\u003e\n        \u003cdate\u003e\u003c![CDATA[2013-12-12T00:00:00+0100]]\u003e\u003c/date\u003e\n    \u003c/resource\u003e\n\u003c/note\u003e\n```\n\n#### Adding New Serializers\n\nYou must implement the `SerializerInterface` that describes two methods to serialize\n**links** and **embedded** relations.\n\n### The HateoasBuilder\n\nThe `HateoasBuilder` class is used to easily configure Hateoas thanks to a\npowerful and fluent API.\n\n```php\nuse Hateoas\\HateoasBuilder;\n\n$hateoas = HateoasBuilder::create()\n    -\u003esetCacheDir('/path/to/cache/dir')\n    -\u003esetDebug($trueOrFalse)\n    -\u003esetDefaultXmlSerializer()\n    ...\n    -\u003ebuild();\n```\n\nAll the methods below return the current builder, so that you can chain them.\n\n#### XML Serializer\n\n* `setXmlSerializer(SerializerInterface $xmlSerializer)`: sets the XML\n  serializer to use. Default is: `XmlSerializer`;\n* `setDefaultXmlSerializer()`: sets the default XML serializer\n  (`XmlSerializer`).\n\n#### JSON Serializer\n\n* `setJsonSerializer(SerializerInterface $jsonSerializer)`: sets the JSON\n  serializer to use. Default is: `JsonHalSerializer`;\n* `setDefaultJsonSerializer()`: sets the default JSON serializer\n  (`JsonHalSerializer`).\n\n#### URL Generator\n\n* `setUrlGenerator($name = null, UrlGeneratorInterface $urlGenerator)`: adds a\n  new named URL generator. If `$name` is `null`, the URL generator will be the\n  default one.\n\n#### Expression Evaluator/Expression Language\n\n* `setExpressionContextVariable($name, $value)`: adds a new expression context\n  variable;\n* `setExpressionLanguage(ExpressionLanguage $expressionLanguage)`;\n\n#### (JMS) Serializer Specific\n\n* `includeInterfaceMetadata($include)`: whether to include the metadata from the\n  interfaces;\n* `setMetadataDirs(array $namespacePrefixToDirMap)`: sets a map of namespace\n  prefixes to directories. This method overrides any previously defined\n  directories;\n* `addMetadataDir($dir, $namespacePrefix = '')`: adds a directory where the\n  serializer will look for class metadata;\n* `addMetadataDirs(array $namespacePrefixToDirMap)`: adds a map of namespace\n  prefixes to directories;\n* `replaceMetadataDir($dir, $namespacePrefix = '')`: similar to\n  `addMetadataDir()`, but overrides an existing entry.\n\nPlease read the official [Serializer\ndocumentation](http://jmsyst.com/libs/serializer) for more details.\n\n#### Others\n\n* `setDebug($debug)`: enables or disables the debug mode;\n* `setCacheDir($dir)`: sets the cache directory.\n\n### Configuring a Cache Directory\n\nBoth the serializer and the Hateoas libraries collect metadata about your\nobjects from various sources such as YML, XML, or annotations. In order to make\nthis process as efficient as possible, it is recommended that you allow the\nHateoas library to cache this information. To do that, configure a cache\ndirectory:\n\n```php\n$builder = \\Hateoas\\HateoasBuilder::create();\n\n$hateoas = $builder\n    -\u003esetCacheDir($someWritableDir)\n    -\u003ebuild();\n```\n\n### Configuring Metadata Locations\n\nHateoas supports several metadata sources. By default, it uses Doctrine\nannotations (PHP \u003c 8.1) or native PHP attributes (PHP \u003e= 8.1), but you may also\nstore metadata in XML, or YAML files. For the latter, it is necessary to\nconfigure a metadata directory where those files are located:\n\n```php\n$hateoas = \\Hateoas\\HateoasBuilder::create()\n    -\u003eaddMetadataDir($someDir)\n    -\u003ebuild();\n```\n\nHateoas would expect the metadata files to be named like the fully qualified\nclass names where all `\\` are replaced with `.`. If you class would be named\n`Vendor\\Package\\Foo` the metadata file would need to be located at\n`$someDir/Vendor.Package.Foo.(xml|yml)`.\n\n### Extending The Library\n\nHateoas allows frameworks to dynamically add relations to classes by providing\nan extension point at configuration level. This feature can be useful for those\nwho want to to create a new layer on top of Hateoas, or to add \"global\"\nrelations rather than copying the same configuration on each class.\n\nIn order to leverage this mechanism, the `ConfigurationExtensionInterface`\ninterface has to be implemented:\n\n```php\nuse Hateoas\\Configuration\\Metadata\\ConfigurationExtensionInterface;\nuse Hateoas\\Configuration\\Metadata\\ClassMetadataInterface;\nuse Hateoas\\Configuration\\Relation;\n\nclass AcmeFooConfigurationExtension implements ConfigurationExtensionInterface\n{\n    /**\n     * {@inheritDoc}\n     */\n    public function decorate(ClassMetadataInterface $classMetadata): void\n    {\n        if (0 === strpos('Acme\\Foo\\Model', $classMetadata-\u003egetName())) {\n            // Add a \"root\" relation to all classes in the `Acme\\Foo\\Model` namespace\n            $classMetadata-\u003eaddRelation(\n                new Relation(\n                    'root',\n                    '/'\n                )\n            );\n        }\n    }\n}\n```\n\nYou can access the existing relations loaded from Annotations, XML, or YAML with\n`$classMetadata-\u003egetRelations()`.\n\nIf the `$classMetadata` has relations, or if you add relations to it, its\nrelations will be cached. So if you read configuration files (Annotations, XML,\nor YAML), make sure to reference them on the class metadata:\n\n```php\n$classMetadata-\u003efileResources[] = $file;\n```\n\n\nReference\n---------\n\n### XML\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cserializer\u003e\n\u003cclass name=\"Acme\\Demo\\Representation\\User\" h:providers=\"Class::getRelations expr(sevice('foo').getMyAdditionalRelations())\" xmlns:h=\"https://github.com/willdurand/Hateoas\"\u003e\n        \u003ch:relation rel=\"self\"\u003e\n            \u003ch:href uri=\"http://acme.com/foo/1\" /\u003e\n        \u003c/h:relation\u003e\n        \u003ch:relation rel=\"friends\"\u003e\n            \u003ch:href route=\"user_friends\" generator=\"my_custom_generator\"\u003e\n                \u003ch:parameter name=\"id\" value=\"expr(object.getId())\" /\u003e\n                \u003ch:parameter name=\"page\" value=\"1\" /\u003e\n            \u003c/h:ref\u003e\n            \u003ch:embedded xml-element-name=\"users\"\u003e\n                \u003ch:content\u003eexpr(object.getFriends())\u003c/h:content\u003e\n                \u003ch:exclusion ... /\u003e\n            \u003c/h:embedded\u003e\n            \u003ch:exclusion groups=\"Default, user_full\" since-version=\"1.0\" until-version=\"2.2\" exclude-if=\"expr(object.getFriends() === null)\" /\u003e\n        \u003c/h:relation\u003e\n    \u003c/class\u003e\n\u003c/serializer\u003e\n```\nSee the\n[`hateoas.xsd`](https://github.com/willdurand/Hateoas/blob/master/hateoas.xsd)\nfile for more details.\n\n### YAML\n\n```yaml\nAcme\\Demo\\Representation\\User:\n    relations:\n        -\n            rel: self\n            href: http://acme.com/foo/1\n        -\n            rel: friends\n            href:\n                route: user_friends\n                parameters:\n                    id: expr(object.getId())\n                    page: 1\n                generator: my_custom_generator\n                absolute: false\n            embedded:\n                content: expr(object.getFriends())\n                xmlElementName: users\n                exclusion: ...\n            exclusion:\n                groups: [Default, user_full]\n                since_version: 1.0\n                until_version: 2.2\n                exclude_if: expr(object.getFriends() === null)\n\n    relation_providers: [ \"Class::getRelations\", \"expr(sevice('foo').getMyAdditionalRelations())\" ]\n```\n\n### Annotations\n\n#### @Relation\n\nThis annotation can be defined on a class.\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n * @Hateoas\\Relation(\n *     name = \"self\",\n *     href = \"http://hello\",\n *     embedded = \"expr(object.getHello())\",\n *     attributes = { \"foo\" = \"bar\" },\n *     exclusion = ...,\n * )\n */\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Hateoas\\Relation(\n    name: 'self',\n    href: 'http://hello',\n    embedded: 'expr(object.getHello())',\n    attributes: ['foo' =\u003e 'bar'],\n    exclusion: '...',\n)]\n```\n\n\u003c/details\u003e\n\n| Property   | Required               | Content                         | Expression language   |\n|------------|------------------------|---------------------------------|-----------------------|\n| name       | Yes                    | string                          | No                    |\n| href       | If embedded is not set | string / [@Route](#route)       | Yes                   |\n| embedded   | If href is not set     | string / [@Embedded](#embedded) | Yes                   |\n| attributes | No                     | array                           | Yes on values         |\n| exclusion  | No                     | [@Exclusion](#exclusion)        | N/A                   |\n\n**Important:** `attributes` are only used on **link relations** (i.e. combined\nwith the `href` property, not with the `embedded` one).\n\n#### @Route\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n * @Hateoas\\Relation(\n *     name = \"self\",\n *     href = @Hateoas\\Route(\n *         \"user_get\",\n *         parameters = { \"id\" = \"expr(object.getId())\" },\n *         absolute = true,\n *         generator = \"my_custom_generator\"\n *     )\n * )\n */\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Hateoas\\Relation(\n    name: 'self',\n    href: new Hateoas\\Route(\n        'user_get',\n        parameters: ['id' = 'expr(object.getId())'],\n        absolute: true,\n        generator: 'my_custom_generator',\n    ),\n)]\n```\n\n\u003c/details\u003e\n\nThis annotation can be defined in the **href** property of the\n[@Relation](#relation) annotation. This is allows you to your URL generator,\nif you have configured one.\n\n| Property   | Required            | Content          | Expression language             |\n|------------|---------------------|------------------|---------------------------------|\n| name       | Yes                 | string           | No                              |\n| parameters | Defaults to array() | array / string   | Yes (string + array values)     |\n| absolute   | Defaults to false   | boolean / string | Yes                             |\n| generator  | No                  | string / null    | No                              |\n\n#### @Embedded\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n * @Hateoas\\Relation(\n *     name = \"friends\",\n *     embedded = @Hateoas\\Embedded(\n *         \"expr(object.getFriends())\",\n *         exclusion = ...,\n *         xmlElementName = \"users\"\n *     )\n * )\n */\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Hateoas\\Relation(\n    name: 'friends',\n    embedded: new Hateoas\\Embedded(\n        'expr(object.getFriends())',\n        exclusion: '...',\n        xmlElementName: 'users',\n    ),\n)]\n```\n\n\u003c/details\u003e\n\nThis annotation can be defined in the **embedded** property of the\n[@Relation](#relation) annotation. It is useful if you need configure the\n`exclusion` or `xmlElementName` options for the embedded resource.\n\n| Property       | Required            | Content                  | Expression language    |\n|----------------|---------------------|--------------------------|------------------------|\n| content        | Yes                 | string / array           | Yes (string)           |\n| exclusion      | Defaults to array() | [@Exclusion](#exclusion) | N/A                    |\n| xmlElementName | Defaults to array() | string                   | No                     |\n\n#### @Exclusion\n\nThis annotation can be defined in the **exclusion** property of both the\n[@Relation](#relation) and [@Embedded](#embedded) annotations.\n\n| Property     | Required | Content          | Expression language    |\n|--------------|----------|------------------|------------------------|\n| groups       | No       | array            | No                     |\n| sinceVersion | No       | string           | No                     |\n| untilVersion | No       | string           | No                     |\n| maxDepth     | No       | integer          | No                     |\n| excludeIf    | No       | string / boolean | Yes                    |\n\nAll values except `excludeIf` act the same way as when they are used directly\non the regular properties with the serializer.\n\n`excludeIf` expects a boolean and is helpful when another expression would fail\nunder some circumstances. In this example, if the `getManager` method is `null`,\nyou should exclude it to prevent the URL generation from failing:\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n * @Hateoas\\Relation(\n *     \"manager\",\n *     href = @Hateoas\\Route(\n *         \"user_get\",\n *         parameters = { \"id\" = \"expr(object.getManager().getId())\" }\n *     ),\n *     exclusion = @Hateoas\\Exclusion(excludeIf = \"expr(null === object.getManager())\")\n * )\n */\nclass User\n{\n    public function getId() {}\n\n    /**\n     * @return User|null\n     */\n    public function getManager() {}\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Hateoas\\Relation(\n    name: 'manager',\n    href: new Hateoas\\Route(\n        'user_get',\n        parameters: ['id' =\u003e 'expr(object.getManager().getId())'],\n    ),\n    exclusion: new Hateoas\\Exclusion(excludeIf: 'expr(null === object.getManager())')\n)]\nclass User\n{\n    public function getId() {}\n\n    public function getManager(): ?User {}\n}\n```\n\n\u003c/details\u003e\n\n#### @RelationProvider\n\nThis annotation can be defined on a class.\nIt is useful if you wish to serialize multiple-relations(links).\nAs an example:\n\n```\n{\n  \"_links\": {\n    \"relation_name\": [\n      {\"href\": \"link1\"},\n      {\"href\": \"link2\"},\n      {\"href\": \"link3\"}\n    ]\n  }\n}\n```\n\n| Property | Required | Content | Expression language |\n|----------|----------|---------|---------------------|\n| name     | Yes      | string  | Yes                 |\n\nIt can be \"name\":\n\n- A function: `my_func`\n- A static method: `MyClass::getExtraRelations`\n- An expression: `expr(service('user.rel_provider').getExtraRelations())`\n\nHere and example using the expression language:\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAnnotation (PHP \u003c 8.1)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n/**\n * @Hateoas\\RelationProvider(\"expr(service('user.rel_provider').getExtraRelations())\")\n */\nclass User\n{\n    ...\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\n\u003csummary\u003eAttribute (PHP 8.1 and greater)\u003c/summary\u003e\n\n```php\nuse Hateoas\\Configuration\\Annotation as Hateoas;\n\n#[Hateoas\\RelationProvider(\"expr(service('user.rel_provider').getExtraRelations())\")]\nclass User\n{\n    ...\n}\n```\n\n\u003c/details\u003e\n\nHere the `UserRelPrvider` class:\n\n```php\nuse Hateoas\\Configuration\\Relation;\nuse Hateoas\\Configuration\\Route;\n\nclass UserRelPrvider\n{\n    private $evaluator;\n    \n    public function __construct(CompilableExpressionEvaluatorInterface $evaluator)\n    {\n        $this-\u003eevaluator = $evaluator;\n    }\n\n    /**\n     * @return Relation[]\n     */\n    public function getExtraRelations(): array\n    {\n        // You need to return the relations\n        return array(\n            new Relation(\n                'self',\n                new Route(\n                    'foo_get',\n                    ['id' =\u003e $this-\u003eevaluator-\u003eparse('object.getId()', ['object'])]\n                )\n            )\n        );\n    }\n}\n```\n\n`$this-\u003eevaluator` implementing `CompilableExpressionEvaluatorInterface` is used to parse the expression language\n in a form that can be cached and saved for later use. \n If you do not need the expression language in your relations, then this service is not needed.\n\n\nThe `user.rel_provider` service is defined as:\n\n```yaml\nuser.rel_provider:\n    class: UserRelPrvider\n    arguments:\n      - '@jms_serializer.expression_evaluator'\n```\n\nIn this case `jms_serializer.expression_evaluator` is a service implementing `CompilableExpressionEvaluatorInterface`.\n\nInternals\n---------\n\nThis section refers to the Hateoas internals, providing documentation about\nhidden parts of this library. This is not always relevant for end users, but\ninteresting for developers or people interested in learning how things work\nunder the hood.\n\nVersioning\n----------\n\n`willdurand/hateoas` follows [Semantic Versioning](http://semver.org/).\n\n### End Of Life\n\nAs of October 2013, versions `1.x` and `0.x` are officially not supported anymore \n(note that `1.x` was never released).\n\n### Stable Version\n\nVersion `3.x` is the current major stable version.\n\nVersion `2.x` is maintained only for security bug fixes and for major issues that might occur.\n\nContributing\n------------\n\nSee CONTRIBUTING file.\n\n\nRunning the Tests\n-----------------\n\nInstall the [Composer](http://getcomposer.org/) `dev` dependencies:\n\n    php composer.phar install --dev\n\nThen, run the test suite using [PHPUnit](http://phpunit.de/):\n\n    bin/phpunit\n\n\nLicense\n-------\n\nHateoas is released under the MIT License. See the bundled LICENSE file for\ndetails.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilldurand%2FHateoas","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwilldurand%2FHateoas","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilldurand%2FHateoas/lists"}