Giter Site home page Giter Site logo

nilportugues / symfony-jsonapi Goto Github PK

View Code? Open in Web Editor NEW
115.0 13.0 19.0 37 KB

JSON API Transformer Bundle for Symfony 2 and Symfony 3

Home Page: http://nilportugues.com

License: MIT License

PHP 100.00%
symfony symfony-bundle symfony3 symfony2 php php7 microservice microservices api serialization

symfony-jsonapi's Introduction

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.

symfony-jsonapi's People

Contributors

gsokol avatar m1n0 avatar nilportugues avatar xdimedrolx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

symfony-jsonapi's Issues

Several questions about working with Doctrine relations

Hello.

How to use it with Doctrine relations (both types: Associations and Embedded)? I have several problems with this feature.

src/FirstBundle/Entity/First.php

<?php

namespace FirstBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use SecondBundle\Entity\Second;

/**
 * @ORM\Entity(repositoryClass="FirstBundle\Repository\First")
 */
class First
{
    /** @ORM\Column(type="datetime") */
    private $created_at;
    /** @ORM\ManyToOne(targetEntity="SecondBundle\Entity\Second", fetch="EAGER") */
    private $second;

    public function getCreatedAt() : \DateTime { return $this->created_at; }
    public function getSecond()    : Second    { return $this->second; }
}

src/FirstBundle/Entity/Embeddable/Contact.php

<?php

namespace FirstBundle\Entity\Embeddable;

use Doctrine\ORM\Mapping as ORM;

/** @ORM\Embeddable */
class Contact
{
    /** @ORM\Column(type="string", length=190) */
    private $city;
    public function getCity() : string { return $this->city; }
}

src/SecondBundle/Entity/Second.php

<?php

namespace SecondBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="SecondBundle\Repository\Second")
 */
class Second
{
}

src/FirstBundle/Resources/config/mapping/first.yml

mapping:
    class: FirstBundle\Entity\First
    id_properties:
        - id
    hide_properties:
        - password
    urls:
        self: first_get_all
    relationships:
        second:
            self: we_dont_need_this

src/FirstBundle/Resources/config/mapping/contact.yml

mapping:
    class: FirstBundle\Entity\Embeddable\Contact
    id_properties: []
    hide_properties: []
    urls:
        self: we_dont_need_this

src/SecondBundle/Resources/config/mapping/second.yml

mapping:
    class: SecondBundle\Entity\Second
    id_properties:
        - id
    hide_properties:
        - password
    urls:
        self: second_get_all

After using this project like this

src/FirstBundle/Controller/First.php

<?php

namespace FirstBundle\Controller;

use NilPortugues\Api\JsonApi\JsonApiSerializer;
use NilPortugues\Symfony\JsonApiBundle\Serializer\JsonApiResponseTrait;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class First extends Controller
{
    use JsonApiResponseTrait;

    /** @var \FirstBundle\Repository\First */
    private $repository;
    /** @var JsonApiSerializer */
    private $serializer;

    public function getAllAction(ParamFetcher $paramFetcher) : Response
    {
        $all = $this->repository->findBy(
            $paramFetcher->get('search') ?? []
            , $paramFetcher->get('order') ?? []
            , $paramFetcher->get('limit')
            , $paramFetcher->get('offset')
        );

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

I get very strange response:

{
    "data": [
        {
            "attributes": {
                "created_at": {
                    "date": "2016-11-15 12:52:55.000000",
                    "timezone": "Etc/UTC",
                    "timezone_type": 3
                },
                "id": 1905572758,
            },
            "id": "1905572758",
            "relationships": {
                "contact": {
                    "data": {
                        "id": "",
                        "type": "contact"
                    }
                },
                "second": {
                    "data": {
                        "id": "1819094130",
                        "type": "second"
                    }
                }
            },
            "type": "first"
        }
    ],
    "included": [
        {
            "attributes": {
                "login": "login1",
                "password": "$2y$10$m7flUsxQoXxFIxQbSTdqsejsU2Pa9BF.Zy9JNejm/vQSSzun/w8ze",
                "registered_at": {
                    "date": "2016-11-15 10:05:03.000000",
                    "timezone": "Etc/UTC",
                    "timezone_type": 3
                }
            },
            "id": "1411924520",
            "type": "second"
        },
        {
            "attributes": {
                "login": "f12",
                "password": "$2y$10$kxxKWMt0WPYtHB.BvieatewTb69sR0R/geiIU3kq7Xy6XXuVlrpG.",
                "registered_at": {
                    "date": "2016-11-15 10:58:26.000000",
                    "timezone": "Etc/UTC",
                    "timezone_type": 3
                }
            },
            "id": "1819094130",
            "type": "second"
        }
    ],
    "jsonapi": {
        "version": "1.0"
    }
}

Questions:

  1. How to include Second entity as relationship with First entity? What i'm doin wrong? Connected with #20 i think but there is no solution provided for me.
  2. WTF is with \DateTime and created_at field? How to format this as string? Connected with #21 but not working for me )-:
  3. Why embeddable entity Contact has id field? Don't need this and doesn't have. It has stored just as contact_city field in first table. No PK exists.
  4. Why embeddable entity Contact don't have city field?
  5. Why id for First is string instead of integer (but it is in attributes).
  6. If i remove EAGER loading of relation then i got error Closures are not supported in Serializer – how to fix this?
  7. Why hide_properties is ignored for Second entity? Password must be excluded.

Thank you for your help.

Multiple mapping locations

Hi,

I am looking at using your bundle in one of my projects but would like to add a number of separate mapping files in various locations, instead of just the one.

This is particularly useful for us so that we can spilt the api into separate contexts.
e.g.
Project
--> src
---> AppBundle
----> User
----> Company
----> Location

I have pulled the code and created a fix - just wanted to know if you were interested in taking a look.
I can either create a branch and PR or just attach the changed files here if you like.

Let me know.
Thanks

Collection response + snake case / camelCase issue

Hello,

First of all, thanik you for your work.
I am currently using your bundle for my REST API.
I am currently facing an issue with 2 routes that exports the same kind of objects (user objects):

  • a GET on a list (collection of users)
  • a GET on a single user

For the list route, I use the following mapping file :

mapping:
  class: \Doctrine\Common\Collections\ArrayCollection
  alias: Collection
  hide_properties: []
  urls:
    self: admin_get_users ## @Route name

In my controller, I retrieve a list of users which I output as you described in your doc. In the response, I have :

{
  "data": {
    "type": "collection",
    "id": "",
    "attributes": [],
    "relationships": {
      "elements": [
        {
          "data": {
            "type": "user",
            "id": "1"
          }
        },
        {
          "data": {
            "type": "user",
            "id": "2"
          }
        },
        {
          "data": {
            "type": "user",
            "id": "3"
          }
        }
      ]
    }
  },
  "included": [
    {
      "type": "user",
      "id": "1",
      "attributes": {
        "username": "gbu",
        "email": "[email protected]",
        "initials": null,
        "firstName": "....",
        "surnamePrefix": null,
        "surname": "....",
        "enabled": true,
        "locked": false,
        "lastLogin": null,
        "roles": [
          "ROLE_ADMIN",
          "ROLE_USER"
        ]
      }
    },
...

I was expecting to retrieve the user objects direcly in the "data" node but it's available in the "included" node.
What configuration do I need to use to have such output ?

I also noticed another thing concerning the keys attributes. As you can see above, the attributes names in the "included" section are in camelcase (firstName, lastLogin, ...).

But when I execute the serializer on my route to retrieve a single instance of User, I get :

{
  "data": {
    "type": "user",
    "id": "1",
    "attributes": {
      "username": "gbu",
      "email": "[email protected]",
      "initials": null,
      "first_name": "...",
      "surname_prefix": null,
      "surname": "...",
      "enabled": true,
      "locked": false,
      "last_login": null
    }
  },
  "links": {
    "self": {
      "href": "/app_dev.php/admin/v1/user/1?access_token=ZmI4Nzc5YTlmOTk2Mjg3MDg4NzEwODY4N2NlNjEyOTViMTA4MTYyMjViZjAxZDY5ZWQ1Yzg0YjRmYmJjN2RhMf"
    }
  },
  "jsonapi": {
    "version": "1.0"
  }
}

As you can see, the names are transformed in snake case.
it is due to the call in JsonAPiTransformer::serialization.
It calls DataAttributesHelper::setResponseDataAttributes($this->mappings, $value) which in turn calls the method transformToValidMemberName.

But could this behavior be configurable ? Because my parser must now be aware of the snake case and the camelCase to construct my user object.

Thanks for your help.

Related Doctrine Entities fail with empty document?

When I try to serialize a doctrine that has related entities, I get a blank 500 error and have to dig through my logs to figure out what happened.

It looks like it's boiling down to a nesting max depth error:

[Thu Nov 19 11:58:08.370490 2015] [:error] [pid 18288] [client 10.0.0.70:50204] PHP Fatal error:  Maximum function nesting level of '256' reached, aborting! in Unknown on line 0

Any ideas on serializing related Doctrine entities?

Add Relationships without "include"

Hi, Nil.

Is there a way to add Relationships without adding the whole Entity to the "include" block?

And is there a way to do some kind of dynamic mapping, meaning that in one case I want to output a User entity with all its Comments, and in another case I don't want to include the Comments.

Thanks! Estigy.

DateTime object from doctrine goes to relationships on included member

When a DateTime object belongs to a top level entity it shows up as this

data: [
{
  type: "entidad1",
  id: "1",
  attributes: {
    fecha: {
      date: "2016-06-09 00:00:00",
      timezone_type: 3,
      timezone: "America/Asuncion"
    },...

But when the DateTime object is an attribute from an included entity it goes to the relationships member as this:

...
included: [
  {
    type: "entidad1",
    id: "1",
    attributes: {
      descripcion: "prueba"
    },
    relationships: {
      fecha: {
        data: [ ]
      },
    ...

Is this normal behaviour? Should I enable something for DateTime serializers to work?

Unserialization to symfony entity

Is there a way to get an entity from a json received via POST request using json api? Maybe this needs to use the mappings needed for serialization in a reverse way.

Merging this project in API Platform

Hi,

I just cam across your project and it's very interesting. In our (not stable yet) API Platform v2, we have decoupled the JSON-LD/Hydra part from the engine. Adding support for new formats (while still having all other features) is just a matter of creating a normalizer for it (what you've done in this bundle): https://github.com/api-platform/doc/blob/master/api-bundle/content-negotiation.md

I'm working on HAL support, but having JSON API support as well as other hypermedia format in a same project would be a big win both for API Platform and the Symfony ecosystem.

Are you interested for collaborating on this topic?

JsonApiResponseTrait::addHeaders behaviour

Hi,

I would like to understand what it is the behaviour for the method JsonApiResponseTrait::addHeaders ?

This method do nothing more than returning the response parameter and is named addHeaders but there is no header parameter, only the response. Is this method only here to be overrided ? Something else ?

filter the included resource in the mapping configuration

Hi, it's a pretty nice job you have done.
I just have a small issue, I don't know if there is a way to say in the mappings which relationships I want to include by default ?
It seems relationships are included by default. I would like to choose which one are included by default.

From what I understood in the json api specification, it is something we can do.

Choosing which properties to serialize?

With the symfony serializer (or JMS), you can either use groups or config to dictate which properties should be serialized.

Can we do the same thing here?

Any reason underscores and not dashes?

I notice the attribute keys get passed through this function:

    /**
     * Transforms a given string from camelCase to under_score style.
     *
     * @param string $camel
     * @param string $splitter
     *
     * @return string
     */
    public static function camelCaseToUnderscore($camel, $splitter = '_')
    {
        $camel = \preg_replace(
            '/(?!^)[[:upper:]][[:lower:]]/',
            '$0',
            \preg_replace('/(?!^)[[:upper:]]+/', $splitter.'$0', $camel)
        );

        return \strtolower($camel);
    }

However the spec seems to recommend dashes:

http://jsonapi.org/recommendations/#naming

Any thoughts? Can you support both?

setResponseMeta missing from serializedArray

In the JsonApiTransformer class when objects are being serialized there are three calls to build the response body...

function serialization(...)

$this->setResponseLinks($value, $data);
$this->setResponseMeta($data);
$this->setResponseVersion($data);

But that is missing inside the function serializedArray.

function serializedArray(...)

$this->setResponseLinks($value, $data);
$this->setResponseVersion($data);

This is causing meta properties to be missed from serialized arrays. The setResponseMeta method should be called inside serializedArray.

Symfony 4 support

Hi,
are there any plans adding Symfony 4 support? Or are there any blockers?

I may try to do this, but without tests its hard to validate the bundle behaves as expected, so it may be easier for you.

Using ramsey/uuid without calling toString gives an gives 'Array to string conversion' error

I currently use Ramsey\Uuid for UUID generation.

I get an 'Array to string conversion' error. This happens when I put the following in my mapping file:

id_properties:
- id

If you have any questions or would like to see the project, just comment. The project is a test project and doesn't contain any secret/business data, so I can safely share everything.

My entity (txt because Github doesn't accept php):
Toilet.php

My mapping file (txt because Github doesn't accept yml):
toilet.yml

Stacktrace here:
stacktrace.txt

ordering of attributes in the output

Is there anyway to sort the attribute keys before outputting them (or at least have the option to do a sort)?

The reflection does seem to pick out the properties "in order" from the Class file, however if you have a sub-class it can be difficult then to keep things strictly alphabetical, especially if there are attributes defined in both files.

How to avoid serializing compound objects?

Hi Nil. Is there a way to avoid using the top-level included member?
Here is an example:

  1. The entity Invoice has a many to one relation to Customer;
  2. The entity Invoice_detail has a many to one relation to an Invoice;
    If I'm forced to use the included member, to avoid the doctrine closure issue, then every time I request an Invoice_detail to the jsonapi I would have to include in the request the Invoice and the Customer. Is this approach the right way to use the API?

Serializer fails because of old nilportugues/php-serializer dependency

Hi,

while trying to serialize an object I ran into an issue that was fixed already here:
nilportugues/php-serializer@8489eab

This is because symfony-jsonapi relies on
"nilportugues/json-api": "^2.4"

which relies on
"nilportugues/api-transformer": "^3.0.0"

which relies on
"nilportugues/serializer": "~1.1"

I installed the package about 2 days ago. According to https://getcomposer.org/doc/articles/versions.md#tilde version 1.2 of serializer should have been installed, but it wasn't. Instead I have this in my composer.lock file (excerpt):
snip snip

{
            "name": "nilportugues/serializer",
            "version": "1.1.5",
            "source": {
                "type": "git",
                "url": "https://github.com/nilportugues/serializer.git",
                "reference": "82ede04c687336aaec5f1c58f3ec986626b05810"
            },
},
{
            "name": "nilportugues/api-transformer",
            "version": "3.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/nilportugues/php-api-transformer.git",
                "reference": "4e2743527b7deb7af5b4ef55f2ca33146e9fab65"
            },
},
{
            "name": "nilportugues/json-api",
            "version": "2.4.4",
            "source": {
                "type": "git",
                "url": "https://github.com/nilportugues/php-json-api.git",
                "reference": "87d5898dee49670df8c67c81014db3c31cf94202"
            },
},
{
            "name": "nilportugues/jsonapi-bundle",
            "version": "1.5.2",
            "source": {
                "type": "git",
                "url": "https://github.com/nilportugues/symfony-jsonapi.git",
                "reference": "c4bbdfec0bb0b402aa0057fd32d1225447988460"
            },
}

Any idea what's wrong with the dependencies?

Mapping self url does not works

I try to serialise an object but I get an error with the URL generation for the self url property.

This is my mapping:

mapping:
  class: AppBundle\User
  alias: User
  id_properties:
    - id
  urls:
    self: get_user

This is get_user route definition (which works if I call it directly):

@Route(
    path="/user/{id}",
    name="get_user",
    requirements={"id": "\d+"}
)

And this is the error I get:

Parameter "id" for route "get_user" must match "\d+" ("{id}" given) to generate a corresponding URL.

If I change the mapping to something like

# ...
  urls:
    self: route_without_parameters
# ...

I get a response but without any url referencing that route:

{
  "data": {
    "type": "user",
    "id": "1",
    "attributes": {
        ...
    }
  },
  "jsonapi": {
    "version": "1.0"
  }
}

Class name is serialized singular?

Any way to change the way the class name is serialized - e.g.

Article.php --> "type": "article"

versus

Article.php --> "type": "articles"

The examples on jsonapi.org seem to tend towards plural, although the spec is "agnostic" about it.

http://jsonapi.org/format/

Annotations

Would it be possible to have annotation support? That would make it a lot easier to write the relationship mapping, you could simply do it in your doctrine entities.

Symfony\Component\Security\Acl\Exception and Symfony 3

Attempted to load class "Exception" from namespace "Symfony\Component\Security\Acl\Exception".
Did you forget a "use" statement for "Symfony\Component\Config\Definition\Exception\Exception"?

in vendor/nilportugues/jsonapi-bundle/src/Serializer/JsonApiSerializer.php at line 103

Serializing DateTime objects?

How do I serialize a DateTime property? When I do so from a Doctrine entity, I get the following:

Notice: Undefined index: DateTime (500 Internal Server Error)

Multiple mapping locations

Hi,

I am looking at using you bundle in one of my projects but would like to add a number of separate mapping files in various locations, instead of just the one.

This is particularly useful for us so that we can spilt the api into separate contexts.
e.g.
Project
--> src
---> AppBundle
----> User
----> Company
----> Location

I have pulled the code and created a fix - just wanted to know if you were interested in taking a look.
I can either create a branch and PR or just attach the changed files here if you like.

Let me know.
Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.