rstgroup / json-api-php
A PHP library for creating json-api standard responses (see: http://jsonapi.org)
Installs: 320
Dependents: 0
Suggesters: 0
Security: 0
Stars: 24
Watchers: 18
Forks: 9
Open Issues: 0
Requires
- php: >=5.3.0
Requires (Dev)
- phpunit/phpunit: 4.1
This package is not auto-updated.
Last update: 2024-09-24 07:49:21 UTC
README
It's a lightweight PHP library for creating json-api standard responses. If you want to know more about the json-api standard, please see the details at http://jsonapi.org.
About this doc
This document is not finished yet, it's only a draft for now.
Usage
Quick example
<?php use RstGroup\JsonApiPhp\EntityInterface; use RstGroup\JsonApiPhp\Resource; use RstGroup\JsonApiPhp\Relation; use RstGroup\JsonApiPhp\Writer; // define posts resource $postsResource = new Resource(); $postsResource->setCollectionName('posts'); $postsResource->setName('post'); $postsResource->setHref('/posts/{posts.id}'); // create post entities $post1 = new Post(1, 'first post'); $post2 = new Post(2, 'second awesome post'); $postsResource->setEntities(array($post1, $post2)); // render result $writer = new Writer(); echo json_encode($writer->write($postsResource));
{"posts": [ { "id": "1", "href": "/posts/1", "text": "first post" }, { "id": "2", "href": "/posts/2", "text": "second awesome post" } ]}
Introduction
As an example we take two resources: "authors" and "posts". Author creates posts; he can have many posts, but every single post is related to only one author.
The first thing that we should do is define our resources.
Defining resources
Every single resource class should extend the (surprise!) Resource
class. However, you don't need to inherit from the Resource
class if you don't want to. For example:
<?php use RstGroup\JsonApiPhp\EntityInterface; use RstGroup\JsonApiPhp\Resource; use RstGroup\JsonApiPhp\Relation; use RstGroup\JsonApiPhp\Writer; // authors $authorsResource = new Resource(); $authorsResource->setCollectionName('authors'); $authorsResource->setName('author'); $authorsResource->setHref('/authors/{authors.id}'); // posts $postsResource = new Resource(); $postsResource->setCollectionName('posts'); $postsResource->setName('post'); $postsResource->setHref('/posts/{posts.id}');
As you can see, every resource requires these three properties to be defined:
- collection name
- name
- href
The first two of them are used by the Writer
class in order to apply naming conventions specified by the standard. "href" is used to prepare full link to a particular resource. Name
stands for name of a single entity. Collection name
stands for plural name of resource entities, while href
should contain url template for a single resource entity.
Ok, now we know what kind of resources we have, what are their names, collection names, and links. You may have noticed that links are set as templates with placeholders, e.g. {authors.id}
. Properly prepared placeholders are used by Writer
to determine which parts should be replaced with values (and for a few other things, too). It will be helpful with more complex links, for example: /authors/{authors.id}/posts/{posts.id}
.
It's important to know that a placeholder MUST have a form like this: {RESOURCE_COLLECTION_NAME.id}
Now it's time for entities.
Defining entities
The very first entity we would like to create is Author
. As you can see below, the Author
class implements EntityInterface
. It means that two methods should be implemented: getId()
and toArray()
.
getId()
returns id of a particular entity and it's used in every place in the library where that id should be known.
toArray()
should return those properties of an Author
class that are considered resource fields in the response representation. In the Author
class these will be firstName
and lastName
, as id
will be added automatically.
class Author implements EntityInterface { protected $id; protected $firstName; protected $lastName; public function __construct($id, $firstName, $lastName) { $this->id = $id; $this->firstName = $firstName; $this->lastName = $lastName; } public function getId() { return $this->id; } public function toArray() { return array( 'firstName' => $this->firstName, 'lastName' => $this->lastName, ); } }
class Post implements EntityInterface { protected $id; protected $text; public function __construct($id, $text) { $this->id = $id; $this->text = $text; } public function getId() { return $this->id; } public function toArray() { return array( 'text' => $this->text, ); } }
Now we're ready to prepare our first json-api standard result:
... $author1 = new Author(10, 'John', 'Doe'); $author2 = new Author(20, 'Adam', 'Novak'); $authorsResource->setEntities(array($author1, $author2)); $writer = new Writer(); $result = $writer->write($authorsResource); echo json_encode($result);
And the result looks like this:
{"authors": [ { "id": "10", "href": "/authors/10", "firstName": "John", "lastName": "Doe" }, { "id": "20", "href": "/authors/20", "firstName": "Adam", "lastName": "Novak" } ]}
So, we have an authors list that could be achieved via, for example http://api.example.com/authors
link, but what about related resources like posts
? Well, for that we need to define resource relations.
Adding resource relations
One-to-one
As it was said earlier, every single post can have a relation to just one author. Let's say that we have two posts, added by the same author. For that we need to modify the Post
class a little:
class Post implements EntityInterface { ... /** * @var Author */ protected $author; /** * @param Author $author * @return $this */ public function setAuthor(Author $author) { $this->author = $author; return $this; } /** * @return Author */ public function getAuthor() { return $this->author; } ... }
What we've just done here was adding the $author
property and its setter and getter methods. Author should be an instance of the Author
entity.
Hint: we DID NOT add our
$author
property to the list of entity fields returned by thetoArray()
method, because$author
has only one purpose: to hold data of author related to the post.
... $author1 = new Author(10, 'John', 'Doe'); $post1 = new Post(1, 'first post'); $post1->setAuthor($author1); $post2 = new Post(2, 'second awesome post'); $post2->setAuthor($author1); $postsResource->setEntities(array($post1, $post2));
Setter is not as important as getter---we could use constructor for that as well---but getter method in this case MUST be named exactly getAuthor()
. Why? Because Writer
will automatically determine the name of a needed getter method based on:
- relation type (one-to-one/one-to-many)
- resource name/collection name (here: author/authors)
In our case one post is related to one author, so the relation type is one-to-one
. Therefore, the "author" resource name will be used as the name of the getter method. In case of a one-to-many
relation (like author having many posts), collection name ("posts") of the Post
resource will be used. Because of that, getPosts()
from the Author
entity class will serve as the getter method.
Anyway, we create relation between post and its author like this:
... $postsResource->addRelation(new Relation(Relation::TO_ONE, $authorsResource)); $writer = new Writer(); $result = $writer->write($postsResource); echo json_encode($result);
And the result is:
{"posts": [ { "id": "1", "href": "/posts/1", "text": "first post", "links": { "author": { "id": "10", "href": "/authors/10", "type": "authors" } } }, { "id": "2", "href": "/posts/2", "text": "second awesome post", "links": { "author": { "id": "10", "href": "/authors/10", "type": "authors" } } } ]}
As you can see, new fields called links
were added to the result in case of every post entity.
Field links
contains short info about author related to a particular post. It could be returned in two (three for one-to-many relation type) forms: either as an id or as an object. The latter takes place by default, in order to change this behaviour you need to call:
$writer->setLinkForm(Writer::AS_ID);
The result will be:
{"posts": [ { "id": "1", "href": "/posts/1", "text": "first post", "links": { "author": "10" } }, { "id": "2", "href": "/posts/2", "text": "second awesome post", "links": { "author": "10" } } ] ...
Or you could just---for some strange reasons---switch that off:
$writer->attachResourceObjectsLinks(false);
The result will be:
{"posts": [ { "id": "1", "href": "/posts/1", "text": "first post", }, { "id": "2", "href": "/posts/2", "text": "second awesome post", } ], ...
One-to-many
For now we know how to define a one-to-one
relation (post has an author), but what about one-to-many
relation (author has posts)? This is how it should be done in case of the Author
class:
class Author implements EntityInterface { ... /** * @var Post[] */ protected $posts; /** * @param Post[] $posts * @return $this */ public function setPosts(array $posts) { $this->posts = $posts; return $this; } /** * @return Post[] */ public function getPosts() { return $this->posts; } ... }
... and next:
$post1 = new Post(1, 'first post'); $post2 = new Post(2, 'second awesome post'); $author1 = new Author(10, 'John', 'Doe'); $author1->setPosts(array($post1, $post2)); $authorsResource->setEntities(array($author1)); $authorsResource->addRelation(new Relation(Relation::TO_MANY, $postsResource)); $writer = new Writer(); echo json_encode($writer->write($authorsResource));
So the result looks like this:
{"authors": [ { "id": "10", "href": "/authors/10", "firstName": "John", "lastName": "Doe", "links": { "posts": { "ids": ["1", "2"], "href": "/posts/1,2", "type": "posts" } } } ]}
Embedding related resources data
As mentioned in the previous section, adding relation to a resource causes the links
field to appear in every entity. However, if you want data on related resource to be embedded in response representation, you should fill entity object up with proper values, and turn on attaching linked
objects to representation, because it's disabled by default.
In our case filling up is already done as we've set all the properties earlier via constructor:
$author1 = new Author(10, 'John', 'Doe'); ... $post1 = new Post(1, 'first post'); $post2 = new Post(2, 'second awesome post');
So there's only one thing left to do:
$writer->setAttachLinked(true);
As we can see, the top-level linked
field contains entities with data on resources related to the author resource:
{"authors": [ { "id": "10", "href": "/authors/10", "firstName": "John", "lastName": "Doe", "links": { "posts": { "ids": ["1", "2"], "href": "/posts/1,2", "type": "posts" } } } ], "linked": { "posts": [ { "id": "1", "href": "/posts/1", "text": "first post" }, { "id": "2", "href": "/posts/2", "text": "second awesome post" } ] }}
Url templates
Url templates can be used to describe URL format for resources according to their type. You can add your url templates like this:
$authorTemplate = new Template('posts.author', '/authors/{posts.author.id}', 'authors'); $postsResource->addTemplate($authorTemplate);
{ "links": { "posts.author": "/authors/{posts.author.id}" }, "posts": [ { "id": "1", "href": "/posts/1", "text": "first post", "links": { "author": { "id": "111", "href": "/authors/111", "type": "authors" } } }, ... }
TODO's
- support for 'meta'.