nilportugues/jsonapi-bundle

Symfony 2 & 3 JSON API Transformer Package

2.1.0 2017-07-31 09:44 UTC

This package is auto-updated.

Last update: 2024-09-06 09:03:25 UTC


README

适用于Symfony 2和Symfony 3

Scrutinizer Code Quality SensioLabsInsight Latest Stable Version Total Downloads License Donate

安装

步骤1:下载Bundle

打开命令行,进入项目目录,执行以下命令以下载此Bundle的最新稳定版本

$ composer require nilportugues/jsonapi-bundle

步骤2:启用Bundle

然后,通过将其添加到项目中的注册Bundle列表中来启用此Bundle,在项目中的app/AppKernel.php文件中

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

使用

创建映射

映射目录

映射文件应位于app/config/serializer目录。必须创建此目录。

还可以通过编辑配置文件app/config/config.yml来自定义并放置在其它位置

# app/config/config.yml

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

映射文件

JSON-API转换器通过将现有的PHP对象转换为其JSON表示来工作。对于每个对象,都需要一个映射文件。

映射文件必须放置在映射目录中。预期的映射文件格式是.yml,这将允许您重命名、隐藏和创建与所有数据相关联的链接。

例如,这里有一个相当复杂的Post对象,以演示其工作原理

$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'),
            ]
        ),
    ]
);

所需的映射文件系列

# 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

输出API响应

这非常简单,只需从服务容器中获取JsonApiSerializer的实例,并将对象传递给其serialize()方法。输出将是有效的JSON-API。

这里是一个从Doctrine仓库中获取Post对象的示例。

最后,提供了一个辅助特质,JsonApiResponseTrait,用于编写完全兼容的响应,这些响应包装了原始JSON API Transformer库提供的PSR-7 Response对象。

<?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));
    }
} 

输出

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"
    }
}

请求对象

JSON API附带一个辅助请求类,NilPortugues\Api\JsonApi\Http\Request\Request(ServerRequestInterface $request),实现了PSR-7 Request接口。使用此请求对象将提供您访问在JSON API中期望的所有交互的权限

JSON API查询参数
  • &fields[resource]=field1,field2将只显示给定资源中指定的字段。
  • &include=resource显示给定资源的关系。
  • &include=resource.resource2显示依赖于resource2的资源关系字段。
  • &sort=field1,-field2按field2降序和field1升序排序。
  • &sort=-field1,field2按field1降序和field2升序排序。
  • &page[number]将按基于页面的分页策略返回当前页的元素。
  • &page[size]将按基于页面的分页策略返回元素的总数。
  • &page[limit]将按基于偏移的分页策略返回限制。
  • &page[offset]将按基于偏移的分页策略返回偏移值。
  • &page[cursor]将按基于游标的分页策略返回游标值。
  • &filter将返回在过滤器参数中传递的数据。
NilPortugues\Api\JsonApi\Http\Request\Request

根据上述查询参数,Request实现了解析和返回已准备好的数据的辅助方法。

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() { ... }
}

响应对象(JsonApiResponseTrait)

以下JsonApiResponseTrait方法提供以返回正确的标头和可用的HTTP状态码

    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);

与NelmioApiDocBundleBundle集成

NelmioApiDocBundle 是一个非常著名的用于API文档的扩展包。与当前扩展包的集成非常简单。

以下是一个示例,遵循之前提供的 PostContoller::getPostAction() 方法

<?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));
    }
} 

以下是推荐添加到 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

质量

要在命令行中运行PHPUnit测试,请进入测试目录并执行phpunit。

本库试图遵守 PSR-1PSR-2PSR-4PSR-7

如果您发现遵守方面的疏忽,请通过 Pull Request 提交补丁。

贡献

对本包的贡献总是受欢迎的!

支持

您可以使用以下方式之一与我联系

作者

许可

代码库采用 MIT许可证 许可。