-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from bedita/feat/api-formatter
ApiFormatterComponent
- Loading branch information
Showing
2 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
/** | ||
* BEdita, API-first content management framework | ||
* Copyright 2021 Atlas Srl, ChannelWeb Srl, Chialab Srl | ||
* | ||
* This file is part of BEdita: you can redistribute it and/or modify | ||
* it under the terms of the GNU Lesser General Public License as published | ||
* by the Free Software Foundation, either version 3 of the License, or | ||
* (at your option) any later version. | ||
* | ||
* See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details. | ||
*/ | ||
|
||
namespace BEdita\WebTools\Controller\Component; | ||
|
||
use Cake\Collection\Collection; | ||
use Cake\Controller\Component; | ||
use Cake\Utility\Hash; | ||
|
||
/** | ||
* Component class to format API response data. | ||
*/ | ||
class ApiFormatterComponent extends Component | ||
{ | ||
/** | ||
* Embed included data into relationships. | ||
* | ||
* @param array $response The response from API | ||
* @return array | ||
*/ | ||
public function embedIncluded(array $response): array | ||
{ | ||
$data = (array)Hash::get($response, 'data'); | ||
if (empty($data)) { | ||
return $response; | ||
} | ||
|
||
$included = (array)Hash::get($response, 'included'); | ||
if (empty($included)) { | ||
return $response; | ||
} | ||
|
||
$included = collection($included); | ||
if (!Hash::numeric(array_keys($data))) { | ||
$response['data'] = $this->addIncluded($data, $included); | ||
|
||
return $response; | ||
} | ||
|
||
foreach ($data as &$d) { | ||
$d = $this->addIncluded($d, $included); | ||
} | ||
unset($d); | ||
|
||
$response['data'] = $data; | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Add included data to main resource. | ||
* | ||
* @param array $resource The resource. | ||
* @param \Cake\Collection\Collection $included The included collection. | ||
* @return array | ||
*/ | ||
protected function addIncluded(array $resource, Collection $included): array | ||
{ | ||
foreach ($resource['relationships'] as &$relation) { | ||
if (empty($relation['data'])) { | ||
continue; | ||
} | ||
|
||
$relation['data'] = $this->extractFromIncluded($included, (array)$relation['data']); | ||
} | ||
unset($relation); | ||
|
||
return $resource; | ||
} | ||
|
||
/** | ||
* Extract items from included starting from $relationship data. | ||
* | ||
* @param \Cake\Collection\Collection $included The included collection | ||
* @param array $relationshipData Array of relationship data. | ||
* Every item must contain 'type' and 'id'. | ||
* @return array | ||
*/ | ||
protected function extractFromIncluded(Collection $included, array $relationshipData): array | ||
{ | ||
foreach ($relationshipData as &$data) { | ||
$data = (array)$included->firstMatch([ | ||
'type' => $data['type'], | ||
'id' => $data['id'], | ||
]); | ||
} | ||
unset($data); | ||
|
||
return $relationshipData; | ||
} | ||
} |
191 changes: 191 additions & 0 deletions
191
tests/TestCase/Controller/Component/ApiFormatterComponentTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace BEdita\WebTools\Test\TestCase\Controller\Component; | ||
|
||
use BEdita\WebTools\Controller\Component\ApiFormatterComponent; | ||
use Cake\Controller\ComponentRegistry; | ||
use Cake\TestSuite\TestCase; | ||
|
||
/** | ||
* {@see \BEdita\WebTools\Controller\Component\ApiFormatterComponent} Test Case | ||
* | ||
* @coversDefaultClass \BEdita\WebTools\Controller\Component\ApiFormatterComponent | ||
*/ | ||
class ApiFormatterComponentTest extends TestCase | ||
{ | ||
/** | ||
* Test subject | ||
* | ||
* @var \App\Controller\Component\ApiFormatter | ||
*/ | ||
public $ApiFormatter; | ||
|
||
/** | ||
* setUp method | ||
* | ||
* @return void | ||
*/ | ||
public function setUp(): void | ||
{ | ||
parent::setUp(); | ||
$registry = new ComponentRegistry(); | ||
$this->ApiFormatter = new ApiFormatterComponent($registry); | ||
} | ||
|
||
/** | ||
* tearDown method | ||
* | ||
* @return void | ||
*/ | ||
public function tearDown(): void | ||
{ | ||
unset($this->ApiFormatter); | ||
parent::tearDown(); | ||
} | ||
|
||
/** | ||
* Provider for `testEmbedIncluded` method | ||
* | ||
* @return array | ||
*/ | ||
public function embedIncludedProvider(): array | ||
{ | ||
$gustavo = ['id' => 1, 'type' => 'persons', 'attributes' => ['name' => 'Gustavo'], 'relationships' => [['chief_of' => [['id' => 777, 'type' => 'universes']]]]]; | ||
$tv = ['id' => 2, 'type' => 'things', 'attributes' => ['name' => 'Television'], 'relationships' => [['part_of' => [['id' => 888, 'type' => 'furnitures']]]]]; | ||
$relationships = [ | ||
'a' => [['id' => 1, 'type' => 'persons']], | ||
'b' => [['id' => 2, 'type' => 'things']], | ||
]; | ||
$relationshipsWithData = [ | ||
'a' => ['data' => [['id' => 1, 'type' => 'persons']]], | ||
'b' => ['data' => [['id' => 2, 'type' => 'things']]], | ||
]; | ||
$relationshipsWithDataExpected = [ | ||
'a' => ['data' => [$gustavo]], | ||
'b' => ['data' => [$tv]], | ||
]; | ||
|
||
return [ | ||
'no data' => [ | ||
['something'], | ||
['something'], | ||
], | ||
'empty data' => [ | ||
['data' => [], 'something'], | ||
['data' => [], 'something'], | ||
], | ||
'empty included' => [ | ||
['data' => [['id' => 1]]], | ||
['data' => [['id' => 1]]], | ||
], | ||
'non numeric keys, no data' => [ | ||
[ | ||
'data' => ['relationships' => $relationships], | ||
'included' => [$gustavo, $tv], | ||
], | ||
[ | ||
'data' => ['relationships' => $relationships], | ||
'included' => [$gustavo, $tv], | ||
], | ||
], | ||
'non numeric keys + data' => [ | ||
[ | ||
'data' => ['relationships' => $relationshipsWithData], | ||
'included' => [$gustavo, $tv], | ||
], | ||
[ | ||
'data' => ['relationships' => $relationshipsWithDataExpected], | ||
'included' => [$gustavo, $tv], | ||
], | ||
], | ||
'numeric keys + data' => [ | ||
[ | ||
'data' => [ | ||
[ | ||
'id' => '12', | ||
'type' => 'images', | ||
'attributes' => [ | ||
'title' => 'test', | ||
], | ||
'relationships' => [ | ||
'streams' => [ | ||
'links' => [], | ||
'data' => [ | ||
[ | ||
'id' => 'af829cbb-c570-4282-94c3-782cf315983a', | ||
'type' => 'streams', | ||
], | ||
], | ||
], | ||
], | ||
], | ||
], | ||
'included' => [ | ||
[ | ||
'id' => 'af829cbb-c570-4282-94c3-782cf315983a', | ||
'type' => 'streams', | ||
'attributes' => [ | ||
'file_name' => 'test.jpg', | ||
'mime_type' => 'image/jpeg', | ||
], | ||
], | ||
], | ||
], | ||
[ | ||
'data' => [ | ||
[ | ||
'id' => '12', | ||
'type' => 'images', | ||
'attributes' => [ | ||
'title' => 'test', | ||
], | ||
'relationships' => [ | ||
'streams' => [ | ||
'links' => [], | ||
'data' => [ | ||
[ | ||
'id' => 'af829cbb-c570-4282-94c3-782cf315983a', | ||
'type' => 'streams', | ||
'attributes' => [ | ||
'file_name' => 'test.jpg', | ||
'mime_type' => 'image/jpeg', | ||
], | ||
], | ||
], | ||
], | ||
], | ||
], | ||
], | ||
'included' => [ | ||
[ | ||
'id' => 'af829cbb-c570-4282-94c3-782cf315983a', | ||
'type' => 'streams', | ||
'attributes' => [ | ||
'file_name' => 'test.jpg', | ||
'mime_type' => 'image/jpeg', | ||
], | ||
], | ||
], | ||
], | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* Test `embedIncluded` method | ||
* | ||
* @param array $response The response data for test | ||
* @param array $expected The expected resulting data | ||
* @return void | ||
* @covers ::embedIncluded() | ||
* @covers ::addIncluded() | ||
* @covers ::extractFromIncluded() | ||
* @dataProvider embedIncludedProvider() | ||
*/ | ||
public function testEmbedIncluded(array $response, array $expected): void | ||
{ | ||
$actual = $this->ApiFormatter->embedIncluded($response); | ||
static::assertEquals($expected, $actual); | ||
} | ||
} |