• Stars
    star
    114
  • Rank 308,031 (Top 7 %)
  • Language
    PHP
  • License
    MIT License
  • Created over 9 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

JSON API Transformer Bundle for Symfony 2 and Symfony 3

Symfony JSON-API Transformer Bundle

For Symfony 2 and Symfony 3

Scrutinizer Code Quality SensioLabsInsight Latest Stable Version Total Downloads License Donate

Installation

Step 1: Download the Bundle

Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:

$ composer require nilportugues/jsonapi-bundle

Step 2: Enable the Bundle

Then, enable the bundle by adding it to the list of registered bundles in the app/AppKernel.php file of your project:

<?php
// app/AppKernel.php
// ...
class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new NilPortugues\Symfony\JsonApiBundle\NilPortuguesSymfonyJsonApiBundle(),
        );
        // ...
    }
    // ...
}

Usage

Creating the mappings

Mapping directory

Mapping files should be located at the app/config/serializer directory. This directory must be created.

It can be also be customized and placed elsewhere by editing the app/config/config.yml configuration file:

# app/config/config.yml

nilportugues_json_api:
    mappings: 
        - "%kernel.root_dir%/config/serializer/"
        - @AppBundle/Product/config/Mappings

Mapping files

The JSON-API transformer works by transforming an existing PHP object into its JSON representation. For each object, a mapping file is required.

Mapping files must be placed in the mappings directory. The expected mapping file format is .yml and will allow you to rename, hide and create links relating all of your data.

For instance, here's a quite complex Post object to demonstrate how it works:

$post = new Post(
    new PostId(9),
    'Hello World',
    'Your first post',
    new User(
        new UserId(1),
        'Post Author'
    ),
    [
        new Comment(
            new CommentId(1000),
            'Have no fear, sers, your king is safe.',
            new User(new UserId(2), 'Barristan Selmy'),
            [
                'created_at' => (new DateTime('2015/07/18 12:13:00'))->format('c'),
                'accepted_at' => (new DateTime('2015/07/19 00:00:00'))->format('c'),
            ]
        ),
    ]
);

And the series of mapping files required:

# app/config/serializer/acme_domain_dummy_post.yml

mapping:
  class: Acme\Domain\Dummy\Post
  alias: Message
  aliased_properties:
    author: author
    title: headline
    content: body
  hide_properties: []
  id_properties:
    - postId
  urls:
    self: get_post ## @Route name
    comments: get_post_comments ## @Route name
  relationships:
    author:
      related: get_post_author ## @Route name
      self: get_post_author_relationship  ## @Route name
# app/config/serializer/acme_domain_dummy_value_object_post_id.yml

mapping:
  class: Acme\Domain\Dummy\ValueObject\PostId
  aliased_properties: []
  hide_properties: []
  id_properties:
  - postId
  urls:
    self: get_post  ## @Route name
  relationships:
    comment:
      self: get_post_comments_relationship  ## @Route name
# app/config/serializer/acme_domain_dummy_comment.yml

mapping:
  class: Acme\Domain\Dummy\Comment
  aliased_properties: []
  hide_properties: []
  id_properties:
    - commentId
  urls:
    self: get_comment ## @Route name
  relationships:
    post:
      self: get_post_comments_relationship ## @Route name
# app/config/serializer/acme_domain_dummy_value_object_comment_id.yml

mapping:
  class: Acme\Domain\Dummy\ValueObject\CommentId
  aliased_properties: []
  hide_properties: []
  id_properties:
    - commentId
  urls:
    self: get_comment ## @Route name
  relationships:
    post:
      self: get_post_comments_relationship ## @Route name
# app/config/serializer/acme_domain_dummy_user.yml

mapping:
  class: Acme\Domain\Dummy\User
  aliased_properties: []
  hide_properties: []
  id_properties:
  - userId
  urls:
    self: get_user
    friends: get_user_friends  ## @Route name
    comments: get_user_comments  ## @Route name
# app/config/serializer/acme_domain_dummy_value_object_user_id.yml

mapping:
  class: Acme\Domain\Dummy\ValueObject\UserId
  aliased_properties: []
  hide_properties: []
  id_properties:
  - userId
  urls:
    self: get_user  ## @Route name
    friends: get_user_friends  ## @Route name
    comments: get_user_comments  ## @Route name

Outputing API Responses

It is really easy, just get an instance of the JsonApiSerializer from the Service Container and pass the object to its serialize() method. Output will be valid JSON-API.

Here's an example of a Post object being fetched from a Doctrine repository.

Finally, a helper trait, JsonApiResponseTrait is provided to write fully compilant responses wrapping the PSR-7 Response objects provided by the original JSON API Transformer library.

<?php
namespace AppBundle\Controller;

use NilPortugues\Symfony\JsonApiBundle\Serializer\JsonApiResponseTrait;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class PostController extends Controller
{
    use JsonApiResponseTrait;

    /**
     * @\Symfony\Component\Routing\Annotation\Route("/post/{postId}", name="get_post")
     *
     * @param $postId
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function getPostAction($postId)
    {
        $post = $this->get('doctrine.post_repository')->find($postId);
        
        $serializer = $this->get('nil_portugues.serializer.json_api_serializer');

        /** @var \NilPortugues\Api\JsonApi\JsonApiTransformer $transformer */
        $transformer = $serializer->getTransformer();
        $transformer->setSelfUrl($this->generateUrl('get_post', ['postId' => $postId], true));
        $transformer->setNextUrl($this->generateUrl('get_post', ['postId' => $postId+1], true));

        return $this->response($serializer->serialize($post));
    }
} 

Output:

HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/vnd.api+json
{
    "data": {
        "type": "message",
        "id": "9",
        "attributes": {
            "headline": "Hello World",
            "body": "Your first post"
        },
        "links": {
            "self": {
                "href": "http://example.com/posts/9"
            },
            "comments": {
                "href": "http://example.com/posts/9/comments"
            }
        },
        "relationships": {
            "author": {
                "links": {
                    "self": {
                        "href": "http://example.com/posts/9/relationships/author"
                    },
                    "related": {
                        "href": "http://example.com/posts/9/author"
                    }
                },
                "data": {
                    "type": "user",
                    "id": "1"
                }
            }
        }
    },
    "included": [
        {
            "type": "user",
            "id": "1",
            "attributes": {
                "name": "Post Author"
            },
            "links": {
                "self": {
                    "href": "http://example.com/users/1"
                },
                "friends": {
                    "href": "http://example.com/users/1/friends"
                },
                "comments": {
                    "href": "http://example.com/users/1/comments"
                }
            }
        },
        {
            "type": "user",
            "id": "2",
            "attributes": {
                "name": "Barristan Selmy"
            },
            "links": {
                "self": {
                    "href": "http://example.com/users/2"
                },
                "friends": {
                    "href": "http://example.com/users/2/friends"
                },
                "comments": {
                    "href": "http://example.com/users/2/comments"
                }
            }
        },
        {
            "type": "comment",
            "id": "1000",
            "attributes": {
                "dates": {
                    "created_at": "2015-08-13T21:11:07+02:00",
                    "accepted_at": "2015-08-13T21:46:07+02:00"
                },
                "comment": "Have no fear, sers, your king is safe."
            },
            "relationships": {
                "user": {
                    "data": {
                        "type": "user",
                        "id": "2"
                    }
                }
            },            
            "links": {
                "self": {
                    "href": "http://example.com/comments/1000"
                }
            }
        }
    ],
    "links": {
        "self": {
            "href": "http://example.com/posts/9"
        },
        "next": {
            "href": "http://example.com/posts/10"
        }
    },
    "jsonapi": {
        "version": "1.0"
    }
}

Request objects

JSON API comes with a helper Request class, NilPortugues\Api\JsonApi\Http\Request\Request(ServerRequestInterface $request), implementing the PSR-7 Request Interface. Using this request object will provide you access to all the interactions expected in a JSON API:

JSON API Query Parameters:
  • &fields[resource]=field1,field2 will only show the specified fields for a given resource.
  • &include=resource show the relationship for a given resource.
  • &include=resource.resource2 show the relationship field for those depending on resource2.
  • &sort=field1,-field2 sort by field2 as DESC and field1 as ASC.
  • &sort=-field1,field2 sort by field1 as DESC and field2 as ASC.
  • &page[number] will return the current page elements in a page-based pagination strategy.
  • &page[size] will return the total amout of elements in a page-based pagination strategy.
  • &page[limit] will return the limit in a offset-based pagination strategy.
  • &page[offset] will return the offset value in a offset-based pagination strategy.
  • &page[cursor] will return the cursor value in a cursor-based pagination strategy.
  • &filter will return data passed in the filter param.
NilPortugues\Api\JsonApi\Http\Request\Request

Given the query parameters listed above, Request implements helper methods that parse and return data already prepared.

namespace \NilPortugues\Api\JsonApi\Http\Request;

class Request
{
  public function __construct(ServerRequestInterface $request = null) { ... }
  public function getIncludedRelationships() { ... }
  public function getSort() { ... }
  public function getPage() { ... }
  public function getFilters() { ... }
  public function getFields() { ... }
}

Response objects (JsonApiResponseTrait)

The following JsonApiResponseTrait methods are provided to return the right headers and HTTP status codes are available:

    private function errorResponse($json);
    private function resourceCreatedResponse($json);
    private function resourceDeletedResponse($json);
    private function resourceNotFoundResponse($json);
    private function resourcePatchErrorResponse($json);
    private function resourcePostErrorResponse($json);
    private function resourceProcessingResponse($json);
    private function resourceUpdatedResponse($json);
    private function response($json);
    private function unsupportedActionResponse($json);

Integration with NelmioApiDocBundleBundle

The NelmioApiDocBundle is a very well known bundle used to document APIs. Integration with the current bundle is terrible easy.

Here's an example following the PostContoller::getPostAction() provided before:

<?php
namespace AppBundle\Controller;

use NilPortugues\Symfony\JsonApiBundle\Serializer\JsonApiResponseTrait;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class PostController extends Controller
{
    use JsonApiResponseTrait;

    /**
     * Get a Post by its identifier. Will return Post, Comments and User data.
     *
     * @Nelmio\ApiDocBundle\Annotation\ApiDoc(
     *  resource=true,
     *  description="Get a Post by its unique id",
     * )
     *
     * @Symfony\Component\Routing\Annotation\Route("/post/{postId}", name="get_post")
     * @Sensio\Bundle\FrameworkExtraBundle\Configuration\Method({"GET"})
     *
     * @param $postId
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function getPostAction($postId)
    {
        $post = $this->get('doctrine.post_repository')->find($postId);
        
        $serializer = $this->get('nil_portugues.serializer.json_api_serializer');

        /** @var \NilPortugues\Api\JsonApi\JsonApiTransformer $transformer */
        $transformer = $serializer->getTransformer();
        $transformer->setSelfUrl($this->generateUrl('get_post', ['postId' => $postId], true));
        $transformer->setNextUrl($this->generateUrl('get_post', ['postId' => $postId+1], true));

        return $this->response($serializer->serialize($post));
    }
} 

And the recommended configuration to be added in app/config/config.yml

#app/config/config.yml

nelmio_api_doc:
  sandbox:
        authentication:
          name: access_token
          delivery: http
          type:     basic
          custom_endpoint: false
        enabled:  true
        endpoint: ~
        accept_type: ~
        body_format:
            formats: []
            default_format: form
        request_format:
            formats:
                json: application/vnd.api+json
            method: accept_header
            default_format: json
        entity_to_choice: false

Quality

To run the PHPUnit tests at the command line, go to the tests directory and issue phpunit.

This library attempts to comply with PSR-1, PSR-2, PSR-4 and PSR-7.

If you notice compliance oversights, please send a patch via Pull Request.

Contribute

Contributions to the package are always welcome!

Support

Get in touch with me using one of the following means:

Authors

License

The code base is licensed under the MIT license.

More Repositories

1

php-sql-query-builder

An elegant lightweight and efficient SQL Query Builder with fluid interface SQL syntax supporting bindings and complicated query generation.
PHP
401
star
2

laravel5-jsonapi

Laravel 5 JSON API Transformer Package
PHP
310
star
3

php-backslasher

[Git hook] Tool to add all PHP internal functions and constants to its namespace by adding backslash to them.
PHP
87
star
4

php-json-api

JSON API transformer outputting valid (PSR-7) API Responses.
PHP
68
star
5

php-serializer

Serialize PHP variables, including objects, in any format. Support to unserialize it too.
PHP
48
star
6

php-sitemap

Standalone sitemap builder 100% standards compliant.
PHP
48
star
7

react-jsonschema-form-semanticui

A React component for building forms from JSON Schema with Semantic UI
JavaScript
45
star
8

sql-repository

[PHP 7] SQL Repository implementation
PHP
36
star
9

php-sql-query-formatter

A very lightweight PHP class that re-formats unreadable or computer-generated SQL query statements to human-friendly readable text.
PHP
36
star
10

laravel5-jsonapi-dingo

Laravel5 JSONAPI and Dingo together to build APIs fast
PHP
30
star
11

php-hal

HAL+JSON & HAL+XML API transformer outputting valid (PSR-7) API Responses.
PHP
30
star
12

php-schema.org-mapping

A fluent interface to create mappings using Schema.org for Microdata and JSON-LD.
PHP
30
star
13

php-xml

XML transformer outputting valid (PSR-7) API Responses.
PHP
22
star
14

php-sphinx-search

Sphinx for PHP 5.3 and above. Fully PHPUnit tested.
PHP
19
star
15

eloquent-mongodb-repository

Eloquent MongoDB Repository Implementation
PHP
18
star
16

eloquent-repository

Eloquent Repository implementation
PHP
17
star
17

php-api-problems

PSR7 Response implementation for the Problem Details for HTTP APIs
PHP
17
star
18

plans

Laravel Plans is a package for SaaS-like apps that need easy management over plans, features and event-driven updates on plans and subscriptions.
PHP
16
star
19

laravel5-hal-json

Laravel 5 HAL+JSON API Transformer Package
PHP
15
star
20

php-todo-finder

[Git hook] Do not allow commits if the total amount of to-do increased or is above a user-defined threshold.
PHP
12
star
21

php-validator

A powerful and elegant stand-alone validation library with no dependencies.
PHP
12
star
22

php-forbidden-functions

[Git hook] Command line to look for functions that should be avoided
PHP
10
star
23

mongodb-repository

[PHP 7] MongoDB Repository implementation
PHP
9
star
24

symfony-hal-json

HAL+JSON API Transformer Bundle for Symfony 2 and Symfony 3
PHP
9
star
25

php-api-transformer

Base library providing the core functionality for API transformation.
PHP
9
star
26

php-assert

A simple and elegant assertion library for input validation.
PHP
9
star
27

repository-cache

[PHP 7] Repository with Cache layer using PSR-6
PHP
7
star
28

php-cache

Cache layer for PHP applications capable of being used standalone or with the Chain of Responsability pattern.
PHP
7
star
29

laravel5-jsend

Laravel 5 JSend API Transformer Package
PHP
5
star
30

php-uuid

Use this class to encapsulate the latest and more secure Uuid versions
PHP
5
star
31

php-json

JSON transformer outputting valid (PSR-7) API Responses.
PHP
4
star
32

filesystem-repository

[PHP 7] FileSystem Repository implementation
PHP
4
star
33

serializer-eloquent-driver

Driver for the Serializer library caring of Eloquent ORM model serialization
PHP
4
star
34

twig-macros-boilerplate

Twig macros as building blocks
HTML
3
star
35

laravel5-json

Laravel 5 JSON Transformer Package
PHP
3
star
36

docker-nginx_php7

nginx + PHP7 Docker
Nginx
3
star
37

docker

Docker setup for most of my projects
Perl
2
star
38

docker-jenkins

Jenkins + PHP7 + NodeJS
Shell
2
star
39

php-jsend

JSend API transformer outputting valid (PSR-7) API Responses.
PHP
2
star
40

php-namespace-checker

[WIP][Git hook] Looks into composer.json autoload and checks files for namespace mismatches.
PHP
2
star
41

websocket-react-example

Example on how to run Socket.io and ReactJS
JavaScript
2
star
42

python3-docker-flask-uwsgi

Example repo for a Python Flask application using Docker with Nginx & uWSGI
Python
2
star
43

docker-ansistrano

Ansistrano Docker
Shell
1
star
44

graphql-data-access-generator

Generate all queries, mutation, subscriptions, fragments and types for any GraphQL schema
TypeScript
1
star