2017-01-10 21:13:44 -05:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
/**
|
2017-02-15 16:13:32 -05:00
|
|
|
* Hummingbird Anime List Client
|
2017-01-10 21:13:44 -05:00
|
|
|
*
|
2018-08-22 13:48:27 -04:00
|
|
|
* An API client for Kitsu to manage anime and manga watch lists
|
2017-01-10 21:13:44 -05:00
|
|
|
*
|
2020-03-11 15:15:05 -04:00
|
|
|
* PHP version 7.3
|
2017-01-10 21:13:44 -05:00
|
|
|
*
|
2017-02-15 16:13:32 -05:00
|
|
|
* @package HummingbirdAnimeClient
|
2017-01-10 21:13:44 -05:00
|
|
|
* @author Timothy J. Warren <tim@timshomepage.net>
|
2020-01-08 15:39:49 -05:00
|
|
|
* @copyright 2015 - 2020 Timothy J. Warren
|
2017-01-10 21:13:44 -05:00
|
|
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
2019-12-06 09:16:35 -05:00
|
|
|
* @version 4.2
|
2017-03-07 20:53:58 -05:00
|
|
|
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
2017-01-11 10:34:24 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
namespace Aviat\AnimeClient\API;
|
2017-01-10 21:13:44 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Class encapsulating Json API data structure for a request or response
|
|
|
|
*/
|
2018-08-08 10:12:45 -04:00
|
|
|
final class JsonAPI {
|
2017-01-10 21:13:44 -05:00
|
|
|
|
2018-11-09 10:38:35 -05:00
|
|
|
/*
|
2017-01-10 21:13:44 -05:00
|
|
|
* Basic structure is generally like so:
|
2017-02-17 10:55:17 -05:00
|
|
|
* [
|
2017-01-10 21:13:44 -05:00
|
|
|
* 'id' => '12016665',
|
|
|
|
* 'type' => 'libraryEntries',
|
|
|
|
* 'links' => [
|
|
|
|
* 'self' => 'https://kitsu.io/api/edge/library-entries/13016665'
|
|
|
|
* ],
|
|
|
|
* 'attributes' => [
|
|
|
|
*
|
|
|
|
* ]
|
|
|
|
* ]
|
|
|
|
*/
|
2017-03-31 13:37:53 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Inline all included data
|
|
|
|
*
|
|
|
|
* @param array $data - The raw JsonAPI response data
|
2018-01-16 14:58:07 -05:00
|
|
|
* @return array
|
2017-03-31 13:37:53 -04:00
|
|
|
*/
|
|
|
|
public static function organizeData(array $data): array
|
|
|
|
{
|
|
|
|
// relationships that have singular data
|
|
|
|
$singular = [
|
|
|
|
'waifu'
|
|
|
|
];
|
|
|
|
|
|
|
|
// Reorganize included data
|
2018-01-16 14:58:07 -05:00
|
|
|
$included = array_key_exists('included', $data)
|
2017-04-05 13:01:51 -04:00
|
|
|
? static::organizeIncluded($data['included'])
|
|
|
|
: [];
|
2017-03-31 13:37:53 -04:00
|
|
|
|
|
|
|
// Inline organized data
|
2017-04-03 16:53:04 -04:00
|
|
|
foreach($data['data'] as $i => &$item)
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
2018-10-19 09:30:27 -04:00
|
|
|
if ( ! is_array($item))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-03-31 13:37:53 -04:00
|
|
|
if (array_key_exists('relationships', $item))
|
|
|
|
{
|
|
|
|
foreach($item['relationships'] as $relType => $props)
|
|
|
|
{
|
|
|
|
|
|
|
|
if (array_keys($props) === ['links'])
|
|
|
|
{
|
2017-04-03 16:53:04 -04:00
|
|
|
unset($item['relationships'][$relType]);
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2017-04-03 16:53:04 -04:00
|
|
|
if (empty($item['relationships']))
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
2017-04-03 16:53:04 -04:00
|
|
|
unset($item['relationships']);
|
2017-03-31 13:37:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (array_key_exists('links', $props))
|
|
|
|
{
|
2017-04-03 16:53:04 -04:00
|
|
|
unset($item['relationships'][$relType]['links']);
|
2017-03-31 13:37:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (array_key_exists('data', $props))
|
|
|
|
{
|
|
|
|
if (empty($props['data']))
|
|
|
|
{
|
2017-04-03 16:53:04 -04:00
|
|
|
unset($item['relationships'][$relType]['data']);
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2017-04-03 16:53:04 -04:00
|
|
|
if (empty($item['relationships'][$relType]))
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
2017-04-03 16:53:04 -04:00
|
|
|
unset($item['relationships'][$relType]);
|
2017-03-31 13:37:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
2018-10-26 13:08:45 -04:00
|
|
|
|
2017-03-31 13:37:53 -04:00
|
|
|
// Single data item
|
2018-10-26 13:08:45 -04:00
|
|
|
if (array_key_exists('id', $props['data']))
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
|
|
|
$idKey = $props['data']['id'];
|
2018-10-26 13:08:45 -04:00
|
|
|
$dataType = $props['data']['type'];
|
2017-04-03 16:53:04 -04:00
|
|
|
$relationship =& $item['relationships'][$relType];
|
2017-03-31 13:37:53 -04:00
|
|
|
unset($relationship['data']);
|
|
|
|
|
2018-10-26 13:08:45 -04:00
|
|
|
if (\in_array($relType, $singular, TRUE))
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
2018-10-26 13:08:45 -04:00
|
|
|
$relationship = $included[$dataType][$idKey];
|
2017-03-31 13:37:53 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-10-26 13:08:45 -04:00
|
|
|
if ($relType === $dataType)
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
2018-10-26 13:08:45 -04:00
|
|
|
$relationship[$idKey] = $included[$dataType][$idKey];
|
2017-03-31 13:37:53 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-10-26 13:08:45 -04:00
|
|
|
$relationship[$dataType][$idKey] = $included[$dataType][$idKey];
|
2017-03-31 13:37:53 -04:00
|
|
|
}
|
|
|
|
// Multiple data items
|
|
|
|
else
|
|
|
|
{
|
|
|
|
foreach($props['data'] as $j => $datum)
|
|
|
|
{
|
|
|
|
$idKey = $props['data'][$j]['id'];
|
2018-10-26 13:08:45 -04:00
|
|
|
$dataType = $props['data'][$j]['type'];
|
2017-04-03 16:53:04 -04:00
|
|
|
$relationship =& $item['relationships'][$relType];
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2018-10-26 13:08:45 -04:00
|
|
|
if ($relType === $dataType)
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
2018-10-26 13:08:45 -04:00
|
|
|
$relationship[$idKey] = $included[$dataType][$idKey];
|
2017-03-31 13:37:53 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-10-26 13:08:45 -04:00
|
|
|
$relationship[$dataType][$idKey][$j] = $included[$dataType][$idKey];
|
2017-03-31 13:37:53 -04:00
|
|
|
}
|
2018-11-01 22:15:20 -04:00
|
|
|
|
|
|
|
unset($item['relationships'][$relType]['data']);
|
2017-03-31 13:37:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-09 14:34:23 -05:00
|
|
|
unset($item);
|
|
|
|
|
2017-04-05 13:01:51 -04:00
|
|
|
$data['data']['included'] = $included;
|
|
|
|
|
2017-03-31 13:37:53 -04:00
|
|
|
return $data['data'];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restructure included data to make it simpler to inline
|
|
|
|
*
|
|
|
|
* @param array $included
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function organizeIncluded(array $included): array
|
|
|
|
{
|
|
|
|
$organized = [];
|
|
|
|
|
|
|
|
// First pass, create [ type => items[] ] structure
|
|
|
|
foreach($included as &$item)
|
|
|
|
{
|
|
|
|
$type = $item['type'];
|
|
|
|
$id = $item['id'];
|
|
|
|
$organized[$type] = $organized[$type] ?? [];
|
|
|
|
$newItem = [];
|
|
|
|
|
|
|
|
foreach(['attributes', 'relationships'] as $key)
|
|
|
|
{
|
|
|
|
if (array_key_exists($key, $item))
|
|
|
|
{
|
|
|
|
// Remove 'links' type relationships
|
|
|
|
if ($key === 'relationships')
|
|
|
|
{
|
|
|
|
foreach($item['relationships'] as $relType => $props)
|
|
|
|
{
|
|
|
|
if (array_keys($props) === ['links'])
|
|
|
|
{
|
|
|
|
unset($item['relationships'][$relType]);
|
|
|
|
if (empty($item['relationships']))
|
|
|
|
{
|
|
|
|
continue 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$newItem[$key] = $item[$key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$organized[$type][$id] = $newItem;
|
|
|
|
}
|
2019-12-09 14:34:23 -05:00
|
|
|
unset($item);
|
2017-03-31 13:37:53 -04:00
|
|
|
|
|
|
|
// Second pass, go through and fill missing relationships in the first pass
|
|
|
|
foreach($organized as $type => $items)
|
|
|
|
{
|
|
|
|
foreach($items as $id => $item)
|
|
|
|
{
|
2018-10-26 13:08:45 -04:00
|
|
|
if (array_key_exists('relationships', $item) && \is_array($item['relationships']))
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
|
|
|
foreach($item['relationships'] as $relType => $props)
|
|
|
|
{
|
2018-10-26 13:08:45 -04:00
|
|
|
if (array_key_exists('data', $props) && \is_array($props['data']) && array_key_exists('id', $props['data']))
|
2017-03-31 13:37:53 -04:00
|
|
|
{
|
2018-10-26 13:08:45 -04:00
|
|
|
$idKey = $props['data']['id'];
|
|
|
|
$dataType = $props['data']['type'];
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2018-10-26 13:08:45 -04:00
|
|
|
$relationship =& $organized[$type][$id]['relationships'][$relType];
|
2018-11-09 10:38:35 -05:00
|
|
|
unset($relationship['links'], $relationship['data']);
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2018-10-26 13:08:45 -04:00
|
|
|
if ($relType === $dataType)
|
|
|
|
{
|
|
|
|
$relationship[$idKey] = $included[$dataType][$idKey];
|
|
|
|
continue;
|
|
|
|
}
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2018-11-01 22:15:20 -04:00
|
|
|
if ( ! array_key_exists($dataType, $organized))
|
|
|
|
{
|
|
|
|
$organized[$dataType] = [];
|
|
|
|
}
|
|
|
|
|
2018-10-26 13:08:45 -04:00
|
|
|
if (array_key_exists($idKey, $organized[$dataType]))
|
|
|
|
{
|
|
|
|
$relationship[$dataType][$idKey] = $organized[$dataType][$idKey];
|
2017-03-31 13:37:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $organized;
|
|
|
|
}
|
|
|
|
|
2017-01-12 15:41:20 -05:00
|
|
|
/**
|
|
|
|
* Take organized includes and inline them, where applicable
|
|
|
|
*
|
|
|
|
* @param array $included
|
|
|
|
* @param string $key The key of the include to inline the other included values into
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function inlineIncludedRelationships(array $included, string $key): array
|
|
|
|
{
|
|
|
|
$inlined = [
|
|
|
|
$key => []
|
|
|
|
];
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2017-01-12 15:41:20 -05:00
|
|
|
foreach ($included[$key] as $itemId => $item)
|
|
|
|
{
|
|
|
|
// Duplicate the item for the output
|
|
|
|
$inlined[$key][$itemId] = $item;
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2017-01-12 15:41:20 -05:00
|
|
|
foreach($item['relationships'] as $type => $ids)
|
2018-10-29 10:07:20 -04:00
|
|
|
{
|
2017-01-12 15:41:20 -05:00
|
|
|
$inlined[$key][$itemId]['relationships'][$type] = [];
|
2018-10-29 10:07:20 -04:00
|
|
|
|
2018-10-29 09:39:56 -04:00
|
|
|
if ( ! array_key_exists($type, $included)) continue;
|
2018-10-29 10:07:20 -04:00
|
|
|
|
2018-10-29 09:39:56 -04:00
|
|
|
if (array_key_exists('data', $ids ))
|
|
|
|
{
|
|
|
|
$ids = array_column($ids['data'], 'id');
|
|
|
|
}
|
2018-10-29 10:07:20 -04:00
|
|
|
|
2017-01-12 15:41:20 -05:00
|
|
|
foreach($ids as $id)
|
|
|
|
{
|
|
|
|
$inlined[$key][$itemId]['relationships'][$type][$id] = $included[$type][$id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2017-01-12 15:41:20 -05:00
|
|
|
return $inlined;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reorganizes 'included' data to be keyed by
|
|
|
|
* type => [
|
|
|
|
* id => data/attributes,
|
|
|
|
* ]
|
|
|
|
*
|
|
|
|
* @param array $includes
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function organizeIncludes(array $includes): array
|
|
|
|
{
|
|
|
|
$organized = [];
|
2018-10-29 09:39:56 -04:00
|
|
|
$types = array_unique(array_column($includes, 'type'));
|
|
|
|
sort($types);
|
|
|
|
|
|
|
|
foreach ($types as $type)
|
|
|
|
{
|
|
|
|
$organized[$type] = [];
|
|
|
|
}
|
2017-01-12 15:41:20 -05:00
|
|
|
|
|
|
|
foreach ($includes as $item)
|
|
|
|
{
|
|
|
|
$type = $item['type'];
|
|
|
|
$id = $item['id'];
|
2018-10-29 09:39:56 -04:00
|
|
|
|
2018-11-01 22:15:20 -04:00
|
|
|
if (array_key_exists('attributes', $item))
|
|
|
|
{
|
|
|
|
$organized[$type][$id] = $item['attributes'];
|
|
|
|
}
|
2017-01-12 15:41:20 -05:00
|
|
|
|
|
|
|
if (array_key_exists('relationships', $item))
|
|
|
|
{
|
|
|
|
$organized[$type][$id]['relationships'] = static::organizeRelationships($item['relationships']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $organized;
|
|
|
|
}
|
2017-03-31 13:37:53 -04:00
|
|
|
|
2017-01-12 15:41:20 -05:00
|
|
|
/**
|
|
|
|
* Reorganize relationship mappings to make them simpler to use
|
|
|
|
*
|
|
|
|
* Remove verbose structure, and just map:
|
|
|
|
* type => [ idArray ]
|
|
|
|
*
|
|
|
|
* @param array $relationships
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public static function organizeRelationships(array $relationships): array
|
|
|
|
{
|
2018-10-29 09:39:56 -04:00
|
|
|
$organized = $relationships;
|
2017-01-12 15:41:20 -05:00
|
|
|
|
|
|
|
foreach($relationships as $key => $data)
|
|
|
|
{
|
2018-10-29 14:43:06 -04:00
|
|
|
$organized[$key] = $organized[$key] ?? [];
|
|
|
|
|
2017-01-12 15:41:20 -05:00
|
|
|
if ( ! array_key_exists('data', $data))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($data['data'] as $item)
|
|
|
|
{
|
2018-01-18 16:21:45 -05:00
|
|
|
if (\is_array($item) && array_key_exists('id', $item))
|
2017-03-20 13:14:01 -04:00
|
|
|
{
|
|
|
|
$organized[$key][] = $item['id'];
|
|
|
|
}
|
2017-01-12 15:41:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $organized;
|
|
|
|
}
|
2017-01-10 21:13:44 -05:00
|
|
|
}
|