Download the PHP package gointegro/hateoas without Composer
On this page you can find all versions of the php package gointegro/hateoas. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package hateoas
GOintegro / HATEOAS
This is a library that uses a Doctrine 2 entity map and a RAML API definition to conjure a HATEOAS API, following the JSON-API specification.
You don't get scaffolds. You get a working API.
You get a working API with features sweeter than a Bobcat's self-esteem.
Features
Here's what I mean.
- Flat, referenced JSON serialization.
- Clear distinction between scalar fields and linked resources.
- Magic controllers.
- Fetching resources, with support for:
- Sparse fields;
- Linked resources expansion;
- Standarized filtering and sorting;
- Pagination;
- Resource metadata, such as facets in a search.
- Altering resources, with support for:
- Processing multiple actions in one request;
- Request validation using JSON schema;
- Entity validation using Symfony's validator;
- Create, update, and delete out of the box;
- Assign services to handle any of the above for specific resources.
- Translatable content out-of-the-box.
- Metadata caching, similar to that of Doctrine 2;
- Redis,
- Or Memcached.
Here's what you'll need.
- A Doctrine 2 entity map;
- A RAML API definition;
- At least one Symfony 2 security voter.
Try it out
Check out the example app project, so you can feel the magic in your finger tips without much ado.
Installation
Check out the Symfony 2 bundle for a full-stack framework implementation.
Usage
Design your API in the RAML language following the JSON-API spec.
Something like this example, which assumes you have an entity class with the short-name User
.
Have your entity implement the resource interface.
The namespace isn't considered, only the short-name is used to match against the resources defined above.
Voilà - you get the following for free.
Any combination.
You also get these.
And you get to link or unlink resources thus.
Sweet, right?
The
resource_type
must match the calculated type - for now. E.g.UserGroup
,user-groups
.
Resources
But you need to have some control over what you expose, right? Got you covered.
You can optionally define a class like this for your entity, and optionally define any of the properties and methods you will see within.
Check out the unit tests for more details.
JSON Schema
Requests that create or update resources have the content of their bodies validated against the schema defined in the RAML for that resource and method.
Since bodies in JSON-API look pretty similar whether you are fetching, creating, or updating, you can use a default schema, defined in the root of the RAML document with the resource type as key.
For example, this could be the RAML definition for the /users
resource.
Entities
This bundle is pretty entity-centric. The way your entities look and the relationships between them, as mapped in Doctrine 2, are essential to the intelligence this bundle employs in determining what your API should look like.
Security
Access control is handled by Symfony's Security Component, so either security voters or ACL must be configured.
If you don't want security at all, just configure a single voter accepting anything that implements GoIntegro\Hateoas\JsonApi\ResourceEntityInterface
. Not the best advice ever, though.
What about pagination? I'm pretty sure isGranted
will not be called against every single entity in the collection - right?
Absolutely.
In order to address this, we came up with a really simple solution. We mixed the security voter and custom filter interfaces.
Have your voter/filter implement GoIntegro\Hateoas\Security\VoterFilterInterface
and tag it with both the security.voter
and hateoas.repo_helper.filter
tags.
Your access control logic for viewing the entity should be expressed in the shape of a security voter within the method vote
, and the shape of a fetch request filter within the method filter
.
Voilà.
Validation
Default validation is handled by Symfony's Validator Component, so you can configure basic validation right on your entities.
Also, you can extend the validator by writing your own constraints.
Since constraints can be services, this means that you are probably not going to need to create custom builders or mutators.
Violations of the unique entity constraint ultimately result in a 409 HTTP response status, conflict. :fire:
JSON-API requires 409 to only be used when attempting to create a relationship that already exists. We are expanding its application to include any instance in which validation fails due to a conflict with another resource.
If you'd like to create a custom constraint in which a violation is taken to mean conflict between resources, just have it implement GoIntegro\Hateoas\Entity\Validation\ConflictConstraintInterface
.
Transactions
Creating, updating, or deleting multiple resources on a single request is supported by JSON-API - but no partial updates are allowed.
We use explicit transaction demarcation on the controller that handles creating, updating, and deleting resources magically so that this rule is enforced.
Creating, updating, and deleting
As mentioned, services for creating, updating, and deleting resources are provided by default.
But what about your business logic? That wouldn't fly far.
You can register services that handle each of these operations for specific resources called builders, mutators, and deleters by tagging them.
This builder class should implement a certain interface. Here are the available tags and interfaces.
Tag | Interface |
---|---|
hateoas.entity.builder | GoIntegro\Hateoas\Entity\BuilderInterface |
hateoas.entity.mutator | GoIntegro\Hateoas\Entity\MutatorInterface |
hateoas.entity.deleter | GoIntegro\Hateoas\Entity\DeleterInterface |
Updating relationships and association ownership
You've set everything up, made an HTTP request that should relate two resources, got a 200/204 status response, but your relationship wasn't created.
What gives? Here's a likely cause.
Remember that we will only operate on the entity associated to the resource you're altering. Even when relating resources, you're making a request to either one of them.
E.g.
POST /users/1/links/user-groups
is acting upon/users
.
If the entity you're acting upon isn't the owner of the association in the eyes of Doctrine, your changes will not be persisted.
You need to do two things:
- Enable cascading on the inverse side of the association;
- Have the setter on the inverse side also alter the entity it got.
Here's an example.
This bit of advice would fit a FAQ or troubleshooting section just as well.
Translatable content
The framework provides support for working with the translatable entities feature of @l3pp4rd's Doctrine Extensions (AKA Gedmo) through @stof's Bundle.
When fetching or updating a translatable resource, the framework will act upon the translation corresponding to the locale negotiated by the GoIntegro\Hateoas\JsonApi\Request\DefaultLocaleNegotiator
.
You can override the default locale negotiator by having your negotiator class implement GoIntegro\Hateoas\JsonApi\Request\LocaleNegotiatorInterface
and exposing it as a service using the tag hateoas.request_parser.locale
.
You can fetch all translations for one or many resources by passing the query string parameter meta=i18n
. You can also update them by making a PUT
request with the same body you get.
This is an example out of the example app.
Parlez-vous JSON-API? Oui, oui.
-
Extending
Muggle Controllers
If you want to override the magic controllers for whatever reason, just create a good old Symfony 2 controller.
You can use entities implementing the ResourceEntityInterface
with the services provided by the HATEOAS bundle quite independently.
Here's a pretty basic example.
Check out the bundle's services.yml
for a glimpse at the HATEOAS arsenal we keep there.
Ghosts :ghost:
I know what you're thinking - what if my resource does not have an entity? Am I left to fend for myself in the cold dark night?
Not a chance. Ghosts are there with you, in the dark. To help you out.
Ghosts are what you create ghost-resources from instead of persisted entities. They are created within a custom HATEOAS controller, and can be fed to the resource factory in lieu of an entity. They define their relationships rather than the ORM knowing about them beforehand.
What you use for an Id, and the extent to which you use them is entirely up to you.
Query filters
The standard fetch request filters are bundled along with the bundle.
This is the service providing them.
If you're somewhat familiar with tagged services, you probably guessed that you can add your own.
Just have your filter class implement GoIntegro\Hateoas\JsonApi\Request\FilterInterface
, and add the hateoas.repo_helper.filter
tag when you declare it as a service.
Your filter should use the entity and filter parameters it gets in order to decide whether or not to act. Make sure a single class doesn't get too much filtering responsibility.
Testing
The bundle comes with a comfy PHPUnit test case designed to make HATEOAS API functional tests.
A simple HTTP client makes the request and assertions are made using JSON schemas.
Error handling
JSON-API covers how to inform about errors as well.
Our implementation isn't trully as complete as could be, but you can tell Twig to use our ExceptionController instead of its own in order to have your errors serialized properly.
Fetching multiple URLs
Here's something useful but not RESTful.
You can use the /multi action to fetch several JSON-API URLs, and this won't even result in an additional HTTP request.
The URLs just need to be encoded, but you can use the full set of JSON-API functionality supported.
A blender service wil make sure to notify you if, by chance, the URLs provided are not mergeable.
Caching
Yeah. These processes are not cheap.
You might want to hold on to that metadata you've mined or that resource you've serialized for a while.
Resource Metadata
The resource metadata describes a resource type. It describes its name, fields, its relationships to other resources, and other such things.
It's akin to the Doctrine 2 entity mapping, Doctrine\ORM\Mapping\ClassMetadata
.
We obtain this class by inspecting an entity's mapping and its class, using the ORM and reflection.
You can cache a resource type's metadata for as long as neither of these two things change.
Here's how. Add this parameter.
Cache type | Parameter value |
---|---|
Array (none) | GoIntegro\Hateoas\JsonApi\ArrayResourceCache |
Redis | GoIntegro\Hateoas\JsonApi\RedisResourceCache |
Memcached | GoIntegro\Hateoas\JsonApi\MemcachedResourceCache |
You can customize your Redis or Memcached configuration by using any of the following options. Below are the default values.
HTTP Response
Fetch responses are all delivered with an Etag.
The Etag is created from the full body of the response, so it accurately represents the JSON-API document you're fetching, along with its includes, sparse fields, meta, etc.
Etags on requests are checked using Symfony.
Feedback
Feel free to open an issue if you have valuable (or otherwise) feedback. Hoping to hear from you (either way).
If you're going to dare rocking so hard as to make a pull request, use the master
branch for fixes, and the develop
branch for proposed features.
We are using the Git Flow branching model. Here's a nice cheat-sheet that can give you a general idea of how it goes.
New code should not exceed the legendary eighty char boundary, and be fully documented.
FYI: we still need to migrate the issues from the bundle repo.
Roadmap
Any issue labeled enhancement goes.
PATCH support is coming soon.
(Most of the enhancement issues are still in the bundle repo.)
Disclaimer
You might have noticed something fishy in the PHP snippets above.
I don't actually support using them, my text editor just goes crazy if I don't.
All versions of hateoas with dependencies
symfony/doctrine-bridge Version *
doctrine/doctrine-bundle Version *
doctrine/orm Version *
rhumsaa/uuid Version *
predis/predis Version *
symfony/http-foundation Version *
symfony/http-kernel Version *
symfony/filesystem Version *
symfony/routing Version *
symfony/security-core Version *
symfony/translation Version *
symfony/security Version *
symfony/validator Version *
symfony/yaml Version *
gointegro/json Version *
gointegro/raml Version *