Skip to content

Commit

Permalink
Fixed some issues with the HydratorInterface and ClassHydrator
Browse files Browse the repository at this point in the history
  • Loading branch information
kocsismate committed Mar 26, 2019
1 parent fb53960 commit eba24d5
Show file tree
Hide file tree
Showing 7 changed files with 727 additions and 14 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

ADDED:

- New accessor and mutator methods for `WoohooLabs\Yang\JsonApi\Request\ResourceObject`: `id()`, `setId()`, `type()`, `setType()`,
- [#13](https://github.com/woohoolabs/yang/issues/13): `DocumentHydratorInterface` and `ClassDocumentHydrator` in order to fix some issues with the `HydratorInterface` and `ClassHydrator`
- [#12](https://github.com/woohoolabs/yang/issues/13): New accessor and mutator methods for `WoohooLabs\Yang\JsonApi\Request\ResourceObject`: `id()`, `setId()`, `type()`, `setType()`,
`attributes()`, `relationships()`

CHANGED:

DEPRECATED:

- `HydratorInterface`: use the `DocumentHydratorInterface` instead
- `ClassHydrator`: use the `ClassDocumentHydrator` instead

REMOVED:

FIXED:
Expand Down
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,16 +504,16 @@ foreach ($dogResource->relationship("owners")->resources() as $ownerResource) {
}
```

This is the situation when using a hydrator can help you. Currently, Yang has only one hydrator, the `ClassHydrator` which - if the
This is the situation when using a hydrator can help you. Currently, Yang has only one hydrator, the `ClassDocumentHydrator` which - if the
response was successful - maps the specified document to an `stdClass` along with all the resource attributes and relationships.
It means that errors, links, meta data won't be present in the returned object! However, relationships are very easy to
It means that errors, links, meta data won't be present in the returned object. However, relationships are very easy to
access now.

Let's use the document from the last example for demonstrating the power of hydrators:
Let's use the document from the last example for demonstrating the power of hydrators:

```php
$hydrator = new ClassHydrator();
$dog = $hydrator->hydrate($response->document());
$hydrator = new ClassDocumentHydrator();
$dog = $hydrator->hydrateSingleResource($response->document());
```

That's all you need to do in order to create the same `$dog` object as in the first example! Now, you can display its properties:
Expand All @@ -531,20 +531,27 @@ foreach ($dog->owners as $owner) {
}
```

> Note: The method `ClassHydrator::hydrate()` returns an empty `stdClass` if the document doesn't have any primary data,
it returns an `stdClass` if the document has a single primary resource, and it returns an array of `stdObject`s if the
document contains multiple primary resources.
> Note: The method `ClassDocumentHydrator::hydrateSingleResource()` returns an empty `stdClass` if the document doesn't
have any primary data or if the primary data is a collection. Otherwise - when the primary data is a single resource -
an `stdObject` along with all the attributes and relationships is returned.

You may also use the `ClassHydrator::hydrateCollection()` method for retrieving many dogs:
Additionally, you may use the `ClassHydrator::hydrateCollection()` method for retrieving many dogs:

```php
$hydrator = new ClassHydrator();
$hydrator = new ClassDocumentHydrator();
$dogs = $hydrator->hydrateCollection($response->document());
```

> Note: The method `ClassHydrator::hydrateCollection()` returns an empty array if the document doesn't have any primary data,
it returns an array with only one `stdClass` if the document has a single primary resource, and it returns an array of
`stdObject`s if the document contains multiple primary resources.
> Note: The method `ClassHydrator::hydrateCollection()` returns an empty array if the document doesn't have any primary data
or when the primary data is a single resource. Otherwise - when the primary data is a collection of resources - an array
of `stdObject`s along with all the attributes and relationship is returned.

Furthermore, there is a `hydrate()` method available for you when you don't care if the primary data is a single resource
or a collection of resources.

> Note: The method `ClassDocumentHydrator::hydrate()` returns an empty array if the document doesn't have any primary data.
It returns an array containing a single `stdClass` if the primary data is a single resource. Otherwise - the primary data
is a collection of resources - an array of `stdObject`s is returned.

## Advanced Usage

Expand Down
132 changes: 132 additions & 0 deletions src/JsonApi/Hydrator/ClassDocumentHydrator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
declare(strict_types=1);

namespace WoohooLabs\Yang\JsonApi\Hydrator;

use stdClass;
use WoohooLabs\Yang\JsonApi\Schema\Document;
use WoohooLabs\Yang\JsonApi\Schema\Resource\ResourceObject;

final class ClassDocumentHydrator implements DocumentHydratorInterface
{
/**
* @return stdClass[]
*/
public function hydrate(Document $document): iterable
{
if ($document->hasAnyPrimaryResources() === false) {
return [];
}

if ($document->isSingleResourceDocument()) {
return [$this->hydratePrimaryResource($document)];
}

return $this->hydratePrimaryResources($document);
}

public function hydrateSingleResource(Document $document): stdClass
{
if ($document->isSingleResourceDocument() === false) {
return new stdClass();

This comment has been minimized.

Copy link
@holtkamp

holtkamp Mar 26, 2019

Contributor

Instead of returning an "empty" stdClass here, would it not be better to return null here and use the nullable ?stdClass as return type?

Then it can be used like:

if($article = $hydrator->hydrateSingleResource($document){
    //proceed here
}

//nothing additional to check here...

This comment has been minimized.

Copy link
@kocsismate

kocsismate Mar 26, 2019

Author Member

Good catch, I was also wondering about it... And another candidate would be to throw an exception. Let's think about the problem overnight. :)

}

if ($document->hasAnyPrimaryResources() === false) {
return new stdClass();
}

return $this->hydratePrimaryResource($document);
}

/**
* @return stdClass[]
*/
public function hydrateCollection(Document $document): iterable
{
if ($document->hasAnyPrimaryResources() === false) {
return [];
}

if ($document->isSingleResourceDocument()) {
return [];
}

return $this->hydratePrimaryResources($document);
}

private function hydratePrimaryResources(Document $document): array
{
$result = [];
$resourceMap = [];

foreach ($document->primaryResources() as $primaryResource) {
$result[] = $this->hydrateResource($primaryResource, $document, $resourceMap);

This comment has been minimized.

Copy link
@holtkamp

holtkamp Mar 26, 2019

Contributor

use $this->hydratePrimaryResource() here?

}

return $result;
}

private function hydratePrimaryResource(Document $document): stdClass
{
$resourceMap = [];

return $this->hydrateResource($document->primaryResource(), $document, $resourceMap);
}

/**
* @param stdClass[] $resourceMap
*/
private function hydrateResource(ResourceObject $resource, Document $document, array &$resourceMap): stdClass
{
// Fill basic attributes of the resource
$result = new stdClass();
$result->type = $resource->type();
$result->id = $resource->id();
foreach ($resource->attributes() as $attribute => $value) {
$result->{$attribute} = $value;
}

//Save resource to the identity map
$this->saveObjectToMap($result, $resourceMap);

//Fill relationships
foreach ($resource->relationships() as $name => $relationship) {
foreach ($relationship->resourceLinks() as $link) {
$object = $this->getObjectFromMap($link["type"], $link["id"], $resourceMap);

if ($object === null && $document->hasIncludedResource($link["type"], $link["id"])) {
$relatedResource = $document->resource($link["type"], $link["id"]);
$object = $this->hydrateResource($relatedResource, $document, $resourceMap);
}

if ($object === null) {
continue;
}

if ($relationship->isToOneRelationship()) {
$result->{$name} = $object;
} else {
$result->{$name}[] = $object;
}
}
}

return $result;
}

/**
* @param stdClass[] $resourceMap
*/
private function getObjectFromMap(string $type, string $id, array $resourceMap): ?stdClass
{
return $resourceMap[$type . "-" . $id] ?? null;
}

/**
* @param stdClass[] $resourceMap
*/
private function saveObjectToMap(stdClass $object, array &$resourceMap): void
{
$resourceMap[$object->type . "-" . $object->id] = $object;
}
}
3 changes: 3 additions & 0 deletions src/JsonApi/Hydrator/ClassHydrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
use WoohooLabs\Yang\JsonApi\Schema\Document;
use WoohooLabs\Yang\JsonApi\Schema\Resource\ResourceObject;

/**
* @deprecated Use the ClassDocumentHydrator instead.
*/
final class ClassHydrator implements HydratorInterface
{
/**
Expand Down
31 changes: 31 additions & 0 deletions src/JsonApi/Hydrator/DocumentHydratorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);

namespace WoohooLabs\Yang\JsonApi\Hydrator;

use WoohooLabs\Yang\JsonApi\Schema\Document;

interface DocumentHydratorInterface
{
/**
* Hydrates a document into an array/list of items regardless if the primary data is a single resource or a
* collection of primary resources.
*
* @return iterable<mixed>
*/
public function hydrate(Document $document): iterable;

/**
* Hydrates a document when its primary data is a single resource.
*
* @return mixed
*/
public function hydrateSingleResource(Document $document);

/**
* Hydrates a document when its primary data is a collection of resources.
*
* @return iterable<mixed>
*/
public function hydrateCollection(Document $document): iterable;
}
3 changes: 3 additions & 0 deletions src/JsonApi/Hydrator/HydratorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

use WoohooLabs\Yang\JsonApi\Schema\Document;

/**
* @deprecated Use the DocumentHydratorInterface instead.
*/
interface HydratorInterface
{
/**
Expand Down
Loading

0 comments on commit eba24d5

Please sign in to comment.