Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/json api output #24

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ Homestead.json
/.vagrant
.phpunit.result.cache
coverage/
.php_cs.cache
.php_cs.cache
2 changes: 2 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ parameters:
# This class does string-building with class-strings, which is more hassle
# to type-hint and annotate than it's worth.
- src/Apitizer/Support/ClassFilter.php

checkMissingIterableValueType: false
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not disable this, as it forces us to think about the content of our arrays/iterables, rather than having undocumented arrays with "something" in them. If the array is too complex to easily document, make it an object (or array of objects).

57 changes: 57 additions & 0 deletions src/Apitizer/JsonApi/Document.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Apitizer\JsonApi;

class Document
{
// /** @var ResourceObject[] */
// protected $includedResources = [];

/** @var ResourceObject[] */
protected $resources = [];

/**
* add a resource to the collection
* adds included resources if found inside the resource's relationships, unless $options['includeContainedResources'] is set to false
* @param string $type
* @param string $id
* @param array $attributes optional, if given a ResourceObject is added, otherwise a ResourceIdentifierObject is added
*/
public function addResource(string $type, string $id, array $attributes=[]): void
{
$object = ResourceObject::factory($attributes, $type, $id);

$this->resources[] = $object;
}

// /**
// * @inheritDoc
// */
// public function setPaginationLinks($previousHref=null, $nextHref=null, $firstHref=null, $lastHref=null) {
// if ($previousHref !== null) {
// $this->addLink('prev', $previousHref);
// }
// if ($nextHref !== null) {
// $this->addLink('next', $nextHref);
// }
// if ($firstHref !== null) {
// $this->addLink('first', $firstHref);
// }
// if ($lastHref !== null) {
// $this->addLink('last', $lastHref);
// }
// }

public function toArray(): array
{
$data = [
'data' => []
];

foreach ($this->resources as $resource) {
$data['data'][] = $resource->toArray();
}

return $data;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably simplify this to something like:

return [
    'data' => collect($this->resources)->map->toArray()->all(),
];

If we add the Illuminate\Contracts\Support\Arrayable interface to the ResourceObject (which is already implemented with toArray), then we can simplify it even further to:

return [
    'data' => collect($this->resources)->toArray(),
];

Because the Collection class will then take care of calling toArray for us. This advice holds for all container objects we introduce by the way.

}
229 changes: 229 additions & 0 deletions src/Apitizer/JsonApi/ResourceObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
<?php

namespace Apitizer\JsonApi;

class ResourceObject
{

/** @var array */
protected $attributes = [];

/** @var array */
protected $relationships;

/** @var string */
protected $type;

/** @var string */
protected $id;

public function __construct(string $type, string $id)
{
$this->type = $type;
$this->id = $id;
}

/**
* @param array $attributes
* @param string $type
* @param string $id
* @return ResourceObject
*/
public static function factory(array $attributes, string $type, string $id)
{
$resourceObject = new self($type, $id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the contstructor? Or factory(ResourceObject::class)->create($attributes);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

je can use the factory pattern also just for initing your object if you have some specific create logic you dont always want inside your constructor. I remember we had this discussion before ;)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't finish that discussion

$resourceObject->attributes = $attributes;

return $resourceObject;
}

public function toArray(): array
{
$array = [];
$array['type'] = $this->type;
$array['id'] = $this->id;
if (! empty($this->attributes)) {
$array['attributes'] = $this->attributes;
}

// if ($this->meta !== null && $this->meta->isEmpty() === false) {
// $array['meta'] = $this->meta->toArray();
// }
// if ($this->relationships !== null && $this->relationships->isEmpty() === false) {
// $array['relationships'] = $this->relationships->toArray();
// }
// if ($this->links !== null && $this->links->isEmpty() === false) {
// $array['links'] = $this->links->toArray();
// }

return $array;
}


// /**
// * @param object $attributes
// * @param string $type optional
// * @param string|int $id optional
// * @param array $options optional {@see ResourceObject::$defaults}
// * @return ResourceObject
// */
// public static function fromObject($attributes, $type=null, $id=null, array $options=[]) {
// $array = Converter::objectToArray($attributes);

// return self::fromArray($array, $type, $id, $options);
// }

// /**
// * add key-value pairs to attributes
// *
// * @param string $key
// * @param mixed $value
// * @param array $options optional {@see ResourceObject::$defaults}
// */
// public function add($key, $value, array $options=[]) {
// $options = array_merge(self::$defaults, $options);

// if ($this->attributes === null) {
// $this->attributes = new AttributesObject();
// }

// $this->validator->claimUsedFields([$key], Validator::OBJECT_CONTAINER_ATTRIBUTES, $options);

// $this->attributes->add($key, $value);
// }

// /**
// * @param string $key
// * @param mixed $relation ResourceInterface | ResourceInterface[] | CollectionDocument
// * @param array $links optional
// * @param array $meta optional
// * @param array $options optional {@see ResourceObject::$defaults}
// * @return RelationshipObject
// */
// public function addRelationship($key, $relation, array $links=[], array $meta=[], array $options=[]) {
// $relationshipObject = RelationshipObject::fromAnything($relation, $links, $meta);

// $this->addRelationshipObject($key, $relationshipObject, $options);

// return $relationshipObject;
// }

// /**
// * @param string $href
// * @param array $meta optional, if given a LinkObject is added, otherwise a link string is added
// */
// public function setSelfLink($href, array $meta=[]) {
// $this->addLink('self', $href, $meta);
// }


// /**
// * @param string $key
// * @param RelationshipObject $relationshipObject
// * @param array $options optional {@see ResourceObject::$defaults}
// *
// * @throws DuplicateException if the resource is contained as a resource in the relationship
// */
// public function addRelationshipObject($key, RelationshipObject $relationshipObject, array $options=[]) {
// if ($relationshipObject->hasResource($this)) {
// throw new DuplicateException('can not add relation to self');
// }

// if ($this->relationships === null) {
// $this->setRelationshipsObject(new RelationshipsObject());
// }

// $this->validator->claimUsedFields([$key], Validator::OBJECT_CONTAINER_RELATIONSHIPS, $options);

// $this->relationships->addRelationshipObject($key, $relationshipObject);
// }

// /**
// * @param RelationshipsObject $relationshipsObject
// */
// public function setRelationshipsObject(RelationshipsObject $relationshipsObject) {
// $newKeys = $relationshipsObject->getKeys();
// $this->validator->clearUsedFields(Validator::OBJECT_CONTAINER_RELATIONSHIPS);
// $this->validator->claimUsedFields($newKeys, Validator::OBJECT_CONTAINER_RELATIONSHIPS);

// $this->relationships = $relationshipsObject;
// }

// /**
// * internal api
// */

// /**
// * whether the ResourceObject is empty except for the ResourceIdentifierObject
// *
// * this can be used to determine if a Relationship's resource could be added as included resource
// *
// * @internal
// *
// * @return boolean
// */
// public function hasIdentifierPropertiesOnly() {
// if ($this->attributes !== null && $this->attributes->isEmpty() === false) {
// return false;
// }
// if ($this->relationships !== null && $this->relationships->isEmpty() === false) {
// return false;
// }
// if ($this->links !== null && $this->links->isEmpty() === false) {
// return false;
// }

// return true;
// }

// /**
// * ResourceInterface
// */

// /**
// * @inheritDoc
// */
// public function getResource($identifierOnly=false) {
// if ($identifierOnly) {
// return ResourceIdentifierObject::fromResourceObject($this);
// }

// return $this;
// }

// /**
// * ObjectInterface
// */

// /**
// * @inheritDoc
// */
// public function isEmpty() {
// if (parent::isEmpty() === false) {
// return false;
// }
// if ($this->attributes !== null && $this->attributes->isEmpty() === false) {
// return false;
// }
// if ($this->relationships !== null && $this->relationships->isEmpty() === false) {
// return false;
// }
// if ($this->links !== null && $this->links->isEmpty() === false) {
// return false;
// }

// return true;
// }


// /**
// * @inheritDoc
// */
// public function getNestedContainedResourceObjects() {
// if ($this->relationships === null) {
// return [];
// }

// return $this->relationships->getNestedContainedResourceObjects();
// }
}
38 changes: 38 additions & 0 deletions src/Apitizer/Rendering/AbstractRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,51 @@

namespace Apitizer\Rendering;

use Apitizer\Exceptions\InvalidOutputException;
use Apitizer\Policies\PolicyFailed;
use Apitizer\QueryBuilder;
use Apitizer\Types\AbstractField;
use Apitizer\Types\Association;
use Apitizer\Types\Concerns\FetchesValueFromRow;
use Apitizer\Types\FetchSpec;
use Illuminate\Support\Arr;

abstract class AbstractRenderer
{
use FetchesValueFromRow;

/**
* @param QueryBuilder $queryBuilder
* @param mixed $data
* @param FetchSpec $fetchSpec
* @return array
*/
abstract public function render(QueryBuilder $queryBuilder, $data, FetchSpec $fetchSpec): array;

/**
* @param mixed $row
* @param AbstractField $field
* @param array<string, mixed> $renderedData
*
* @throws InvalidOutputException if the value does not adhere to the
* requirements set by the field. For example, if the field is not
* nullable but the value is null, this will throw an error. Enum
* field may also throw an error if the value is not in the enum.
*/
protected function addRenderedField(
$row,
AbstractField $field,
array &$renderedData
): void {
$value = $field->render($row);

if ($value instanceof PolicyFailed) {
return;
}

$renderedData[$field->getName()] = $value;
}

/**
* Check if we're dealing with a single row of data or a collection of rows.
*
Expand Down
Loading