定制 nilportugues/hal 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

nilportugues/hal

Composer 安装命令:

composer require nilportugues/hal

包简介

HAL+JSON & HAL+XML API transformer outputting valid API responses.

README 文档

README

Build Status Scrutinizer Code Quality SensioLabsInsight Latest Stable Version Total Downloads License Donate

  1. Installation
  2. Mapping
  3. HAL Serialization
  4. HAL Paginated Resource
  5. PSR-7 Response objects

1. Installation

Use Composer to install the package:

$ composer require nilportugues/hal

2. Mapping

Given a PHP Object, and a series of mappings, the HAL+JSON and HAL+XML API transformer will represent the given data following the https://tools.ietf.org/html/draft-kelly-json-hal-07 specification draft.

For instance, given the following piece of code, defining a Blog Post and some comments:

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

We will have to map all the involved classes. This can be done as one single array, or a series of Mapping classes.

2.1 Mapping with arrays

Mapping involved classes using arrays is done as follows:

use NilPortugues\Api\Mapping\Mapper;

$mappings = [
    [
        'class' => Post::class,
        'alias' => 'Message',
        'aliased_properties' => [
            'author' => 'author',
            'title' => 'headline',
            'content' => 'body',
        ],
        'hide_properties' => [

        ],
        'id_properties' => [
            'postId',
        ],
        'urls' => [
            // Mandatory
            'self' => 'http://example.com/posts/{postId}',
             // Optional
            'comments' => 'http://example.com/posts/{postId}/comments'
        ],
        'curies' => [
            'name' => 'example',
            'href' => "http://example.com/docs/rels/{rel}",
        ]
    ],
    [
        'class' => User::class,
        'alias' => '',
        'aliased_properties' => [],
        'hide_properties' => [],
        'id_properties' => [
            'userId',
        ],
        'urls' => [
            'self' => 'http://example.com/users/{userId}',
        ],
        'curies' => [
            'name' => 'example',
            'href' => "http://example.com/docs/rels/{rel}",
        ]
    ],
    [
        'class' => Comment::class,
        'alias' => '',
        'aliased_properties' => [],
        'hide_properties' => [],
        'id_properties' => [
            'commentId',
        ],
        'urls' => [
            'self' => 'http://example.com/comments/{commentId}',
        ],
        'curies' => [
            'name' => 'example',
            'href' => "http://example.com/docs/rels/{rel}",
        ]
    ]
];

$mapper = new Mapper($mappings);

2.2 Mapping with Mapping class

In order to map with Mapping class, you need to create a new class for each involved class.

This mapping fashion scales way better than using an array.

All Mapping classes will extend the \NilPortugues\Api\Mappings\HalMapping interface.

// PostMapping.php

class PostMapping implements \NilPortugues\Api\Mappings\HalMapping 
{
    public function getClass()
    {
        return Post::class;
    }

    public function getAlias()
    {
        return 'Message';
    }

    public function getAliasedProperties()
    {
        return [
           'author' => 'author',
           'title' => 'headline',
           'content' => 'body',
       ];
    }

    public function getHideProperties()
    {
        return [];
    }

    public function getIdProperties()
    {
        return ['postId'];
    }
   
    public function getUrls()
    {
        return [            
            'self' => 'http://example.com/posts/{postId}', // Mandatory            
            'comments' => 'http://example.com/posts/{postId}/comments' // Optional
        ];
    }

    public function getCuries()
    {
        return [
           'name' => 'example',
           'href' => "http://example.com/docs/rels/{rel}",
        ];
    }
}


// UserMapping.php

class UserMapping implements \NilPortugues\Api\Mappings\HalMapping 
{
    public function getClass()
    {
        return User::class;
    }

    public function getAlias()
    {
        return '';
    }

    public function getAliasedProperties()
    {
        return [];
    }

    public function getHideProperties()
    {
        return [];
    }

    public function getIdProperties()
    {
        return ['postId'];
    }
   
    public function getUrls()
    {
        return [            
            'self' => 'http://example.com/users/{userId}'
        ];
    }

    public function getCuries()
    {
        return [
           'name' => 'example',
           'href' => "http://example.com/docs/rels/{rel}",
        ];
    }
}


// CommentMapping.php

class CommentMapping implements \NilPortugues\Api\Mappings\HalMapping 
{
    public function getClass()
    {
        return Comment::class;
    }

    public function getAlias()
    {
        return '';
    }

    public function getAliasedProperties()
    {
        return [];
    }

    public function getHideProperties()
    {
        return [];
    }

    public function getIdProperties()
    {
        return ['commentId'];
    }
   
    public function getUrls()
    {
        return [            
            'self' => 'http://example.com/comments/{commentId}',
        ];
    }

    public function getCuries()
    {
        return [
           'name' => 'example',
           'href' => "http://example.com/docs/rels/{rel}",
        ];
    }
} 

$mappings = [
    PostMapping::class,
    UserMapping::class,
    CommentMapping::class,
];
$mapper = new Mapper($mappings);

3. HAL Serialization

Calling the transformer will output a valid HAL response using the correct formatting:

use NilPortugues\Api\Hal\JsonTransformer; 
use NilPortugues\Api\Hal\HalSerializer;
use NilPortugues\Api\Hal\Http\Message\Response;

$transformer = new JsonTransformer($mapper);
//For XML: $transformer = new XmlTransformer($mapper);

//Output transformation
$serializer = new HalSerializer($transformer);
$output = $serializer->serialize($post);

//PSR7 Response with headers and content.
$response = new Response($output);

header(
    sprintf(
        'HTTP/%s %s %s',
        $response->getProtocolVersion(),
        $response->getStatusCode(),
        $response->getReasonPhrase()
    )
);

foreach($response->getHeaders() as $header => $values) {
    header(sprintf("%s:%s\n", $header, implode(', ', $values)));
}

echo $response->getBody();

3.1 HAL+JSON

HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/hal+json

Output:

{
    "post_id": 9,
    "headline": "Hello World",
    "body": "Your first post",
    "_embedded": {
        "author": {
            "user_id": 1,
            "name": "Post Author",
            "_links": {
                "self": {
                    "href": "http://example.com/users/1"
                },
                "example:friends": {
                    "href": "http://example.com/users/1/friends"
                },
                "example:comments": {
                    "href": "http://example.com/users/1/comments"
                }
            }
        },
        "comments": [
            {
                "comment_id": 1000,
                "dates": {
                    "created_at": "2015-08-13T22:47:45+02:00",
                    "accepted_at": "2015-08-13T23:22:45+02:00"
                },
                "comment": "Have no fear, sers, your king is safe.",
                "_embedded": {
                    "user": {
                        "user_id": 2,
                        "name": "Barristan Selmy",
                        "_links": {
                            "self": {
                                "href": "http://example.com/users/2"
                            },
                            "example:friends": {
                                "href": "http://example.com/users/2/friends"
                            },
                            "example:comments": {
                                "href": "http://example.com/users/2/comments"
                            }
                        }
                    }
                },
                "_links": {
                    "example:user": {
                        "href": "http://example.com/users/2"
                    },
                    "self": {
                        "href": "http://example.com/comments/1000"
                    }
                }
            }
        ]
    },
    "_links": {
        "curies": [
            {
                "name": "example",
                "href": "http://example.com/docs/rels/{rel}",
                "templated": true
            }
        ],
        "self": {
            "href": "http://example.com/posts/9"
        },
        "example:author": {
            "href": "http://example.com/users/1"
        },
        "example:comments": {
            "href": "http://example.com/posts/9/comments"
        }
    }
}

3.2 HAL+XML

For XML output use the sample code but using the XML transformer instead:

$transformer = new XmlTransformer($mapper);

Output:

HTTP/1.1 200 OK
Cache-Control: private, max-age=0, must-revalidate
Content-type: application/hal+xml
<?xml version="1.0" encoding="UTF-8"?>
<resource href="http://example.com/posts/9">
  <post_id><![CDATA[9]]></post_id>
  <headline><![CDATA[Hello World]]></headline>
  <body><![CDATA[Your first post]]></body>
  <embedded>
    <resource href="http://example.com/users/1" rel="author">
      <user_id><![CDATA[1]]></user_id>
      <name><![CDATA[Post Author]]></name>
      <links>
        <link rel="self" href="http://example.com/users/1"/>
        <link rel="example:friends" href="http://example.com/users/1/friends"/>
        <link rel="example:comments" href="http://example.com/users/1/comments"/>
      </links>
    </resource>
    <comments>
      <resource href="http://example.com/comments/1000">
        <comment_id><![CDATA[1000]]></comment_id>
        <dates>
          <created_at><![CDATA[2015-07-18T12:13:00+00:00]]></created_at>
          <accepted_at><![CDATA[2015-07-19T00:00:00+00:00]]></accepted_at>
        </dates>
        <comment><![CDATA[Have no fear, sers, your king is safe.]]></comment>
        <embedded>
          <resource href="http://example.com/users/2" rel="user">
            <user_id><![CDATA[2]]></user_id>
            <name><![CDATA[Barristan Selmy]]></name>
            <links>
              <link rel="self" href="http://example.com/users/2"/>
              <link rel="example:friends" href="http://example.com/users/2/friends"/>
              <link rel="example:comments" href="http://example.com/users/2/comments"/>
            </links>
          </resource>
        </embedded>
        <links>
          <link rel="example:user" href="http://example.com/users/2"/>
          <link rel="self" href="http://example.com/comments/1000"/>
        </links>
      </resource>
    </comments>
  </embedded>
  <links>
    <curies>
      <link rel="resource" href="http://example.com/docs/rels/{rel}">
        <name><![CDATA[example]]></name>
        <templated><![CDATA[true]]></templated>
      </link>
    </curies>
    <link rel="self" href="http://example.com/posts/9"/>
    <link rel="example:author" href="http://example.com/users/1"/>
    <link rel="example:comments" href="http://example.com/posts/9/comments"/>
  </links>
</resource>

4. HAL Paginated Resource

A pagination object to easy the usage of this package is provided.

For both XML and JSON output, use the HalPagination object to build your paginated representation of the current resource.

Methods provided by HalPagination are as follows:

  • setSelf($self)
  • setFirst($first)
  • setPrev($prev)
  • setNext($next)
  • setLast($last)
  • setCount($count)
  • setTotal($total)
  • setEmbedded(array $embedded)

In order to use it, create a new HalPagination instance, use the setters and pass the instance to the serialize($value) method of the serializer.

Everything else will be handled by serializer itself. Easy as that!

use NilPortugues\Api\Hal\HalPagination; 
use NilPortugues\Api\Hal\HalSerializer; 
use NilPortugues\Api\Hal\JsonTransformer; 

// ...
//$objects is an array of objects, such as Post::class.
// ...
 
$page = new HalPagination();

//set the amounts
$page->setTotal(20);
$page->setCount(10);

//set the objects
$page->setEmbedded($objects);

//set up the pagination links
$page->setSelf('/post?page=1');
$page->setPrev('/post?page=1');
$page->setFirst('/post?page=1');
$page->setLast('/post?page=1');

$output = $serializer->serialize($page);

5. Response objects

The following PSR-7 Response objects providing the right headers and HTTP status codes are available.

Its use is optional and are provided as a starting point.

  • NilPortugues\Api\Hal\Http\Message\ErrorResponse($body)
  • NilPortugues\Api\Hal\Http\Message\ResourceCreatedResponse($body)
  • NilPortugues\Api\Hal\Http\Message\ResourceDeletedResponse($body)
  • NilPortugues\Api\Hal\Http\Message\ResourceNotFoundResponse($body)
  • NilPortugues\Api\Hal\Http\Message\ResourcePatchErrorResponse($body)
  • NilPortugues\Api\Hal\Http\Message\ResourcePostErrorResponse($body)
  • NilPortugues\Api\Hal\Http\Message\ResourceProcessingResponse($body)
  • NilPortugues\Api\Hal\Http\Message\ResourceUpdatedResponse($body)
  • NilPortugues\Api\Hal\Http\Message\Response($body)
  • NilPortugues\Api\Hal\Http\Message\UnsupportedActionResponse($body)

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.

nilportugues/hal 适用场景与选型建议

nilportugues/hal 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 7.68k 次下载、GitHub Stars 达 30, 最近一次更新时间为 2015 年 12 月 26 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「json」 「api」 「response」 「hal」 「serializer」 「transformer」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 nilportugues/hal 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 nilportugues/hal 我们能提供哪些服务?
定制开发 / 二次开发

基于 nilportugues/hal 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 7.68k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 30
  • 点击次数: 8
  • 依赖项目数: 2
  • 推荐数: 0

GitHub 信息

  • Stars: 30
  • Watchers: 2
  • Forks: 4
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2015-12-26