A PHP library to support implementing representations for HATEOAS REST web services.

Overview

Hateoas

GitHub Actions GitHub Actions Latest Stable Version PHP Version Require

A PHP library to support implementing representations for HATEOAS REST web services.

Installation

The recommended way to install Hateoas is through Composer. Require the willdurand/hateoas package by running the following command:

composer require willdurand/hateoas

This will resolve the latest stable version.

Otherwise, install the library and setup the autoloader yourself.

Working With Symfony

There is a bundle for that! Install the BazingaHateoasBundle, and enjoy!

Usage

Important:

For those who use the 1.0 version, you can jump to this documentation page.

For those who use the 2.0 version, you can jump to this documentation page.

The following documentation has been written for Hateoas 3.0 and above.

Introduction

Hateoas leverages the Serializer library to provide a nice way to build HATEOAS REST web services. HATEOAS stands for Hypermedia as the Engine of Application State, and adds hypermedia links to your representations (i.e. your API responses). HATEOAS is about the discoverability of actions on a resource.

For instance, let's say you have a User API which returns a representation of a single user as follow:

{
    "user": {
        "id": 123,
        "first_name": "John",
        "last_name": "Doe"
    }
}

In order to tell your API consumers how to retrieve the data for this specific user, you have to add your very first link to this representation, let's call it self as it is the URI for this particular user:

{
    "user": {
        "id": 123,
        "first_name": "John",
        "last_name": "Doe",
        "_links": {
            "self": { "href": "http://example.com/api/users/123" }
        }
    }
}

Let's dig into Hateoas now.

Configuring Links

In Hateoas terminology, links are seen as relations added to resources. It is worth mentioning that relations also refer to embedded resources too, but this topic will be covered in the Embedding Resources section.

A link is a relation which is identified by a name (e.g. self) and that has an href parameter:

use JMS\Serializer\Annotation as Serializer;
use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Serializer\XmlRoot("user")
 *
 * @Hateoas\Relation("self", href = "expr('/api/users/' ~ object.getId())")
 */
class User
{
    /** @Serializer\XmlAttribute */
    private $id;
    private $firstName;
    private $lastName;

    public function getId() {}
}

In the example above, we configure a self relation that is a link because of the href parameter. Its value, which may look weird at first glance, will be extensively covered in The Expression Language section. This special value is used to generate a URI.

In this section, annotations are used to configure Hateoas. XML and YAML formats are also supported. If you wish, you can use plain PHP too.

Important: you must configure both the Serializer and Hateoas the same way. E.g. if you use YAML for configuring Serializer, use YAML for configuring Hateoas.

The easiest way to try HATEOAS is with the HateoasBuilder. The builder has numerous methods to configure the Hateoas serializer, but we won't dig into them right now (see The HateoasBuilder). Everything works fine out of the box:

use Hateoas\HateoasBuilder;

$hateoas = HateoasBuilder::create()->build();

$user = new User(42, 'Adrien', 'Brault');
$json = $hateoas->serialize($user, 'json');
$xml  = $hateoas->serialize($user, 'xml');

The $hateoas object is an instance of JMS\Serializer\SerializerInterface, coming from the Serializer library. Hateoas does not come with its own serializer, it hooks into the JMS Serializer.

By default, Hateoas uses the Hypertext Application Language (HAL) for JSON serialization. This specifies the structure of the response (e.g. that "links" should live under a _links key):

{
    "id": 42,
    "first_name": "Adrien",
    "last_name": "Brault",
    "_links": {
        "self": {
            "href": "/api/users/42"
        }
    }
}

For XML, Atom Links are used by default:

<user id="42">
    <first_name><![CDATA[Adrien]]></first_name>
    <last_name><![CDATA[Brault]]></last_name>
    <link rel="self" href="/api/users/42"/>
</user>

It is worth mentioning that these formats are the default ones, not the only available ones. You can use different formats through different serializers, and even add your owns.

Now that you know how to add links, let's see how to add embedded resources.

Embedding Resources

Sometimes, it's more efficient to embed related resources rather than link to them, as it prevents clients from having to make extra requests to fetch those resources.

An embedded resource is a named relation that contains data, represented by the embedded parameter.

use JMS\Serializer\Annotation as Serializer;
use Hateoas\Configuration\Annotation as Hateoas;

/**
 * ...
 *
 * @Hateoas\Relation(
 *     "manager",
 *     href = "expr('/api/users/' ~ object.getManager().getId())",
 *     embedded = "expr(object.getManager())",
 *     exclusion = @Hateoas\Exclusion(excludeIf = "expr(object.getManager() === null)")
 * )
 */
class User
{
    ...

    /** @Serializer\Exclude */
    private $manager;
}

Note: You will need to exclude the manager property from the serialization, otherwise both the serializer and Hateoas will serialize it. You will also have to exclude the manager relation when the manager is null, because otherwise an error will occur when creating the href link (calling getId() on null).

Tip: If the manager property is an object that already has a _self link, you can re-use that value for the href instead of repeating it here. See LinkHelper.

$hateoas = HateoasBuilder::create()->build();

$user = new User(42, 'Adrien', 'Brault', new User(23, 'Will', 'Durand'));
$json = $hateoas->serialize($user, 'json');
$xml  = $hateoas->serialize($user, 'xml');

For json, the HAL representation places these embedded relations inside an _embedded key:

{
    "id": 42,
    "first_name": "Adrien",
    "last_name": "Brault",
    "_links": {
        "self": {
            "href": "/api/users/42"
        },
        "manager": {
            "href": "/api/users/23"
        }
    },
    "_embedded": {
        "manager": {
            "id": 23,
            "first_name": "Will",
            "last_name": "Durand",
            "_links": {
                "self": {
                    "href": "/api/users/23"
                }
            }
        }
    }
}

In XML, serializing embedded relations will create new elements:

<user id="42">
    <first_name><![CDATA[Adrien]]></first_name>
    <last_name><![CDATA[Brault]]></last_name>
    <link rel="self" href="/api/users/42"/>
    <link rel="manager" href="/api/users/23"/>
    <manager rel="manager" id="23">
        <first_name><![CDATA[Will]]></first_name>
        <last_name><![CDATA[Durand]]></last_name>
        <link rel="self" href="/api/users/23"/>
    </manager>
</user>

The tag name of an embedded resource is inferred from the @XmlRoot annotation (xml_root_name in YAML, xml-root-name in XML) coming from the Serializer configuration.

Dealing With Collections

The library provides several classes in the Hateoas\Representation\* namespace to help you with common tasks. These are simple classes configured with the library's annotations.

The PaginatedRepresentation, OffsetRepresentation and CollectionRepresentation classes are probably the most interesting ones. These are helpful when your resource is actually a collection of resources (e.g. /users is a collection of users). These help you represent the collection and add pagination and limits:

use Hateoas\Representation\PaginatedRepresentation;
use Hateoas\Representation\CollectionRepresentation;

$paginatedCollection = new PaginatedRepresentation(
    new CollectionRepresentation(array($user1, $user2, ...)),
    'user_list', // route
    array(), // route parameters
    1,       // page number
    20,      // limit
    4,       // total pages
    'page',  // page route parameter name, optional, defaults to 'page'
    'limit', // limit route parameter name, optional, defaults to 'limit'
    false,   // generate relative URIs, optional, defaults to `false`
    75       // total collection size, optional, defaults to `null`
);

$json = $hateoas->serialize($paginatedCollection, 'json');
$xml  = $hateoas->serialize($paginatedCollection, 'xml');

The CollectionRepresentation offers a basic representation of an embedded collection.

The PaginatedRepresentation is designed to add self, first, and when possible last, next, and previous links.

The OffsetRepresentation works just like PaginatedRepresentation but is useful when pagination is expressed by offset, limit and total.

The RouteAwareRepresentation adds a self relation based on a given route.

You can generate absolute URIs by setting the absolute parameter to true in both the PaginatedRepresentation and the RouteAwareRepresentation.

The Hateoas library also provides a PagerfantaFactory to easily build PaginatedRepresentation from a Pagerfanta instance. If you use the Pagerfanta library, this is an easier way to create the collection representations:

use Hateoas\Configuration\Route;
use Hateoas\Representation\Factory\PagerfantaFactory;

$pagerfantaFactory   = new PagerfantaFactory(); // you can pass the page,
                                                // and limit parameters name
$paginatedCollection = $pagerfantaFactory->createRepresentation(
    $pager,
    new Route('user_list', array())
);

$json = $hateoas->serialize($paginatedCollection, 'json');
$xml  = $hateoas->serialize($paginatedCollection, 'xml');

You would get the following JSON content:

{
    "page": 1,
    "limit": 10,
    "pages": 1,
    "_links": {
        "self": {
            "href": "/api/users?page=1&limit=10"
        },
        "first": {
            "href": "/api/users?page=1&limit=10"
        },
        "last": {
            "href": "/api/users?page=1&limit=10"
        }
    },
    "_embedded": {
        "items": [
            { "id": 123 },
            { "id": 456 }
        ]
    }
}

And the following XML content:

<?xml version="1.0" encoding="UTF-8"?>
<collection page="1" limit="10" pages="1">
    <entry id="123"></entry>
    <entry id="456"></entry>
    <link rel="self" href="/api/users?page=1&amp;limit=10" />
    <link rel="first" href="/api/users?page=1&amp;limit=10" />
    <link rel="last" href="/api/users?page=1&amp;limit=10" />
</collection>

If you want to customize the inlined CollectionRepresentation, pass one as third argument of the createRepresentation() method:

use Hateoas\Representation\Factory\PagerfantaFactory;

$pagerfantaFactory   = new PagerfantaFactory(); // you can pass the page and limit parameters name
$paginatedCollection = $pagerfantaFactory->createRepresentation(
    $pager,
    new Route('user_list', array()),
    new CollectionRepresentation($pager->getCurrentPageResults())
);

$json = $hateoas->serialize($paginatedCollection, 'json');
$xml  = $hateoas->serialize($paginatedCollection, 'xml');

If you want to change the xml root name of the collection, create a new class with the xml root configured and use the inline mechanism:

use JMS\Serializer\Annotation as Serializer;

/**
 * @Serializer\XmlRoot("users")
 */
class UsersRepresentation
{
    /**
     * @Serializer\Inline
     */
    private $inline;

    public function __construct($inline)
    {
        $this->inline = $inline;
    }
}

$paginatedCollection = ...;
$paginatedCollection = new UsersRepresentation($paginatedCollection);

Representations

As mentionned in the previous section, representations are classes configured with the library's annotations in order to help you with common tasks. The collection representations are described in Dealing With Collection.

VndErrorRepresentation

The VndErrorRepresentation allows you to describe an error response following the vnd.error specification.

$error = new VndErrorRepresentation(
    'Validation failed',
    42,
    'http://.../',
    'http://.../'
);

Serializing such a representation in XML and JSON would give you the following outputs:

<?xml version="1.0" encoding="UTF-8"?>
    <resource logref="42">
    <message><![CDATA[Validation failed]]></message>
    <link rel="help" href="http://.../"/>
    <link rel="describes" href="http://.../"/>
</resource>
{
    "message": "Validation failed",
    "logref": 42,
    "_links": {
        "help": {
            "href": "http://.../"
        },
        "describes": {
            "href": "http://.../"
        }
    }
}

Hint: it is recommended to create your own error classes that extend the VndErrorRepresentation class.

The Expression Language

Hateoas relies on the powerful Symfony ExpressionLanguage component to retrieve values such as links, ids or objects to embed.

Each time you fill in a value (e.g. a Relation href in annotations or YAML), you can either pass a hardcoded value or an expression. In order to use the Expression Language, you have to use the expr() notation:

/**
 * @Hateoas\Relation("self", href = "expr('/api/users/' ~ object.getId())")
 */

You can learn more about the Expression Syntax by reading the official documentation: The Expression Syntax.

Context

Natively, a special variable named object is available in each expression, and represents the current object:

expr(object.getId())

We call such a variable a context variable.

You can add your own context variables to the Expression Language context by adding them to the expression evaluator.

Adding Your Own Context Variables

Using the HateoasBuilder, call the setExpressionContextVariable() method to add new context variables:

use Hateoas\HateoasBuilder;

$hateoas = HateoasBuilder::create()
    ->setExpressionContextVariable('foo', new Foo())
    ->build();

The foo variable is now available:

expr(foo !== null)
Expression Functions

For more info on how to add functions to the expression language, please refer to https://symfony.com/doc/current/components/expression_language/extending.html

URL Generators

Since you can use the Expression Language to define the relations links (href key), you can do a lot by default. However if you are using a framework, chances are that you will want to use routes to build links.

You will first need to configure an UrlGenerator on the builder. You can either implement the Hateoas\UrlGenerator\UrlGeneratorInterface, or use the Hateoas\UrlGenerator\CallableUrlGenerator:

use Hateoas\UrlGenerator\CallableUrlGenerator;

$hateoas = HateoasBuilder::create()
    ->setUrlGenerator(
        null, // By default all links uses the generator configured with the null name
        new CallableUrlGenerator(function ($route, array $parameters, $absolute) use ($myFramework) {
            return $myFramework->generateTheUrl($route, $parameters, $absolute);
        })
    )
    ->build()
;

You will then be able to use the @Route annotation:

use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Hateoas\Relation(
 *      "self",
 *      href = @Hateoas\Route(
 *          "user_get",
 *          parameters = {
 *              "id" = "expr(object.getId())"
 *          }
 *      )
 * )
 */
class User
{
    "id": 42,
    "first_name": "Adrien",
    "last_name": "Brault",
    "_links": {
        "self": {
            "href": "/api/users/42"
        }
    }
}

Note that the library comes with a SymfonyUrlGenerator. For example, to use it in Silex:

use Hateoas\UrlGenerator\SymfonyUrlGenerator;

$hateoas = HateoasBuilder::create()
    ->setUrlGenerator(null, new SymfonyUrlGenerator($app['url_generator']))
    ->build()
;

Helpers

Hateoas provides a set of helpers to ease the process of building APIs.

LinkHelper

The LinkHelper class provides a getLinkHref($object, $rel, $absolute = false) method that allows you to get the href value of any object, for any given relation name. It is able to generate a URI (either absolute or relative) from any link relation:

$user = new User(123, 'William', 'Durand');

$linkHelper->getLinkHref($user, 'self');
// /api/users/123

$linkHelper->getLinkHref($user, 'self', true);
// http://example.com/api/users/123
The link Function

The feature above is also available in your expressions (cf. The Expression Language) through the link(object, rel, absolute) function:

/**
 * @Hateoas\Relation(
 *     "self",
 *     href = @Hateoas\Route("post_get", parameters = {"id" = "expr(object.getId())"})
 * )
 */
class Post {}

/**
 * @Hateoas\Relation(
 *     "self",
 *     href = @Hateoas\Route("user_get", parameters = {"id" = "expr(object.getId())"})
 * )
 * @Hateoas\Relation(
 *     "post",
 *     href = "expr(link(object.getPost(), 'self', true))"
 * )
 * @Hateoas\Relation(
 *     "relative",
 *     href = "expr(link(object.getRelativePost(), 'self'))"
 * )
 */
class User
{
    ...

    public function getPost()
    {
        return new Post(456);
    }

    public function getRelativePost()
    {
        return new Post(789);
    }
}

Pay attention to the href expressions for the post and relative relations, as well as their corresponding values in the following JSON content:

{
    "user": {
        "id": 123,
        "first_name": "William",
        "last_name": "Durand",
        "_links": {
            "self": { "href": "http://example.com/api/users/123" },
            "post": { "href": "http://example.com/api/posts/456" },
            "relative": { "href": "/api/posts/789" }
        }
    }
}

It is worth mentioning that you can force whether you want an absolute or relative URI by using the third argument in both the getLinkHref() method and the link function.

Important: by default, all URIs will be relative, even those which are defined as absolute in their configuration.

$linkHelper->getLinkHref($user, 'post');
// /api/posts/456

$linkHelper->getLinkHref($user, 'post', true);
// http://example.com/api/posts/456

$linkHelper->getLinkHref($user, 'relative');
// /api/posts/789

$linkHelper->getLinkHref($user, 'relative', true);
// http://example.com/api/posts/789

Twig Extensions

Hateoas also provides a set of Twig extensions.

LinkExtension

The LinkExtension allows you to use the LinkHelper into your Twig templates, so that you can generate links in your HTML templates for instance.

This extension exposes the getLinkHref() helper's method through the link_href Twig function:

{{ link_href(user, 'self') }}
{# will generate: /users/123 #}

{{ link_href(will, 'self', false) }}
{# will generate: /users/123 #}

{{ link_href(will, 'self', true) }}
{# will generate: http://example.com/users/123 #}

Serializers & Formats

Hateoas provides a set of serializers. Each serializer allows you to generate either XML or JSON content following a specific format, such as HAL, or Atom Links for instance.

The JsonHalSerializer

The JsonHalSerializer allows you to generate HAL compliant relations in JSON. It is the default JSON serializer in Hateoas.

HAL provides its linking capability with a convention which says that a resource object has a reserved property called _links. This property is an object that contains links. These links are key'ed by their link relation.

HAL also describes another convention which says that a resource may have another reserved property named _embedded. This property is similar to _links in that embedded resources are key'ed by relation name. The main difference is that rather than being links, the values are resource objects.

{
    "message": "Hello, World!",
    "_links": {
        "self": {
            "href": "/notes/0"
        }
    },
    "_embedded": {
        "associated_events": [
            {
                "name": "SymfonyCon",
                "date": "2013-12-12T00:00:00+0100"
            }
        ]
    }
}

The XmlSerializer

The XmlSerializer allows you to generate Atom Links into your XML documents. It is the default XML serializer.

<?xml version="1.0" encoding="UTF-8"?>
<note>
    <message><![CDATA[Hello, World!]]></message>
    <link rel="self" href="/notes/0" />
    <events rel="associated_events">
        <event>
            <name><![CDATA[SymfonyCon]]></name>
            <date><![CDATA[2013-12-12T00:00:00+0100]]></date>
        </event>
    </events>
</note>

The XmlHalSerializer

The XmlHalSerializer allows you to generate HAL compliant relations in XML.

HAL in XML is similar to HAL in JSON in the sense that it describes link tags and resource tags.

Note: the self relation will actually become an attribute of the main resource instead of being a link tag. Other links will be generated as link tags.

<?xml version="1.0" encoding="UTF-8"?>
<note href="/notes/0">
    <message><![CDATA[Hello, World!]]></message>

    <resource rel="associated_events">
        <name><![CDATA[SymfonyCon]]></name>
        <date><![CDATA[2013-12-12T00:00:00+0100]]></date>
    </resource>
</note>

Adding New Serializers

You must implement the SerializerInterface that describes two methods to serialize links and embedded relations.

The HateoasBuilder

The HateoasBuilder class is used to easily configure Hateoas thanks to a powerful and fluent API.

use Hateoas\HateoasBuilder;

$hateoas = HateoasBuilder::create()
    ->setCacheDir('/path/to/cache/dir')
    ->setDebug($trueOrFalse)
    ->setDefaultXmlSerializer()
    ...
    ->build();

All the methods below return the current builder, so that you can chain them.

XML Serializer

  • setXmlSerializer(SerializerInterface $xmlSerializer): sets the XML serializer to use. Default is: XmlSerializer;
  • setDefaultXmlSerializer(): sets the default XML serializer (XmlSerializer).

JSON Serializer

  • setJsonSerializer(SerializerInterface $jsonSerializer): sets the JSON serializer to use. Default is: JsonHalSerializer;
  • setDefaultJsonSerializer(): sets the default JSON serializer (JsonHalSerializer).

URL Generator

  • setUrlGenerator($name = null, UrlGeneratorInterface $urlGenerator): adds a new named URL generator. If $name is null, the URL generator will be the default one.

Expression Evaluator/Expression Language

  • setExpressionContextVariable($name, $value): adds a new expression context variable;
  • setExpressionLanguage(ExpressionLanguage $expressionLanguage);

(JMS) Serializer Specific

  • includeInterfaceMetadata($include): whether to include the metadata from the interfaces;
  • setMetadataDirs(array $namespacePrefixToDirMap): sets a map of namespace prefixes to directories. This method overrides any previously defined directories;
  • addMetadataDir($dir, $namespacePrefix = ''): adds a directory where the serializer will look for class metadata;
  • addMetadataDirs(array $namespacePrefixToDirMap): adds a map of namespace prefixes to directories;
  • replaceMetadataDir($dir, $namespacePrefix = ''): similar to addMetadataDir(), but overrides an existing entry.

Please read the official Serializer documentation for more details.

Others

  • setDebug($debug): enables or disables the debug mode;
  • setCacheDir($dir): sets the cache directory.

Configuring a Cache Directory

Both the serializer and the Hateoas libraries collect metadata about your objects from various sources such as YML, XML, or annotations. In order to make this process as efficient as possible, it is recommended that you allow the Hateoas library to cache this information. To do that, configure a cache directory:

$builder = \Hateoas\HateoasBuilder::create();

$hateoas = $builder
    ->setCacheDir($someWritableDir)
    ->build();

Configuring Metadata Locations

Hateoas supports several metadata sources. By default, it uses Doctrine annotations, but you may also store metadata in XML, or YAML files. For the latter, it is necessary to configure a metadata directory where those files are located:

$hateoas = \Hateoas\HateoasBuilder::create()
    ->addMetadataDir($someDir)
    ->build();

Hateoas would expect the metadata files to be named like the fully qualified class names where all \ are replaced with .. If you class would be named Vendor\Package\Foo the metadata file would need to be located at $someDir/Vendor.Package.Foo.(xml|yml).

Extending The Library

Hateoas allows frameworks to dynamically add relations to classes by providing an extension point at configuration level. This feature can be useful for those who want to to create a new layer on top of Hateoas, or to add "global" relations rather than copying the same configuration on each class.

In order to leverage this mechanism, the ConfigurationExtensionInterface interface has to be implemented:

use Hateoas\Configuration\Metadata\ConfigurationExtensionInterface;
use Hateoas\Configuration\Metadata\ClassMetadataInterface;
use Hateoas\Configuration\Relation;

class AcmeFooConfigurationExtension implements ConfigurationExtensionInterface
{
    /**
     * {@inheritDoc}
     */
    public function decorate(ClassMetadataInterface $classMetadata): void
    {
        if (0 === strpos('Acme\Foo\Model', $classMetadata->getName())) {
            // Add a "root" relation to all classes in the `Acme\Foo\Model` namespace
            $classMetadata->addRelation(
                new Relation(
                    'root',
                    '/'
                )
            );
        }
    }
}

You can access the existing relations loaded from Annotations, XML, or YAML with $classMetadata->getRelations().

If the $classMetadata has relations, or if you add relations to it, its relations will be cached. So if you read configuration files (Annotations, XML, or YAML), make sure to reference them on the class metadata:

$classMetadata->fileResources[] = $file;

Reference

XML

<?xml version="1.0" encoding="UTF-8"?>
<serializer>
<class name="Acme\Demo\Representation\User" h:providers="Class::getRelations expr(sevice('foo').getMyAdditionalRelations())" xmlns:h="https://github.com/willdurand/Hateoas">
        <h:relation rel="self">
            <h:href uri="http://acme.com/foo/1" />
        </h:relation>
        <h:relation rel="friends">
            <h:href route="user_friends" generator="my_custom_generator">
                <h:parameter name="id" value="expr(object.getId())" />
                <h:parameter name="page" value="1" />
            </h:ref>
            <h:embedded xml-element-name="users">
                <h:content>expr(object.getFriends())</h:content>
                <h:exclusion ... />
            </h:embedded>
            <h:exclusion groups="Default, user_full" since-version="1.0" until-version="2.2" exclude-if="expr(object.getFriends() === null)" />
        </h:relation>
    </class>
</serializer>

See the hateoas.xsd file for more details.

YAML

Acme\Demo\Representation\User:
    relations:
        -
            rel: self
            href: http://acme.com/foo/1
        -
            rel: friends
            href:
                route: user_friends
                parameters:
                    id: expr(object.getId())
                    page: 1
                generator: my_custom_generator
                absolute: false
            embedded:
                content: expr(object.getFriends())
                xmlElementName: users
                exclusion: ...
            exclusion:
                groups: [Default, user_full]
                since_version: 1.0
                until_version: 2.2
                exclude_if: expr(object.getFriends() === null)

    relation_providers: [ "Class::getRelations", "expr(sevice('foo').getMyAdditionalRelations())" ]

Annotations

@Relation

This annotation can be defined on a class.

use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Hateoas\Relation(
 *     name = "self",
 *     href = "http://hello",
 *     embedded = "expr(object.getHello())",
 *     attributes = { "foo" = "bar" },
 *     exclusion = ...,
 * )
 */
Property Required Content Expression language
name Yes string No
href If embedded is not set string / @Route Yes
embedded If href is not set string / @Embedded Yes
attributes No array Yes on values
exclusion No @Exclusion N/A

Important: attributes are only used on link relations (i.e. combined with the href property, not with the embedded one).

@Route

use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Hateoas\Relation(
 *     name = "self",
 *     href = @Hateoas\Route(
 *         "user_get",
 *         parameters = { "id" = "expr(object.getId())" },
 *         absolute = true,
 *         generator = "my_custom_generator"
 *     )
 * )
 */

This annotation can be defined in the href property of the @Relation annotation. This is allows you to your URL generator, if you have configured one.

Property Required Content Expression language
name Yes string No
parameters Defaults to array() array / string Yes (string + array values)
absolute Defaults to false boolean / string Yes
generator No string / null No

@Embedded

use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Hateoas\Relation(
 *     name = "friends",
 *     embedded = @Hateoas\Embedded(
 *         "expr(object.getFriends())",
 *         exclusion = ...,
 *         xmlElementName = "users"
 *     )
 * )
 */

This annotation can be defined in the embedded property of the @Relation annotation. It is useful if you need configure the exclusion or xmlElementName options for the embedded resource.

Property Required Content Expression language
content Yes string / array Yes (string)
exclusion Defaults to array() @Exclusion N/A
xmlElementName Defaults to array() string No

@Exclusion

This annotation can be defined in the exclusion property of both the @Relation and @Embedded annotations.

Property Required Content Expression language
groups No array No
sinceVersion No string No
untilVersion No string No
maxDepth No integer No
excludeIf No string / boolean Yes

All values except excludeIf act the same way as when they are used directly on the regular properties with the serializer.

excludeIf expects a boolean and is helpful when another expression would fail under some circumstances. In this example, if the getManager method is null, you should exclude it to prevent the URL generation from failing:

/**
 * @Hateoas\Relation(
 *     "manager",
 *     href = @Hateoas\Route(
 *         "user_get",
 *         parameters = { "id" = "expr(object.getManager().getId())" }
 *     ),
 *     exclusion = @Hateoas\Exclusion(excludeIf = "expr(null === object.getManager())")
 * )
 */
class User
{
    public function getId() {}

    /**
     * @return User|null
     */
    public function getManager() {}
}

@RelationProvider

This annotation can be defined on a class. It is useful if you wish to serialize multiple-relations(links). As an example:

{
  "_links": {
    "relation_name": [
      {"href": "link1"},
      {"href": "link2"},
      {"href": "link3"}
    ]
  }
}
Property Required Content Expression language
name Yes string Yes

It can be "name":

  • A function: my_func
  • A static method: MyClass::getExtraRelations
  • An expression: expr(service('user.rel_provider').getExtraRelations())

Here and example using the expression language:

use Hateoas\Configuration\Annotation as Hateoas;

/**
 * @Hateoas\RelationProvider("expr(service('user.rel_provider').getExtraRelations())")
 */
class User
{
    ...
}

Here the UserRelPrvider class:

use Hateoas\Configuration\Relation;
use Hateoas\Configuration\Route;

class UserRelPrvider
{
    private $evaluator;
    
    public function __construct(CompilableExpressionEvaluatorInterface $evaluator)
    {
        $this->evaluator = $evaluator;
    }

    /**
     * @return Relation[]
     */
    public function getExtraRelations(): array
    {
        // You need to return the relations
        return array(
            new Relation(
                'self',
                new Route(
                    'foo_get',
                    ['id' => $this->evaluator->parse('object.getId()', ['object'])]
                )
            )
        );
    }
}

$this->evaluator implementing CompilableExpressionEvaluatorInterface is used to parse the expression language in a form that can be cached and saved for later use. If you do not need the expression language in your relations, then this service is not needed.

The user.rel_provider service is defined as:

user.rel_provider:
    class: UserRelPrvider
    arguments:
      - '@jms_serializer.expression_evaluator'

In this case jms_serializer.expression_evaluator is a service implementing CompilableExpressionEvaluatorInterface.

Internals

This section refers to the Hateoas internals, providing documentation about hidden parts of this library. This is not always relevant for end users, but interesting for developers or people interested in learning how things work under the hood.

Versioning

willdurand/hateoas follows Semantic Versioning.

End Of Life

As of October 2013, versions 1.x and 0.x are officially not supported anymore (note that 1.x was never released).

Stable Version

Version 3.x is the current major stable version.

Version 2.x is maintained only for security bug fixes and for major issues that might occur.

Contributing

See CONTRIBUTING file.

Running the Tests

Install the Composer dev dependencies:

php composer.phar install --dev

Then, run the test suite using PHPUnit:

bin/phpunit

License

Hateoas is released under the MIT License. See the bundled LICENSE file for details.

Comments
  • RelationProvider error while using an expression

    RelationProvider error while using an expression

    When trying to migrate our code from the 2.12 version to the current 3.0, we don't seem to be able to use the RelationProvider.

    Adding the following annotation to an entity class @Hateoas\RelationProvider("expr(service('app.hateoas.relprovider').getExtraRelations())")

    produces this error:

    The function "service" does not exist around position 1 for expression service('app.hateoas.relprovider').getEntityRelations().

    Any help would be very much appreciated.

    opened by sorix6 26
  • Use the symfony ExpressionLanguage component instead of handler system

    Use the symfony ExpressionLanguage component instead of handler system

    Basically have the same expressions as in the JMSSecurityExtraBundle. See https://github.com/Kitano/php-expression

    We can add an unlimited number of variables using ExpressionEvaluator::setContextValue. The bundle could add tagged services, the request/container etc, global variables (like in twig, defined in the bundle config).

    Example having global variables and the request ( ping @lsmith77 ):

    # app/config/config.yml
    
    bazinga_hateoas:
        expressions:
            variables:
                BASE_REL: https://rel.site.com
    

    And then use it for the relations rels:

    Acme\DemoBundle\Entity\User:
        relations:
            -
                rel: eval BASE_REL ~ '/friends'
                href:
                    route: user_friends
                    parameters:
                        id: eval object.getId()
                        _format: eval request.getRequestFormat() }
    

    This definitely feels way better than the previous handler system. The only "annoying" thing is that we have to use eval, though it's what the JMSSecurityExtraBundle does (must be fine, right ? :P).

    WDYT ?

    enhancement 
    opened by adrienbrault 26
  • MaxDepth exclusion strategy

    MaxDepth exclusion strategy

    It seem's that MaxDepth exclusion strategy does not work. At the moment in jms-serializer it can only be tested if metadata is an instance of PropertyMetadata

    https://github.com/schmittjoh/serializer/blob/master/src/JMS/Serializer/Exclusion/DepthExclusionStrategy.php#L55

    But Exclusion can only be defined in class annotation.

    bug 
    opened by yoye 22
  • Use UrlGeneratorInterface constants for UrlGenerator::generate

    Use UrlGeneratorInterface constants for UrlGenerator::generate

    Don't use hardcoded values for the $referenceType argument of the Symfony\Component\Routing\Generator\UrlGenerator::generate

    for full absolute path use Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_URL for relative path (with absolute url) use Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_PATH

    opened by ahilles107 18
  • Add OffsetRepresentation

    Add OffsetRepresentation

    PaginationRepresentation is very handy, but is not reusable when the API use a offset/limit/count collection definition.

    I've created OffsetRepresentation to fill this common use case. There are some similarities to PaginationRepresentation but subclassing/abstracting in this case seems overkill to me.

    The name can be improved though... OffsetLimitRepresentation? OffsetPaginationRepresentation?

    opened by giosh94mhz 18
  • Extending Collection relations

    Extending Collection relations

    I've added the following features into the Collection Added exclusions to the collection relation so the collection works with groups. Also added ability to specify a callback function which returns the collection relation

    I'm all ears if somebody has a better suggestion on how to make Collection better out of the box without requiring everyone to extend it to do basic stuff.

    enhancement 
    opened by mvrhov 18
  • [RFC] Allow to change the xml root name of a collection

    [RFC] Allow to change the xml root name of a collection

    Actually, I got that:

    <?xml version="1.0" encoding="UTF-8"?>
    <collection page="1" limit="10" pages="1">
      <entry rel="items">
        <project/>
        <project/>
        <project/>
        <project/>
      </entry>
      <link rel="self" href="/app_dev.php/api/projects?page=1&amp;limit=10"/>
      <link rel="first" href="/app_dev.php/api/projects?page=1&amp;limit=10"/>
      <link rel="last" href="/app_dev.php/api/projects?page=1&amp;limit=10"/>
    </collection>
    

    And I want to rename the collection to projects.

    (And I also want to remove the entry, but I think I need to read more the doc)

    enhancement 
    opened by lyrixx 17
  • Link display conditions

    Link display conditions

    Currently the factory definition can configure the output with pre-specificed link definitions. I was wondering if it makes sense to have a display condition on the definition so that in situations where the link should not be shown it can be skipped. The case in mind is where the 'next' relations is the same as the 'self'. Since we can specify a 'last' relation we can put some logic in the client to compute the need to look up the 'next' relation. However it would be simpler i think if the 'next' relation simply didn't appear in the output, and vice versa the 'previous' relation.

    Another example may be where a resource has a sub collection referenced by a link. If that collection is known to be empty is there any need to show the link?

    question 
    opened by MrHash 17
  • Embedded resources

    Embedded resources

    Does this library support embedding of resources?

    I'd like to eventually contribute to this library.

    I created my own a while ago, but I like where you're headed!.. Before I refractor our API to this library, embedding resources is a must. Do you see this being a part of this library?

    https://github.com/j/hal

    enhancement 
    opened by j 16
  • Getting current object in relation provider.

    Getting current object in relation provider.

    In previous version relation providers receives current object as the first argument. After updating to the latest version I can't find a way to get current object. When I try to pass it with object variable like this:

    My\DTO:
        # ... some config ...
        relation_providers: [ "expr(service('my_dto_relation_provider').addRelations(object))" ]
    

    then the following error appears:

    Variable "object" is not valid around position 72 for expression service('my_dto_relation_provider').addRelations(object).

    How can I get current object inside service? I need it because I dynamically add some relations based on current object and authenticated user. Thanks.

    question 
    opened by Invis1ble 15
  • [WIP] Add JsonApiSerializer

    [WIP] Add JsonApiSerializer

    URL based, see: http://jsonapi.org/format/#url-based-json-api

    Limitations

    This serializer works but it requires a few conditions:

    1. The serializer MUST take an array to serialize, not just a resource, because of the JSON API specification:
    $user = new User(123);
    
    // we can't just pass `$user` here
    $hateoas->serialize(array('users' => $user), 'json');
    
    1. href values for link relations MUST be identifiers (still because of the JSON API spec):
    /**
     * @Hateoas\Relation("posts", href = "expr(object.getPostIds())")
     */
    class User
    {
        public function getPostIds()
        {
            return [ 4, 5, 10 ];
        }
    }
    

    The output would be:

    {
        "users": [{
            "id": 123,
            "links": {
                "posts": [ 4, 5, 10 ]
            }
        }]
    }
    
    1. In order to define top level links, you MUST set a topLevel attribute:
    /**
     * @Hateoas\Relation(
     *     "posts",
     *     href = "http://example.com/{id}/posts",
     *     attributes = { "topLevel" = true, "type" = "posts" }
     * )
     *
     * ...
     */
    class User
    {
        // ...
    }
    

    The output would be:

    {
        "links": {
            "posts": {
                "href": "http://example.com/{id}/posts",
                "type": "posts"
            }
        },
        "users": {
            "id": 123,
            "links": {
                "posts": [
                    4,
                    5,
                    10
                ]
            }
        }
    }
    

    I guess these requirements are acceptable.

    WDYT?

    enhancement 
    opened by willdurand 14
  • how to use @RelationProvider

    how to use @RelationProvider

    Hello,

    I don't understand how to use the annotation @RelationProvider. The current object is never available on all methods (exp(), object::static() or func() )

    How to get the current object to iterate on elements from his method ?

    example :

    namespace App\Entity;
    
    /*
    * @Hateoas\Relation( ??? ... object.getOtherObjects() ... ??? )
    */
    class MyObject
    {
    
        /**
         * @ORM\ManyToMany(targetEntity=MyOtherObject::class, inversedBy="myObjects")
         */
        private $otherObject;
    
    ...
        public function getOtherObjects()
        {
            return $this->otherObject;
        }
    ...
    
    }
    
    
    class MyObject
    {
    
    ...
         public function getId()
        {
            return $this->id;
        }
    ...
    }
    

    To generate ...

    {
      "_links": {
        "otherObjects": [
          {"href": "/api/otherObject/1"},
          {"href": "/api/otherObject/6"},
          {"href": "/api/otherObject/78"}
        ]
      }
    }
    

    thank's you

    opened by TOTOleHero 1
  • Allow multiple lines in excludeIf expression annotation

    Allow multiple lines in excludeIf expression annotation

    I have some issues when I use the excludeIf option with a large expression in the annotation. I want to split it to see the entire expression without scrolling horizontally.

    Example

    /**
     * @Hateoas\Relation(
     *     name="foo"
     *     embedded=@Hateoas\Embedded(
     *         "expr(object.getA().getB().getC().getD()",
     *         exclusion=@Hateoas\Exclusion(
     *             groups={"public"},
     *             excludeIf="expr(not object.getA()
       or not object.getA().getB()
       or not object.getA().getB().getC())"
     *         )
     *     )
     * )
     */
    class Group
    

    The CheckExpressionTrait::checkExpression method gets the expression like this expr(not object.getA()\n or not object.getA().getB()\n or not object.getA().getB().getC()).

    The Symfony Expression Language component will replace all the \n with whitespaces, but the regular expression is not taking the entire expression.

    https://github.com/willdurand/Hateoas/blob/master/src/Configuration/Metadata/Driver/CheckExpressionTrait.php#L25

    if (is_string($exp) && preg_match('/expr\((?P<expression>.+)\)/', $exp, $matches)) {
    

    So the matching in the expression group is not object.getA(

    I am not sure if the regular expression can be updated to use "single line" flag, /expr\((?P<expression>.+)\)/s, so dots matches newline.

    Thanks

    feature-request 
    opened by ixarlie 0
  • Allow for clean extension of factories

    Allow for clean extension of factories

    We needed to re-add runtime embeddeds and links again. In order to do so, we override the EmbeddedsFactory and LinksFactory with class extending each. That’s a bit unclean and a cleaner way would be to decorate the original classes. To enable that, one would need to introduce two interface Hateoas\Factory\EmbeddedsFactoryInterface and Hateoas\Factory\LinksFactoryInterface:

    interface EmbeddedsFactoryInterface
    {
        /** @return Embedded[] */
        public function create(object $object, SerializationContext $context): array;
    }
    
    interface LinksFactoryInterface
    {
        /** @return Link[] */
        public function create(object $object, SerializationContext $context): array;
    }
    

    Then change the AddRelationsListener to use these new interfaces instead of the concrete classes. Would you be interested in this change?

    opened by lstrojny 4
  • getPages also nullable

    getPages also nullable

    Hello, in constructor of PaginatedReprensation you make the param for pages optional, but the variable is declared to int. So you also have to make this nullable.

    opened by wolfgang-dev 0
  • Allow a relation to have an array of values

    Allow a relation to have an array of values

    A relation link can contain multiple links.

    See example case as in https://github.com/willdurand/Hateoas/issues/290#issuecomment-491450460

    I think is a bad idea to give to relation providers access to object, probably can be avoided by still creating only one relation, but the allowing multiple links in it.

    feature-request 
    opened by goetas 1
Releases(3.8.0)
  • 3.8.0(Dec 18, 2021)

    What's Changed

    • Allow symfony 6 and use github actions for CI by @W0rma in https://github.com/willdurand/Hateoas/pull/318
    • Update Pagerfanta to test with supported versions by @mbabker in https://github.com/willdurand/Hateoas/pull/316
    • Fix branch name in workflow by @W0rma in https://github.com/willdurand/Hateoas/pull/319
    • Fix PHP 8 test pipelines by @W0rma in https://github.com/willdurand/Hateoas/pull/320
    • Allow doctrine/annotations > 1.13 by @W0rma in https://github.com/willdurand/Hateoas/pull/321
    • Use github action to check coding standards by @W0rma in https://github.com/willdurand/Hateoas/pull/322
    • Fix badges by @W0rma in https://github.com/willdurand/Hateoas/pull/323
    • Fix Deprecated Notice for PHP8 by @rogerb87 in https://github.com/willdurand/Hateoas/pull/324

    New Contributors

    • @mbabker made their first contribution in https://github.com/willdurand/Hateoas/pull/316
    • @rogerb87 made their first contribution in https://github.com/willdurand/Hateoas/pull/324

    Full Changelog: https://github.com/willdurand/Hateoas/compare/3.7.0...3.8.0

    Source code(tar.gz)
    Source code(zip)
  • 3.7.0(Jan 16, 2021)

  • 3.6.0(Feb 22, 2020)

  • 3.5.0(Dec 6, 2019)

  • 3.4.0(Nov 29, 2019)

  • 3.3.0(Nov 29, 2019)

  • 3.2.0(Sep 9, 2019)

  • 3.1.0(Apr 23, 2019)

  • 3.0.0(Jan 26, 2019)

    Improvement are:

    • compatibility with jms/serializer v2.0
    • PHP 7.2 is the minimum version
    • Adopted PSR-4
    • fully cached relation metadata
    • fully cached expression language expressions
    • removed deprecated functionalities
    • fixed some long-standing bugs
    • adopted doctrine codestyle 5.0

    A detailed list of closed issues is here

    Information on how to upgrade are in the UPGRADING document

    In parallel has been released willdurand/BazingaHateoasBundle 2.0.0 that integrates this library into Symfony.

    Source code(tar.gz)
    Source code(zip)
  • 3.0.0-RC1(Jan 11, 2019)

    This is the first release candidate for willdurand/Hateoas v3.0 that brings major improvement as:

    • compatibility with jms/serializer v2.0
    • PHP 7.2 is the minimum version
    • fully cached relation metadata
    • fully cached expression language expressions
    • removed deprecated functionalities
    • fixed some long-standing bugs
    • adopted doctrine codestyle 5.0

    A detailed list of closed issues is here

    Information on how to upgrade are in the UPGRADING document

    In parallel has been relreased willdurand/BazingaHateoasBundle 2.0.0-RC1 that integrates this library into Symfony.

    A stable release is expected in a couple of weeks.

    Source code(tar.gz)
    Source code(zip)
  • 2.12.0(Feb 23, 2018)

  • 2.11.0(May 22, 2017)

    • Added: JMS serializer had a BC break in 1.6.1 and 1.6.2 that has been reverted in 1.7.0
    • Fixed: support all (non EOL) Symfony versions / improve tests
    • Fixed: MaxDepth issues when used in embedded collections

    Thanks to all contributors, especiall Asmir!

    Source code(tar.gz)
    Source code(zip)
  • 2.10.0(May 19, 2016)

  • 2.9.1(Feb 25, 2016)

    • Fixed: Use UrlGeneratorInterface constants for UrlGenerator::generate For relative path (with absolute url) use Symfony\Component\Routing\Generator\UrlGeneratorInterface::ABSOLUTE_PATH
    Source code(tar.gz)
    Source code(zip)
  • v2.9.0(Dec 7, 2015)

  • v2.8.1(Dec 4, 2015)

  • v2.8.0(Sep 9, 2015)

  • v2.7.0(Jul 4, 2015)

  • v2.6.0(May 28, 2015)

    • Added: OffsetRepresentation and AbstractSegmentedRepresentation
    • Added: JMS annotation Type on PaginatedRepresentation
    • Reverted: "Set a default exclusion configuration to Embedded" (fix #196)
    Source code(tar.gz)
    Source code(zip)
  • v2.5.0(Apr 13, 2015)

    • Fixed: set a default exclusion configuration to Embedded
    • Added: support for multiple embedded resources with the same relation name, fixed #188
    Source code(tar.gz)
    Source code(zip)
  • v2.4.0(Mar 6, 2015)

    • Added: cache for Expression Language evaluations
    • Added: introduce RelationProviderInterface
    • Fixed: ExpressionEvaluator::evaluate with non strings, fixes #183
    • Fixed: documentation (YAML ref. config, JSON snippets)
    • Fixed: checking on existence of class property before accessing it to avoid notice.
    • Fixed: #164
    Source code(tar.gz)
    Source code(zip)
  • v2.3.0(Jun 3, 2014)

    Code

    • Added: total attribute to PaginatedRepresentation
    • Updated: use fixed Frankenstein 0.1.2

    Documentation

    • Added: documentation and tests on how to override a collection xml root name
    • Fixed: missing reference in YAML reference
    Source code(tar.gz)
    Source code(zip)
  • v2.2.0(Mar 26, 2014)

    • Added: getters to VndErrorRepresentation
    • Added: getters to PagerFantaFactory
    • Fixed: PHPDoc for HateoasBuilder:create() function
    • Removed: the Hateoas::getLinkHref() has been removed according to the deprecated message added a while ago. Use the getLinkHelper() method instead, and call getLinkHref() on the helper itself — BC Break
    • Removed: the PagerfantaFactory::create() has been removed according to the deprecated message added a while ago. Use the createRepresentation() method instead — BC Break
    Source code(tar.gz)
    Source code(zip)
  • v2.1.0(Jan 13, 2014)

    Changelog

    • Added: allow users to add expression functions, cf. Expression Functions
    • Added: support curies in JsonHalSerializer
    • Added: LinkExtension, a Twig extension to generate links, cf. documentation
    • Added: VndErrorRepresentation representation class to support vnd.error specification, cf. documentation (#111, #114)
    • Added: PagerfantaFactory now exposes a createRepresentation() method, and replaces the deprecated create() method. See the important note below. (#131, #133)
    • Added: more documentation
    • Fixed: keep embedded relations even with custom relations (#122)
    • Fixed: PagerfantaFactory inlines \Traversable results (#128)

    Important Note

    The PagerfantaFactory has been refactored, and now exposes a createRepresentation() method. It is heavily recommended to use it! The old create() method has been deprecated and will be removed as of version 2.2.0.

    As this class is not part of the "core" of the lib, but rather a helper class, we decided not to bump the major version number. Such a change was required as this factory is nearly unusable in real life, and that is why we see this change as a "fix" rather than a "BC break". This is a sort of "soft" backward compatibility break, we apologize for the inconvenience.

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0(Dec 18, 2013)

    This is the first stable version of the new Hateoas library!

    The library has been rewritten from scratch, and merges both the plain old Hateoas library and the FSCHateoasBundle. This is a standalone PHP library, PSR-* compliant and extensively unit tested. We also provide a Symfony2 bundle: BazingaHateoasBundle.

    You will find the documentation here: http://hateoas-php.org/ (or here). There is no upgrade instructions as it is nearly impossible, but upgrading should not be that difficult, things being a lot easier now.

    Thank you @adrienbrault for the hard work! :heart: :yellow_heart: :blue_heart:

    Let's celebrate! :ship: :star: :stars: :star2: :tada: :boom: :cake: :candy:

    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-alpha4(Dec 16, 2013)

    • Added: more documentation
    • Added: tests for XmlSerializer::serializeEmbeds()
    • Changed: allow passing $inline to the PagerfantaFactory
    • Changed: pass SerializerContext into *SerializerInterface::createLinks()
    • Changed: refactor basic Collection support
    • Changed: SimpleCollectionTest (related to the change made on collections)
    • Changed: s/embedded/embeds/i
    • Changed: LinksFactory::createLinks() to LinksFactory::create() to be consistent with the EmbedsFactory
    • Fixed: undefined variable
    • Fixed: PaginatedCollectionTest
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-alpha3(Dec 11, 2013)

    • Added: documentation
    • Added: LinkHelper and LinkExpressionFunction classes (see LinkHelper)
    • Added: ExpressionFunctionInterface to extend the Expression Language
    • Changed: better exception message
    • Changed: refactor XmlDriver
    • Changed: require symfony/expression-language 2.4 stable
    • Changed: rename "Context Values" to "Context Variables"
    • Fixed: setUrlGenerator() method (builder)
    • Removed: Hateoas::getRelationsRepository() method
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-alpha2(Dec 5, 2013)

    • Added: file resources into the metadata for XML an YAML drivers, so cache can be rebuild when needed
    • Fixed: As metadata is in the same file as the one for a serializer, the elements from our namespace need a prefix
    • Added: support for XML metadata driver
    • Added: annotations reference documentation
    Source code(tar.gz)
    Source code(zip)
  • v2.0.0-alpha1(Oct 18, 2013)

    This is the very first release of Hateoas 2.0. :ship: :star: :star2: :fireworks: :clap:

    As we expect people to start playing with it but we didn't provide any documentation yet, this version is not meant for production use. We need your feedback, so don't hesitate to open issues!

    We are now working on the documentation to release a stable version as soon as possible, but we can't give any ETA yet.

    One more thing, I (@willdurand) would like to personally thank @adrienbrault for his impressive work on this new powerful version!

    Source code(tar.gz)
    Source code(zip)
Pocketmine Plugin implementing Stackable Spawners

Information This plugin adds a Stackable Spawner System to PMMP. When you place a Spawner, you can stack additional Spawners onto the same Spawner to

null 33 Aug 17, 2022
An API for implementing leaderboards into your games

simple-leaderboard-api An API for implementing leaderboards into your games How to use it You'll first want to visit http://indiealchemy.com/simple-le

null 4 Apr 10, 2022
Learning design patterns by implementing them in various programming languages.

design-patterns Learning design patterns by implementing them in various programming languages. Creational design patterns Creational design patterns

Paweł Tryfon 1 Dec 13, 2021
Type and shape system for arrays. Help write clearer code when implementing configs for your PocketMine-MP plugin or composer project.

ConfigStruct Type and shape system for arrays. Help write clearer code when implementing configs for your PocketMine-MP plugin or composer project. It

EndermanbugZJFC 9 Aug 22, 2022
Michael Pratt 307 Dec 23, 2022
Skosmos is a web-based tool providing services for accessing controlled vocabularies, which are used by indexers describing documents and searchers looking for suitable keywords.

Skosmos is a web-based tool providing services for accessing controlled vocabularies, which are used by indexers describing documents and searchers looking for suitable keywords.

National Library of Finland 195 Dec 24, 2022
A PHP library to convert text to speech using various services

speaker A PHP library to convert text to speech using various services

Craig Duncan 98 Nov 27, 2022
The Assure Alliance support website. This website is based on Questions2Answers and is a forum for support using Biblical Tools

The Assure Alliance support website. This website is based on Questions2Answers and is a forum for support using Biblical Tools

United Bible Societies Institute for Computer Assisted Publishing 3 Jul 29, 2022
A PHP 7.4+ library to consume the Confluent Schema Registry REST API

A PHP 7.4+ library to consume the Confluent Schema Registry REST API. It provides low level functions to create PSR-7 compliant requests that can be used as well as high level abstractions to ease developer experience.

Flix.TECH 38 Sep 1, 2022
KodExplorer is a file manager for web. It is also a web code editor, which allows you to develop websites directly within the web browser.

KodExplorer is a file manager for web. It is also a web code editor, which allows you to develop websites directly within the web browser.

warlee 5.5k Feb 10, 2022
A collection of samples that demonstrate how to call Google Cloud services from PHP.

PHP Docs Samples A collection of samples that demonstrate how to call Google Cloud services from PHP. See our other Google Cloud Platform github repos

Google Cloud Platform 875 Dec 29, 2022
A collection of samples that demonstrate how to call Google Cloud services from PHP.

PHP Docs Samples A collection of samples that demonstrate how to call Google Cloud services from PHP. See our other Google Cloud Platform github repos

Google Cloud Platform 796 Dec 22, 2021
Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

Introduction Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services. It handles almost all of the boilerpl

The Laravel Framework 2.2k Dec 31, 2022
MajorDoMo is an open-source DIY smarthome automation platform aimed to be used in multi-protocol and multi-services environment.

MajorDoMo (Major Domestic Module) is an open-source DIY smarthome automation platform aimed to be used in multi-protocol and multi-services environment. It is based on web-technologies stack and ready to be delivered to any modern device. It is very flexible in configuration with OOP paradigm used to set up automation rules and scripts. This platform can be installed on almost any personal computer running Windows or Linux OS.

Sergei Jeihala 369 Dec 30, 2022
Redmine API services

DevMakerLab/My-Mine Want to track and analyze your Redmine tickets/projects, Installation Examples Installation ⚠️ Requires >= PHP 7.4 ⚠️ composer req

DevMakerLab 5 Jul 19, 2022
Simple repository pattern for laravel, with services!

With repository and service you can separate business logic and query logic, slim controller and DRY. Simple generate repository and service with artisan command, automatically bind interface with repository

Yaz3 27 Jan 1, 2023
Prepare your Laravel apps incredibly fast, with various commands, services, facades and boilerplates.

Grafite Builder Grafite has archived this project and no longer supports or develops the code. We recommend using only as a source of ideas for your o

Grafite Inc 997 Dec 22, 2022
A cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services.

Motan Overview Motan is a cross-language remote procedure call(RPC) framework for rapid development of high performance distributed services. Related

Weibo R&D Open Source Projects 5.8k Dec 20, 2022
SPAM Registration Stopper is a Q2A plugin that prevents highly probable SPAM user registrations based on well-known SPAM checking services and other techniques

SPAM Registration Stopper [by Gabriel Zanetti] Description SPAM Registration Stopper is a Question2Answer plugin that prevents highly probable SPAM us

Gabriel Zanetti 2 Jan 23, 2022