Updates
This commit is contained in:
567
vendor/league/fractal/src/Serializer/JsonApiSerializer.php
vendored
Normal file
567
vendor/league/fractal/src/Serializer/JsonApiSerializer.php
vendored
Normal file
@@ -0,0 +1,567 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the League\Fractal package.
|
||||
*
|
||||
* (c) Phil Sturgeon <me@philsturgeon.uk>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace League\Fractal\Serializer;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use League\Fractal\Pagination\PaginatorInterface;
|
||||
use League\Fractal\Resource\ResourceInterface;
|
||||
|
||||
class JsonApiSerializer extends ArraySerializer
|
||||
{
|
||||
protected $baseUrl;
|
||||
protected $rootObjects;
|
||||
|
||||
/**
|
||||
* JsonApiSerializer constructor.
|
||||
*
|
||||
* @param string $baseUrl
|
||||
*/
|
||||
public function __construct($baseUrl = null)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->rootObjects = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a collection.
|
||||
*
|
||||
* @param string $resourceKey
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function collection($resourceKey, array $data)
|
||||
{
|
||||
$resources = [];
|
||||
|
||||
foreach ($data as $resource) {
|
||||
$resources[] = $this->item($resourceKey, $resource)['data'];
|
||||
}
|
||||
|
||||
return ['data' => $resources];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an item.
|
||||
*
|
||||
* @param string $resourceKey
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function item($resourceKey, array $data)
|
||||
{
|
||||
$id = $this->getIdFromData($data);
|
||||
|
||||
$resource = [
|
||||
'data' => [
|
||||
'type' => $resourceKey,
|
||||
'id' => "$id",
|
||||
'attributes' => $data,
|
||||
],
|
||||
];
|
||||
|
||||
unset($resource['data']['attributes']['id']);
|
||||
|
||||
if ($this->shouldIncludeLinks()) {
|
||||
$resource['data']['links'] = [
|
||||
'self' => "{$this->baseUrl}/$resourceKey/$id",
|
||||
];
|
||||
}
|
||||
|
||||
return $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the paginator.
|
||||
*
|
||||
* @param PaginatorInterface $paginator
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function paginator(PaginatorInterface $paginator)
|
||||
{
|
||||
$currentPage = (int)$paginator->getCurrentPage();
|
||||
$lastPage = (int)$paginator->getLastPage();
|
||||
|
||||
$pagination = [
|
||||
'total' => (int)$paginator->getTotal(),
|
||||
'count' => (int)$paginator->getCount(),
|
||||
'per_page' => (int)$paginator->getPerPage(),
|
||||
'current_page' => $currentPage,
|
||||
'total_pages' => $lastPage,
|
||||
];
|
||||
|
||||
$pagination['links'] = [];
|
||||
|
||||
$pagination['links']['self'] = $paginator->getUrl($currentPage);
|
||||
$pagination['links']['first'] = $paginator->getUrl(1);
|
||||
|
||||
if ($currentPage > 1) {
|
||||
$pagination['links']['prev'] = $paginator->getUrl($currentPage - 1);
|
||||
}
|
||||
|
||||
if ($currentPage < $lastPage) {
|
||||
$pagination['links']['next'] = $paginator->getUrl($currentPage + 1);
|
||||
}
|
||||
|
||||
$pagination['links']['last'] = $paginator->getUrl($lastPage);
|
||||
|
||||
return ['pagination' => $pagination];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the meta.
|
||||
*
|
||||
* @param array $meta
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function meta(array $meta)
|
||||
{
|
||||
if (empty($meta)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result['meta'] = $meta;
|
||||
|
||||
if (array_key_exists('pagination', $result['meta'])) {
|
||||
$result['links'] = $result['meta']['pagination']['links'];
|
||||
unset($result['meta']['pagination']['links']);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function null()
|
||||
{
|
||||
return [
|
||||
'data' => null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the included data.
|
||||
*
|
||||
* @param ResourceInterface $resource
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function includedData(ResourceInterface $resource, array $data)
|
||||
{
|
||||
list($serializedData, $linkedIds) = $this->pullOutNestedIncludedData($data);
|
||||
|
||||
foreach ($data as $value) {
|
||||
foreach ($value as $includeObject) {
|
||||
if ($this->isNull($includeObject) || $this->isEmpty($includeObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$includeObjects = $this->createIncludeObjects($includeObject);
|
||||
|
||||
foreach ($includeObjects as $object) {
|
||||
$includeType = $object['type'];
|
||||
$includeId = $object['id'];
|
||||
$cacheKey = "$includeType:$includeId";
|
||||
if (!array_key_exists($cacheKey, $linkedIds)) {
|
||||
$serializedData[] = $object;
|
||||
$linkedIds[$cacheKey] = $object;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return empty($serializedData) ? [] : ['included' => $serializedData];
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if includes should be side-loaded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function sideloadIncludes()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $includedData
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function injectData($data, $includedData)
|
||||
{
|
||||
$relationships = $this->parseRelationships($includedData);
|
||||
|
||||
if (!empty($relationships)) {
|
||||
$data = $this->fillRelationships($data, $relationships);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to manipulate the final sideloaded includes.
|
||||
* The JSON API specification does not allow the root object to be included
|
||||
* into the sideloaded `included`-array. We have to make sure it is
|
||||
* filtered out, in case some object links to the root object in a
|
||||
* relationship.
|
||||
*
|
||||
* @param array $includedData
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filterIncludes($includedData, $data)
|
||||
{
|
||||
if (!isset($includedData['included'])) {
|
||||
return $includedData;
|
||||
}
|
||||
|
||||
// Create the RootObjects
|
||||
$this->createRootObjects($data);
|
||||
|
||||
// Filter out the root objects
|
||||
$filteredIncludes = array_filter($includedData['included'], [$this, 'filterRootObject']);
|
||||
|
||||
// Reset array indizes
|
||||
$includedData['included'] = array_merge([], $filteredIncludes);
|
||||
|
||||
return $includedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter function to delete root objects from array.
|
||||
*
|
||||
* @param array $object
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function filterRootObject($object)
|
||||
{
|
||||
return !$this->isRootObject($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the root objects of the JSON API tree.
|
||||
*
|
||||
* @param array $objects
|
||||
*/
|
||||
protected function setRootObjects(array $objects = [])
|
||||
{
|
||||
$this->rootObjects = array_map(function ($object) {
|
||||
return "{$object['type']}:{$object['id']}";
|
||||
}, $objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an object is a root object of the JSON API tree.
|
||||
*
|
||||
* @param array $object
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isRootObject($object)
|
||||
{
|
||||
$objectKey = "{$object['type']}:{$object['id']}";
|
||||
return in_array($objectKey, $this->rootObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isCollection($data)
|
||||
{
|
||||
return array_key_exists('data', $data) &&
|
||||
array_key_exists(0, $data['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isNull($data)
|
||||
{
|
||||
return array_key_exists('data', $data) && $data['data'] === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isEmpty($data)
|
||||
{
|
||||
return array_key_exists('data', $data) && $data['data'] === [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param array $relationships
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function fillRelationships($data, $relationships)
|
||||
{
|
||||
if ($this->isCollection($data)) {
|
||||
foreach ($relationships as $key => $relationship) {
|
||||
$data = $this->fillRelationshipAsCollection($data, $relationship, $key);
|
||||
}
|
||||
} else { // Single resource
|
||||
foreach ($relationships as $key => $relationship) {
|
||||
$data = $this->fillRelationshipAsSingleResource($data, $relationship, $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $includedData
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parseRelationships($includedData)
|
||||
{
|
||||
$relationships = [];
|
||||
|
||||
foreach ($includedData as $key => $inclusion) {
|
||||
foreach ($inclusion as $includeKey => $includeObject) {
|
||||
$relationships = $this->buildRelationships($includeKey, $relationships, $includeObject, $key);
|
||||
}
|
||||
}
|
||||
|
||||
return $relationships;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getIdFromData(array $data)
|
||||
{
|
||||
if (!array_key_exists('id', $data)) {
|
||||
throw new InvalidArgumentException(
|
||||
'JSON API resource objects MUST have a valid id'
|
||||
);
|
||||
}
|
||||
return $data['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep all sideloaded inclusion data on the top level.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function pullOutNestedIncludedData(array $data)
|
||||
{
|
||||
$includedData = [];
|
||||
$linkedIds = [];
|
||||
|
||||
foreach ($data as $value) {
|
||||
foreach ($value as $includeObject) {
|
||||
if (isset($includeObject['included'])) {
|
||||
foreach ($includeObject['included'] as $object) {
|
||||
$includeType = $object['type'];
|
||||
$includeId = $object['id'];
|
||||
$cacheKey = "$includeType:$includeId";
|
||||
|
||||
if (!array_key_exists($cacheKey, $linkedIds)) {
|
||||
$includedData[] = $object;
|
||||
$linkedIds[$cacheKey] = $object;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [$includedData, $linkedIds];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the serializer should include `links` for resource objects.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldIncludeLinks()
|
||||
{
|
||||
return $this->baseUrl !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the objects are part of a collection or not
|
||||
*
|
||||
* @param $includeObject
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function createIncludeObjects($includeObject)
|
||||
{
|
||||
if ($this->isCollection($includeObject)) {
|
||||
$includeObjects = $includeObject['data'];
|
||||
|
||||
return $includeObjects;
|
||||
} else {
|
||||
$includeObjects = [$includeObject['data']];
|
||||
|
||||
return $includeObjects;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the RootObjects, either as collection or not.
|
||||
*
|
||||
* @param $data
|
||||
*/
|
||||
private function createRootObjects($data)
|
||||
{
|
||||
if ($this->isCollection($data)) {
|
||||
$this->setRootObjects($data['data']);
|
||||
} else {
|
||||
$this->setRootObjects([$data['data']]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loops over the relationships of the provided data and formats it
|
||||
*
|
||||
* @param $data
|
||||
* @param $relationship
|
||||
* @param $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function fillRelationshipAsCollection($data, $relationship, $key)
|
||||
{
|
||||
foreach ($relationship as $index => $relationshipData) {
|
||||
$data['data'][$index]['relationships'][$key] = $relationshipData;
|
||||
|
||||
if ($this->shouldIncludeLinks()) {
|
||||
$data['data'][$index]['relationships'][$key] = array_merge([
|
||||
'links' => [
|
||||
'self' => "{$this->baseUrl}/{$data['data'][$index]['type']}/{$data['data'][$index]['id']}/relationships/$key",
|
||||
'related' => "{$this->baseUrl}/{$data['data'][$index]['type']}/{$data['data'][$index]['id']}/$key",
|
||||
],
|
||||
], $data['data'][$index]['relationships'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @param $relationship
|
||||
* @param $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function fillRelationshipAsSingleResource($data, $relationship, $key)
|
||||
{
|
||||
$data['data']['relationships'][$key] = $relationship[0];
|
||||
|
||||
if ($this->shouldIncludeLinks()) {
|
||||
$data['data']['relationships'][$key] = array_merge([
|
||||
'links' => [
|
||||
'self' => "{$this->baseUrl}/{$data['data']['type']}/{$data['data']['id']}/relationships/$key",
|
||||
'related' => "{$this->baseUrl}/{$data['data']['type']}/{$data['data']['id']}/$key",
|
||||
],
|
||||
], $data['data']['relationships'][$key]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $includeKey
|
||||
* @param $relationships
|
||||
* @param $includeObject
|
||||
* @param $key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function buildRelationships($includeKey, $relationships, $includeObject, $key)
|
||||
{
|
||||
$relationships = $this->addIncludekeyToRelationsIfNotSet($includeKey, $relationships);
|
||||
|
||||
if ($this->isNull($includeObject)) {
|
||||
$relationship = $this->null();
|
||||
} elseif ($this->isEmpty($includeObject)) {
|
||||
$relationship = [
|
||||
'data' => [],
|
||||
];
|
||||
} elseif ($this->isCollection($includeObject)) {
|
||||
$relationship = ['data' => []];
|
||||
|
||||
$relationship = $this->addIncludedDataToRelationship($includeObject, $relationship);
|
||||
} else {
|
||||
$relationship = [
|
||||
'data' => [
|
||||
'type' => $includeObject['data']['type'],
|
||||
'id' => $includeObject['data']['id'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$relationships[$includeKey][$key] = $relationship;
|
||||
|
||||
return $relationships;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $includeKey
|
||||
* @param $relationships
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function addIncludekeyToRelationsIfNotSet($includeKey, $relationships)
|
||||
{
|
||||
if (!array_key_exists($includeKey, $relationships)) {
|
||||
$relationships[$includeKey] = [];
|
||||
return $relationships;
|
||||
}
|
||||
|
||||
return $relationships;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $includeObject
|
||||
* @param $relationship
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function addIncludedDataToRelationship($includeObject, $relationship)
|
||||
{
|
||||
foreach ($includeObject['data'] as $object) {
|
||||
$relationship['data'][] = [
|
||||
'type' => $object['type'],
|
||||
'id' => $object['id'],
|
||||
];
|
||||
}
|
||||
|
||||
return $relationship;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user