swisnl/jsonapi

此包已被废弃且不再维护。作者建议使用 swisnl/json-api-client 包代替。

一个将远程 JSON:API 资源映射到 Eloquent 类型和集合的 PHP 包。

2.4.0 2024-05-15 07:16 UTC

README

PHP from Packagist Latest Version on Packagist Software License Buy us a tree Build Status Scrutinizer Coverage Scrutinizer Code Quality Made by SWIS

一个将远程 JSON:API 资源映射到 Eloquent 类型和集合的 PHP 包。

💡 在开始之前,请注意,这个库只能用于 JSON:API 资源,并需要一些关于该规范的基本知识。如果您不熟悉 {json:api},请阅读 Björn Brala 的优秀博客,快速了解介绍。

安装

ℹ️ 使用 Laravel?请查看 swisnl/json-api-client-laravel 以轻松集成 Laravel。

composer require swisnl/json-api-client

注意。在安装此包之前,请确保您已安装 PSR-18 HTTP 客户端和 PSR-17 HTTP 工厂,或者同时安装它们,例如 composer require swisnl/json-api-client guzzlehttp/guzzle:^7.3

HTTP 客户端

借助 PSR-18 HTTP 客户端PSR-17 HTTP 工厂,我们与任何 HTTP 消息客户端解耦。这需要额外的包提供 psr/http-client-implementationpsr/http-factory-implementation。例如,要使用 Guzzle 7,只需添加 guzzlehttp/guzzle

composer require guzzlehttp/guzzle:^7.3

如果您想使用自己的 HTTP 客户端或使用特定的配置选项,请参阅 HTTP 客户端

入门

您可以直接创建 DocumentClient 的实例并在您的类中使用它。或者,您可以创建一个 repository

use Swis\JsonApi\Client\DocumentClient;

$client = DocumentClient::create();
$document = $client->get('https://cms.contentacms.io/api/recipes');

/** @var \Swis\JsonApi\Client\Collection&\Swis\JsonApi\Client\Item[] $collection */
$collection = $document->getData();

foreach ($collection as $item) {
  // Do stuff with the items
}

项目

默认情况下,所有项目都是 \Swis\JsonApi\Client\Item 的实例。该 Item 提供了一个类似 Laravel Eloquent 的基础类。

您可以通过扩展 \Swis\JsonApi\Client\Item 或自己实现 \Swis\JsonApi\Client\Interfaces\ItemInterface 来定义自己的模型。如果您想定义隐藏属性、转换或 get/set 修改器等,这可能很有用。如果您使用自定义模型,您必须使用 TypeMapper 注册它们。

关系

此包实现了类似 Laravel Eloquent 的关系。这些关系提供了一个流畅的接口来检索相关项。目前有四种关系可用

  • HasOneRelation
  • HasManyRelation
  • MorphToRelation
  • MorphToManyRelation

请参阅以下示例以了解如何定义关系

use Swis\JsonApi\Client\Item;

class AuthorItem extends Item
{
    protected $type = 'author';

    public function blogs()
    {
        return $this->hasMany(BlogItem::class);
    }
}

class BlogItem extends Item
{
    protected $type = 'blog';

    public function author()
    {
        return $this->hasOne(AuthorItem::class);
    }
}

命名支持

应使用 camelCase 方法定义关系。然后可以通过 camelCase 或 snake_case 的魔法属性访问相关项,或使用定义关系时使用的显式名称。

集合

此包使用 Laravel Collections 作为项目数组的包装器。

链接

所有可以具有链接的对象(即文档、错误、项目和相关关系)使用Concerns/HasLinks,因此具有一个返回Links实例的getLinks方法。这是一个简单的类似数组的对象,其中的键值对是Link实例或null

示例

给定以下JSON

{
	"links": {
		"self": "http://example.com/articles"
	},
	"data": [{
		"type": "articles",
		"id": "1",
		"attributes": {
			"title": "JSON:API paints my bikeshed!"
		},
		"relationships": {
			"author": {
				"data": {
					"type": "people",
					"id": "9"
				},
				"links": {
					"self": "http://example.com/articles/1/author"
				}
			}
		},
		"links": {
			"self": "http://example.com/articles/1"
		}
	}]
}

您可以通过这种方式获取链接

/** @var $document \Swis\JsonApi\Client\Document */

// Document links
$links = $document->getLinks();
echo $links->self->getHref(); // http://example.com/articles

// Item links
$links = $document->getData()->getLinks();
echo $links->self->getHref(); // http://example.com/articles/1

// Relationship links
$links = $document->getData()->author()->getLinks();
echo $links->self->getHref(); // http://example.com/articles/1/author

元数据

所有可以具有元信息对象(即文档、错误、项目、jsonapi、链接和相关关系)使用Concerns/HasMeta,因此具有一个返回Meta实例的getMeta方法。这是一个简单的类似数组的对象,包含键值对。

示例

给定以下JSON

{
	"links": {
		"self": {
			"href": "http://example.com/articles/1",
			"meta": {
				"foo": "bar"
			}
		}
	},
	"data": {
		"type": "articles",
		"id": "1",
		"attributes": {
			"title": "JSON:API paints my bikeshed!"
		},
		"relationships": {
			"author": {
				"data": {
					"type": "people",
					"id": "9"
				},
				"meta": {
					"written_at": "2019-07-16T13:47:26"
				}
			}
		},
		"meta": {
			"copyright": "Copyright 2015 Example Corp."
		}
	},
	"meta": {
		"request_id": "a77ab2b4-7132-4782-8b5e-d94ebaff6e13"
	}
}

您可以通过这种方式获取元数据

/** @var $document \Swis\JsonApi\Client\Document */

// Document meta
$meta = $document->getMeta();
echo $meta->request_id; // a77ab2b4-7132-4782-8b5e-d94ebaff6e13

// Link meta
$meta = $document->getLinks()->self->getMeta();
echo $meta->foo; // bar

// Item meta
$meta = $document->getData()->getMeta();
echo $meta->copyright; // Copyright 2015 Example Corp.

// Relationship meta
$meta = $document->getData()->author()->getMeta();
echo $meta->written_at; // 2019-07-16T13:47:26

类型映射器

所有自定义模型都必须在TypeMapper中进行注册。正如其名,TypeMapper将JSON:API类型映射到自定义项目

仓库

为了方便,此包包括一个基本的仓库,其中包含几个用于处理资源的方法。您可以根据\Swis\JsonApi\Client\Repository创建您使用的每个端点的仓库。然后,此仓库将使用所有操作的标准化CRUD端点。

class BlogRepository extends \Swis\JsonApi\Client\Repository
{
    protected $endpoint = 'blogs';
}

上述仓库将包含所有CRUD操作的方方法。如果您正在与只读API一起工作,并且不想执行所有操作,您可以扩展\Swis\JsonApi\Client\BaseRepository并仅包括所需的操作/特性。

use Swis\JsonApi\Client\Actions\FetchMany;
use Swis\JsonApi\Client\Actions\FetchOne;

class BlogRepository extends \Swis\JsonApi\Client\BaseRepository
{
    use FetchMany;
    use FetchOne;
    
    protected $endpoint = 'blogs';
}

如果此仓库(模式)不符合您的需求,您可以使用此包提供的客户端创建自己的实现。

请求参数

仓库提供的所有方法都接受额外的参数,这些参数将被附加到URL上。这可以用于添加包含和/或分页参数。

$repository = new BlogRepository();
$repository->all(['include' => 'author', 'page' => ['limit' => 15, 'offset' => 0]]);

项目填充器

ItemHydrator可用于使用具有属性的关联数组填充/填充项目及其关系。如果您想使用请求的POST数据填充项目,这很有用。

$typeMapper = new TypeMapper();
$itemHydrator = new ItemHydrator($typeMapper);
$blogRepository = new BlogRepository(DocumentClient::create($typeMapper), new DocumentFactory());

$item = $itemHydrator->hydrate(
    $typeMapper->getMapping('blog'),
    request()->all(['title', 'author', 'date', 'content', 'tags']),
    request()->id
);
$blogRepository->save($item);

关系

ItemHydrator还会填充(嵌套)关系。必须显式在项目中的$availableRelations数组中列出关系,以便进行填充。如果我们取上述示例,我们可以使用以下属性数组来填充一个新的博客项目

$attributes = [
    'title'   => 'Introduction to JSON:API',
    'author'  => [
        'id'       => 'f1a775ef-9407-40ba-93ff-7bd737888dc6',
        'name'     => 'Björn Brala',
        'homepage' => 'https://github.com/bbrala',
    ],
    'co-author' => null,
    'date'    => '2018-12-02 15:26:32',
    'content' => 'JSON:API was originally drafted in May 2013 by Yehuda Katz...',
    'media' => [],
    'tags'    => [
        1,
        15,
        56,
    ],
];
$itemDocument = $itemHydrator->hydrate($typeMapper->getMapping('blog'), $attributes);

echo json_encode($itemDocument, JSON_PRETTY_PRINT);

{
    "data": {
        "type": "blog",
        "attributes": {
            "title": "Introduction to JSON:API",
            "date": "2018-12-02 15:26:32",
            "content": "JSON:API was originally drafted in May 2013 by Yehuda Katz..."
        },
        "relationships": {
            "author": {
                "data": {
                    "type": "author",
                    "id": "f1a775ef-9407-40ba-93ff-7bd737888dc6"
                }
            },
            "co-author": {
                "data": null
            },
            "media": {
                "data": []
            },
            "tags": {
                "data": [{
                    "type": "tag",
                    "id": "1"
                }, {
                    "type": "tag",
                    "id": "15"
                }, {
                    "type": "tag",
                    "id": "56"
                }]
            }
        }
    },
    "included": [{
        "type": "author",
        "id": "f1a775ef-9407-40ba-93ff-7bd737888dc6",
        "attributes": {
            "name": "Björn Brala",
            "homepage": "https://github.com/bbrala"
        }
    }]
}

如示例所示,关系可以通过id或通过包含id和更多属性的关联数组进行填充。如果项目使用关联数组进行填充,除非在关系上调用setOmitIncluded(true),否则它将包含在结果JSON中。您可以通过为单数关系传递null或为复数关系传递空数组来取消关系。

注意。Morph关系需要数据中存在'type'属性,以便知道应创建哪种类型的项目。

处理错误

请求可能会因多个原因而失败,处理方式取决于发生了什么。如果DocumentClient遇到错误,基本有三种选择。

非2xx请求且没有主体

如果响应没有成功的状态码(2xx)并且没有主体,则DocumentClient(因此也是Repository)将返回一个InvalidResponseDocument实例。

非2xx请求且有无效的JSON:API主体

如果响应没有成功的状态码(2xx)并且有主体,它将被解析为JSON:API文档。如果响应不能解析为该文档,将抛出ValidationException

非2xx请求具有有效的JSON:API正文

如果响应没有成功的状态码(2xx)但有正文,则将其解析为JSON:API文档。在这种情况下,DocumentClient(因此也是Repository)将返回一个Document实例。该文档包含响应中的错误,假设服务器响应了错误。

检查错误

根据上述规则,您可以这样检查错误

$document = $repository->all();

if ($document instanceof InvalidResponseDocument || $document->hasErrors()) {
    // do something with errors
}

客户端

此包提供两个客户端:DocumentClientClient

DocumentClient

这是您通常会使用的客户端,例如,存储库在内部使用此客户端。根据JSON:API规范,所有请求和响应都是文档。因此,此客户端始终在发布数据时期望一个\Swis\JsonApi\Client\Interfaces\DocumentInterface作为输入,并且始终返回此相同接口。这可能是一个普通的Document,当没有数据时,一个ItemDocument用于一个项目,一个CollectionDocument用于一个集合,或者当服务器响应非2xx响应时,一个InvalidResponseDocument

DocumentClient在内部遵循以下步骤

  1. 使用您的HTTP客户端发送请求;
  2. 使用ResponseParser解析和验证响应;
  3. 创建正确的文档实例;
  4. 通过使用与TypeMapper注册的项目模型或一个\Swis\JsonApi\Client\Item作为后备来为每个项目添加水化;
  5. 为所有关系添加水化;
  6. 将元数据添加到文档中,例如错误链接

Client

这是一个更底层的客户端,例如,可以用于发布二进制数据,如图像。它可以接受您的请求工厂接受的任何输入数据,并返回原始的\Psr\Http\Message\ResponseInterface。它不会解析、验证响应或为项目添加水化!

DocumentFactory

当创建或更新资源时,DocumentClient需要ItemDocumentInterface实例。可以通过使用DataInterface实例来轻松创建此类文档,并将其提供给DocumentFactory。这可以是一个ItemInterface,通常由ItemHydrator创建,或者一个Collection

HTTP客户端

默认情况下,Client使用php-http/discovery查找可用的HTTP客户端、请求工厂和流工厂,因此您无需自己设置。您也可以指定自己的HTTP客户端、请求工厂或流工厂。这是向HTTP客户端添加额外选项或为测试注册模拟HTTP客户端的绝佳方式

if (app()->environment('testing')) {
    $httpClient = new \Swis\Http\Fixture\Client(
        new \Swis\Http\Fixture\ResponseBuilder('/path/to/fixtures')
    );
} else {
    $httpClient = new \GuzzleHttp\Client(
        [
            'http_errors' => false,
            'timeout' => 2,
        ]
    );
}

$typeMapper = new TypeMapper();
$client = DocumentClient::create($typeMapper, $httpClient);
$document = $client->get('https://cms.contentacms.io/api/recipes');

注意。此示例在测试环境中使用我们的swisnl/php-http-fixture-client。此包允许您轻松使用静态固定值模拟请求。绝对值得一试!

高级使用

如果您不喜欢使用提供的存储库或客户端,您也可以使用Parsers\ResponseParserParser\DocumentParser分别使用\Psr\Http\Message\ResponseInterface或简单的json字符串进行解析。

变更日志

有关最近更改的更多信息,请参阅CHANGELOG

测试

composer test

贡献

请参阅CONTRIBUTINGCODE_OF_CONDUCT以获取详细信息。

安全

如果您发现任何与安全相关的问题,请通过电子邮件security@swis.nl而不是使用问题跟踪器。

许可证

本软件采用MIT许可协议。请参阅许可文件以获取更多信息。

本软件包为Treeware。如果您在生产中使用它,我们希望您为世界买一棵树以感谢我们的工作。通过为Treeware森林做出贡献,您将为当地家庭创造就业机会并恢复野生动物栖息地。

SWIS ❤️ 开源

SWIS是来自荷兰莱顿的网页代理机构。我们热爱与开源软件合作。