Skip to content

Commit

Permalink
Parse as resource available resource's relationship data as relations…
Browse files Browse the repository at this point in the history
…hip resource

Figure out how meta can be parsed into a resource using Resource::META_MEMBER_ATTRIBUTE
Use add relationship data callbacks for TYPE_TO_ONE items
Allow return of both string[] and RelationshipResource[] from relationship data callbacks
  • Loading branch information
nohponex committed Feb 2, 2016
1 parent 7c43f9e commit 629d26d
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 75 deletions.
217 changes: 149 additions & 68 deletions src/Resource.php
Expand Up @@ -16,6 +16,8 @@
*/
namespace Phramework\JSONAPI;

use Symfony\Component\Config\Definition\Exception\Exception;

/**
* @since 1.0.0
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
Expand All @@ -25,6 +27,8 @@
*/
class Resource
{
const META_MEMBER_ATTRIBUTE = 'attributes-meta';

const PARSE_DEFAULT = Resource::PARSE_ATTRIBUTES | Resource::PARSE_LINKS
| Resource::PARSE_RELATIONSHIP | Resource::PARSE_RELATIONSHIP_LINKS;

Expand Down Expand Up @@ -95,8 +99,7 @@ public static function parseFromRecords(
$records,
$modelClass,
Fields $fields = null,
$flags = Resource::PARSE_DEFAULT,
$meta = null
$flags = Resource::PARSE_DEFAULT
) {
if (empty($records)) {
return [];
Expand All @@ -106,7 +109,7 @@ public static function parseFromRecords(

foreach ($records as $record) {
//Convert this record to resource object
$resource = static::parseFromRecord($record, $modelClass, $fields, $flags, $meta);
$resource = static::parseFromRecord($record, $modelClass, $fields, $flags);

//Attach links.self to this resource
if (!empty($resource)) {
Expand Down Expand Up @@ -138,13 +141,13 @@ public static function parseFromRecords(
* ```
* @todo Rewrite relationship code
* @todo WHAT ABOUT meta?
* @todo WHAT ABOUT relationship meta?
*/
public static function parseFromRecord(
$record,
$modelClass,
Fields $fields = null,
$flags = Resource::PARSE_DEFAULT,
$meta = null
$flags = Resource::PARSE_DEFAULT
) {
if (empty($record)) {
return null;
Expand All @@ -170,7 +173,7 @@ public static function parseFromRecord(
));
}

//Determine which class called parsed method
//Determine in which class parse method was called (create a Resource or a RelationshipResource)
$resourceClass = static::class;

//Initialize resource
Expand All @@ -182,6 +185,28 @@ public static function parseFromRecord(
//Delete $idAttribute from record's attributes
unset($record->{$idAttribute});

//Attach resource resource if META_ATTRIBUTE is available
if (isset($record->{Resource::META_MEMBER_ATTRIBUTE})) {
$meta = $record->{Resource::META_MEMBER_ATTRIBUTE};

if (is_array($meta) && Resource::isArrayAssoc($meta)) {
$meta = (object) $meta;
}

if (!is_object($meta)) {
throw new \Exception(sprintf(
'The value of meta member MUST be an object for resource with id "%s" of type "%s"',
$resource->id,
$resource->type
));
}

//Push meta
$resource->meta = $meta;

unset($record->{Resource::META_MEMBER_ATTRIBUTE});
}

//Attach relationships if resource's relationships are set
if ($flagRelationships && ($relationships = $modelClass::getRelationships())) {

Expand All @@ -203,74 +228,104 @@ public static function parseFromRecord(
];
}

$attribute = $relationshipObject->getAttribute();
$relationshipType = $relationshipObject->getRelationshipType();
$type = $relationshipObject->getResourceType();

//If relationship data exists in record's attributes use them
if (isset($record->{$attribute}) && $record->{$attribute}) {

//In case of TYPE_TO_ONE attach single object to data
if ($relationshipType == Relationship::TYPE_TO_ONE) {
//TODO PARSE AS RESOURCE!
//TODO ALLOW An optional set to handle return item else use plain data ??
$relationshipEntry->data = (object)[
'id' => (string)$record->{$attribute},
'type' => $type
];

} elseif ($relationshipType == Relationship::TYPE_TO_MANY) {

//In case of TYPE_TO_MANY attach an array of objects
$relationshipEntry->data = [];

foreach ($record->{$attribute} as $k => $d) {
if (!is_array($d)) {
$d = [$d];
}
//TODO PARSE AS RESOURCE!
foreach ($d as $dd) {
//Push object
$relationshipEntry->data[] = (object)[
'id' => (string)$dd,
'type' => $type
];
}
}
$attribute = $relationshipObject->getAttribute();
$relationshipType = $relationshipObject->getRelationshipType();
$relationshipResourceType = $relationshipObject->getResourceType();

//Define callback method
$callbackMethod = [
$relationshipObject->getRelationshipClass(),
$modelClass::GET_RELATIONSHIP_BY_PREFIX . ucfirst($resource->type)
// TODO https://github.com/phramework/jsonapi/issues/6#issuecomment-178689524
];

//Define callback
$callback = function () use
(
$callbackMethod,
$resource,
$relationshipKey,
$fields,
$flags
) {
return call_user_func(
$callbackMethod,
$resource->id,
$relationshipKey,
$fields,
$flags // use $flagRelationshipsAttributes to enable/disable parsing of relationship attributes
);
};

if ($relationshipType == Relationship::TYPE_TO_ONE) {

$relationshipEntryResource = null;

if (isset($record->{$attribute}) && $record->{$attribute}) { //preloaded
$relationshipEntryResource = $record->{$attribute};
}else if (is_callable($callbackMethod)) { //available from callback
$relationshipEntryResource = $callback();
}
} else { //Else try to use relationship`s class method to retrieve data
//TODO can there be an optional method to return TO_ONE items?
if ($relationshipType == Relationship::TYPE_TO_MANY) {
$callMethod = [
$relationshipObject->getRelationshipClass(),
$modelClass::GET_RELATIONSHIP_BY_PREFIX . ucfirst($resource->type)
];
//Check if method exists
if (is_callable($callMethod)) {
$relationshipEntry->data = [];

$relationshipEntryResources = call_user_func(
$callMethod,
$resource->id,
$relationshipKey,
$fields,
$flags // use $flagRelationshipsAttributes to enable/disable parsing of relationship attributes

if ($relationshipEntryResource !== null) {
//parse
if (is_string($relationshipEntryResource)) {
//If returned $relationshipEntryResource is an id string
$relationshipEntry = new RelationshipResource(
$relationshipResourceType,
(string) $relationshipEntryResource
);
} elseif ($relationshipEntryResource instanceof RelationshipResource) {
//If returned $relationshipEntryResource is RelationshipResource
$relationshipEntry->data = $relationshipEntryResource;
} else {
throw new \Exception(sprintf(
'Unexpected relationship entry resource of relationship "%s",'
. ' expecting string or RelationshipResource "%s" given',
$relationshipKey,
gettype($relationshipEntryResource)
));
}
}
} elseif ($relationshipType == Relationship::TYPE_TO_MANY) {
//Initialize
$relationshipEntry->data = [];

//TODO Can we detect if array of strings or array of resources is returned
//So we can continue to support string[]
$relationshipEntryResources = [];

foreach ($relationshipEntryResources as $k => $d) {
//Push object
$relationshipEntry->data[] = $d;
if (isset($record->{$attribute}) && $record->{$attribute}) { //preloaded
$relationshipEntryResources = $record->{$attribute};
}else if (is_callable($callbackMethod)) { //available from callback
$relationshipEntryResources = $callback();
}

/*(object)[
'id' => (string)$d,
'type' => $type
];*/
}
if (!is_array($relationshipEntryResources)) {
throw new \Exception(sprintf(
'Expecting array for relationship entry resources of relationship "%s"',
$relationshipKey
));
}

//Parse resources

if (Resource::isArrayOf($relationshipEntryResources, 'string')) {
//If returned $relationshipEntryResources are id strings
foreach ($relationshipEntryResources as $relationshipEntryResourceId) {
$relationshipEntry->data[] = new RelationshipResource(
$relationshipResourceType,
(string) $relationshipEntryResourceId
);
}
} elseif (Resource::isArrayOf($relationshipEntryResources, RelationshipResource::class)) {
//If returned $relationshipEntryResources are RelationshipResource
$relationshipEntry->data[] = $relationshipEntryResources;
} else {
throw new \Exception(sprintf(
'Unexpected relationship entry resources of relationship "%s",'
. ' expecting string or RelationshipResource "%s" given',
$relationshipKey,
gettype($relationshipEntryResources[0])
));
}
}

Expand Down Expand Up @@ -298,4 +353,30 @@ public static function parseFromRecord(
//Return final resource object
return $resource;
}

/**
* @param array $array
* @param string $type
* @return bool
*/
public static function isArrayOf(array $array, $type = 'string') {
foreach ($array as $value) {
$valueType = gettype($value);

if ($type != $valueType && !(is_object($value) && $value instanceof $type)) {
return false;
}
}

return true;
}

/**
* @param $array
* @return bool
*/
public static function isArrayAssoc($array)
{
return array_keys($array) !== range(0, count($array) - 1);
}
}
20 changes: 15 additions & 5 deletions tests/APP/Models/Article.php
Expand Up @@ -21,6 +21,7 @@
use Phramework\JSONAPI\Filter;
use Phramework\JSONAPI\Page;
use \Phramework\JSONAPI\Relationship;
use Phramework\JSONAPI\Resource;
use Phramework\JSONAPI\Sort;
use Phramework\JSONAPI\ValidationModel;
use Phramework\Models\Operator;
Expand Down Expand Up @@ -127,33 +128,42 @@ public static function get(
$records = [
[
'id' => '1',
'creator-user_id' => 1,
'creator-user_id' => '1',
'status' => 1,
'title' => 'First post',
'updated' => null,
'meta' => (object) [
'keywords' => 'blog'
],
Resource::META_MEMBER_ATTRIBUTE => (object) [
'view' => 1000,
'unique' => 100
]
],
[
'id' => '2',
'creator-user_id' => 1,
'creator-user_id' => '1',
'status' => 1,
'title' => 'Second post',
'updated' => time(),
'meta' => null
'meta' => null,
Resource::META_MEMBER_ATTRIBUTE => [
'some_key' => [
1, 2, 3
]
]
],
[
'id' => '3',
'creator-user_id' => 2,
'creator-user_id' => '2',
'status' => 1,
'title' => 'Third post',
'updated' => time() + 100,
'meta' => null
],
[
'id' => '4',
'creator-user_id' => 1,
'creator-user_id' => '1',
'status' => 0,
'title' => 'Fourth post',
'updated' => time() + 1000,
Expand Down
5 changes: 4 additions & 1 deletion tests/APP/Models/Tag.php
Expand Up @@ -133,7 +133,10 @@ public static function getRelationshipByArticle(
[
'article-id' => '1',
'id' => '1',
'status' => 'ENABLED'
'status' => 'ENABLED',
Resource::META_MEMBER_ATTRIBUTE => (object) [
'weight' => 100
]
],
[
'article-id' => '1',
Expand Down
2 changes: 1 addition & 1 deletion tests/src/Model/RelationshipTest.php
Expand Up @@ -34,7 +34,7 @@ class RelationshipTest extends \PHPUnit_Framework_TestCase
*/
public function testGetRelationshipData()
{
$articles = Article::get(new Page(1))[0];
$articles = Article::get(new Page(1, 1))[0];

print_r($articles);

Expand Down
29 changes: 29 additions & 0 deletions tests/src/ResourceTest.php
Expand Up @@ -35,4 +35,33 @@ public function testConstruct()
'id'
);
}

/**
* @covers ::isArrayOf
*/
public function testIsArrayOf()
{
$strings = ['1', '10', '612ea09b-934d-4d0a-ac33-f26509feb91c'];

$resources = [
new Resource(Article::getType(), '1'),
new Resource(Article::getType(), '1')
];

$relationshipResources = [
new RelationshipResource(Article::getType(), '1'),
new RelationshipResource(Article::getType(), '1')
];

$this->assertTrue(Resource::isArrayOf($strings, 'string'));
$this->assertTrue(Resource::isArrayOf([], 'string'));
$this->assertFalse(Resource::isArrayOf($resources, 'string'));

$this->assertTrue(Resource::isArrayOf($resources, Resource::class));
$this->assertTrue(Resource::isArrayOf($relationshipResources, Resource::class));
$this->assertFalse(Resource::isArrayOf($strings, Resource::class));

$this->assertTrue(Resource::isArrayOf($relationshipResources, RelationshipResource::class));
$this->assertFalse(Resource::isArrayOf($resources, RelationshipResource::class));
}
}

0 comments on commit 629d26d

Please sign in to comment.