From 480a0fa9b66ac82816778a1d884bc2b102bb0923 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Thu, 22 Aug 2019 12:26:36 +0930 Subject: [PATCH] wip --- README.md | 4 --- src/Adapter/AdapterInterface.php | 15 +++------- src/Adapter/EloquentAdapter.php | 37 ++++++++++++------------- src/Handler/Concerns/SavesData.php | 21 ++++++++++---- src/Handler/Create.php | 4 +-- src/Handler/Delete.php | 6 ++-- src/Handler/Update.php | 6 ++-- src/Schema/Relationship.php | 11 ++++++-- src/Serializer.php | 12 ++++---- src/functions.php | 4 +-- tests/feature/RelationshipTypesTest.php | 5 ++++ 11 files changed, 67 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 3715697..93ec273 100644 --- a/README.md +++ b/README.md @@ -507,10 +507,6 @@ You should indicate to the server if the consumer is authenticated using the `au $api->authenticated(); ``` -## Examples - -- [Forust](https://github.com/forust/core/tree/master/schema) is forum software that uses tobyz/json-api-server to power its API. - ## Contributing Feel free to send pull requests or create issues if you come across problems or have great ideas. See the [Contributing Guide](https://github.com/tobyz/json-api-server/blob/master/CONTRIBUTING.md) for more information. diff --git a/src/Adapter/AdapterInterface.php b/src/Adapter/AdapterInterface.php index fcd5d88..0815455 100644 --- a/src/Adapter/AdapterInterface.php +++ b/src/Adapter/AdapterInterface.php @@ -150,27 +150,20 @@ interface AdapterInterface * * @param $model * @param HasOne $relationship + * @param array $fields * @return mixed|null */ - public function getHasOne($model, HasOne $relationship); - - /** - * Get the ID of the related resource for a has-one relationship. - * - * @param $model - * @param HasOne $relationship - * @return mixed|null - */ - public function getHasOneId($model, HasOne $relationship): ?string; + public function getHasOne($model, HasOne $relationship, array $fields = null); /** * Get a list of models for a has-many relationship for the model. * * @param $model * @param HasMany $relationship + * @param array $fields * @return array */ - public function getHasMany($model, HasMany $relationship): array; + public function getHasMany($model, HasMany $relationship, array $fields = null): array; /** * Apply an attribute value to the model. diff --git a/src/Adapter/EloquentAdapter.php b/src/Adapter/EloquentAdapter.php index 122bf95..552ac60 100644 --- a/src/Adapter/EloquentAdapter.php +++ b/src/Adapter/EloquentAdapter.php @@ -69,28 +69,25 @@ class EloquentAdapter implements AdapterInterface return $model->{$this->getAttributeProperty($attribute)}; } - public function getHasOneId($model, HasOne $relationship): ?string + public function getHasOne($model, HasOne $relationship, array $fields = null) { - $relation = $this->getRelation($model, $relationship); + $relation = $this->getEloquentRelation($model, $relationship); - // If this is a belongs-to relation, we can simply return the value of - // the foreign key on the model. Otherwise we will have to fetch the - // full related model and return its key. - if ($relation instanceof BelongsTo) { - return $model->{$relation->getForeignKeyName()}; + // comment + if ($fields === ['id'] && $relation instanceof BelongsTo) { + if ($key = $model->{$relation->getForeignKeyName()}) { + $related = $relation->getRelated(); + + return $related->newInstance()->forceFill([$related->getKeyName() => $key]); + } + + return null; } - $related = $this->getRelationValue($model, $relationship); - - return $related ? $related->getKey() : null; - } - - public function getHasOne($model, HasOne $relationship) - { return $this->getRelationValue($model, $relationship); } - public function getHasMany($model, HasMany $relationship): array + public function getHasMany($model, HasMany $relationship, array $fields = null): array { $collection = $this->getRelationValue($model, $relationship); @@ -104,7 +101,7 @@ class EloquentAdapter implements AdapterInterface public function setHasOne($model, HasOne $relationship, $related): void { - $this->getRelation($model, $relationship)->associate($related); + $this->getEloquentRelation($model, $relationship)->associate($related); } public function save($model): void @@ -114,7 +111,7 @@ class EloquentAdapter implements AdapterInterface public function saveHasMany($model, HasMany $relationship, array $related): void { - $this->getRelation($model, $relationship)->sync(new Collection($related)); + $this->getEloquentRelation($model, $relationship)->sync(new Collection($related)); } public function delete($model): void @@ -165,7 +162,7 @@ class EloquentAdapter implements AdapterInterface public function filterByHasOne($query, HasOne $relationship, array $ids): void { - $relation = $this->getRelation($query->getModel(), $relationship); + $relation = $this->getEloquentRelation($query->getModel(), $relationship); $query->whereIn($relation->getQualifiedForeignKeyName(), $ids); } @@ -173,7 +170,7 @@ class EloquentAdapter implements AdapterInterface public function filterByHasMany($query, HasMany $relationship, array $ids): void { $property = $this->getRelationshipProperty($relationship); - $relation = $this->getRelation($query->getModel(), $relationship); + $relation = $this->getEloquentRelation($query->getModel(), $relationship); $relatedKey = $relation->getRelated()->getQualifiedKeyName(); $query->whereHas($property, function ($query) use ($relatedKey, $ids) { @@ -239,7 +236,7 @@ class EloquentAdapter implements AdapterInterface return implode('.', array_map([$this, 'getRelationshipProperty'], $trail)); } - private function getRelation($model, Relationship $relationship) + private function getEloquentRelation($model, Relationship $relationship) { return $model->{$this->getRelationshipProperty($relationship)}(); } diff --git a/src/Handler/Concerns/SavesData.php b/src/Handler/Concerns/SavesData.php index 795081f..da7de61 100644 --- a/src/Handler/Concerns/SavesData.php +++ b/src/Handler/Concerns/SavesData.php @@ -6,6 +6,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use function Tobyz\JsonApiServer\evaluate; use function Tobyz\JsonApiServer\get_value; use function Tobyz\JsonApiServer\has_value; +use function Tobyz\JsonApiServer\set_value; use Tobyz\JsonApiServer\Exception\BadRequestException; use Tobyz\JsonApiServer\Exception\UnprocessableEntityException; use function Tobyz\JsonApiServer\run_callbacks; @@ -44,7 +45,7 @@ trait SavesData ); } - private function getModelForIdentifier(Request $request, $identifier) + private function getModelForIdentifier(Request $request, $identifier, array $validTypes = null) { if (! isset($identifier['type'])) { throw new BadRequestException('type not specified'); @@ -54,6 +55,10 @@ trait SavesData throw new BadRequestException('id not specified'); } + if ($validTypes !== null && ! in_array($identifier['type'], $validTypes)) { + throw new BadRequestException("type [{$identifier['type']}] not allowed"); + } + $resource = $this->api->getResource($identifier['type']); return $this->findResource($request, $resource, $identifier['id']); @@ -94,16 +99,20 @@ trait SavesData continue; } - $value = &get_value($data, $field); + $value = get_value($data, $field); if (isset($value['data'])) { + $allowedTypes = $field->getAllowedTypes(); + if ($field instanceof HasOne) { - $value = $this->getModelForIdentifier($request, $value['data']); + set_value($data, $field, $this->getModelForIdentifier($request, $value['data'], $allowedTypes)); } elseif ($field instanceof HasMany) { - $value = array_map(function ($identifier) use ($request) { - return $this->getModelForIdentifier($request, $identifier); - }, $value['data']); + set_value($data, $field, array_map(function ($identifier) use ($request, $allowedTypes) { + return $this->getModelForIdentifier($request, $identifier, $allowedTypes); + }, $value['data'])); } + } else { + set_value($data, $field, null); } } } diff --git a/src/Handler/Create.php b/src/Handler/Create.php index 33bb2bb..de99860 100644 --- a/src/Handler/Create.php +++ b/src/Handler/Create.php @@ -43,11 +43,11 @@ class Create implements RequestHandlerInterface $this->assertDataValid($data, $model, $request, true); $this->setValues($data, $model, $request); - run_callbacks($schema->getListeners('creating'), [$request, $model]); + run_callbacks($schema->getListeners('creating'), [$model, $request]); $this->save($data, $model, $request); - run_callbacks($schema->getListeners('created'), [$request, $model]); + run_callbacks($schema->getListeners('created'), [$model, $request]); return (new Show($this->api, $this->resource, $model)) ->handle($request) diff --git a/src/Handler/Delete.php b/src/Handler/Delete.php index 7a782ce..f44d9ae 100644 --- a/src/Handler/Delete.php +++ b/src/Handler/Delete.php @@ -26,15 +26,15 @@ class Delete implements RequestHandlerInterface { $schema = $this->resource->getSchema(); - if (! evaluate($schema->getDeletable(), [$request, $this->model])) { + if (! evaluate($schema->getDeletable(), [$this->model, $request])) { throw new ForbiddenException; } - run_callbacks($schema->getListeners('deleting'), [$request, $this->model]); + run_callbacks($schema->getListeners('deleting'), [$this->model, $request]); $this->resource->getAdapter()->delete($this->model); - run_callbacks($schema->getListeners('deleted'), [$request, $this->model]); + run_callbacks($schema->getListeners('deleted'), [$this->model, $request]); return new EmptyResponse; } diff --git a/src/Handler/Update.php b/src/Handler/Update.php index 373c83e..818d628 100644 --- a/src/Handler/Update.php +++ b/src/Handler/Update.php @@ -30,7 +30,7 @@ class Update implements RequestHandlerInterface { $schema = $this->resource->getSchema(); - if (! evaluate($schema->getUpdatable(), [$request, $this->model])) { + if (! evaluate($schema->getUpdatable(), [$this->model, $request])) { throw new ForbiddenException; } @@ -41,11 +41,11 @@ class Update implements RequestHandlerInterface $this->assertDataValid($data, $this->model, $request, false); $this->setValues($data, $this->model, $request); - run_callbacks($schema->getListeners('updating'), [$request, $this->model]); + run_callbacks($schema->getListeners('updating'), [$this->model, $request]); $this->save($data, $this->model, $request); - run_callbacks($schema->getListeners('updated'), [$request, $this->model]); + run_callbacks($schema->getListeners('updated'), [$this->model, $request]); return (new Show($this->api, $this->resource, $this->model))->handle($request); } diff --git a/src/Schema/Relationship.php b/src/Schema/Relationship.php index f1a0c8f..57ca3ee 100644 --- a/src/Schema/Relationship.php +++ b/src/Schema/Relationship.php @@ -8,6 +8,7 @@ use function Tobyz\JsonApiServer\negate; abstract class Relationship extends Field { private $type; + private $allowedTypes; private $linkage = false; private $links = true; private $loadable = true; @@ -20,9 +21,10 @@ abstract class Relationship extends Field return $this; } - public function polymorphic() + public function polymorphic(array $types = null) { $this->type = null; + $this->allowedTypes = $types; return $this; } @@ -69,11 +71,16 @@ abstract class Relationship extends Field return $this; } - public function getType() + public function getType(): ?string { return $this->type; } + public function getAllowedTypes(): ?array + { + return $this->allowedTypes; + } + public function links() { $this->links = true; diff --git a/src/Serializer.php b/src/Serializer.php index f8fb1b5..d083b5a 100644 --- a/src/Serializer.php +++ b/src/Serializer.php @@ -125,7 +125,7 @@ final class Serializer { $links = $this->getRelationshipLinks($field, $resourceUrl); - $value = $isIncluded ? (($getter = $field->getGetter()) ? $getter($model, $this->request) : $adapter->getHasOne($model, $field)) : ($isLinkage && $field->getLoadable() ? $adapter->getHasOneId($model, $field) : null); + $value = $isIncluded ? (($getter = $field->getGetter()) ? $getter($model, $this->request) : $adapter->getHasOne($model, $field)) : ($isLinkage && $field->getLoadable() ? $adapter->getHasOne($model, $field, ['id']) : null); if (! $value) { return new Structure\ToNull( @@ -207,7 +207,7 @@ final class Serializer private function resourceForModel($model) { foreach ($this->api->getResources() as $resource) { - if ($resource->getAdapter()->handles($model)) { + if ($resource->getAdapter()->represents($model)) { return $resource; } } @@ -270,11 +270,13 @@ final class Serializer private function relatedResourceIdentifier(Schema\Relationship $field, $model) { - $relatedResource = $this->api->getResource($type = $field->getType()); + $type = $field->getType(); + + $relatedResource = $type ? $this->api->getResource($type) : $this->resourceForModel($model); return $this->resourceIdentifier([ - 'type' => $type, - 'id' => is_string($model) ? $model : $relatedResource->getAdapter()->getId($model) + 'type' => $relatedResource->getType(), + 'id' => $relatedResource->getAdapter()->getId($model) ]); } } diff --git a/src/functions.php b/src/functions.php index 87446bf..18d9fb8 100644 --- a/src/functions.php +++ b/src/functions.php @@ -40,9 +40,9 @@ function has_value(array $data, Field $field) return isset($data[$field->getLocation()][$field->getName()]); } -function &get_value(array $data, Field $field) +function get_value(array $data, Field $field) { - return $data[$field->getLocation()][$field->getName()]; + return $data[$field->getLocation()][$field->getName()] ?? null; } function set_value(array &$data, Field $field, $value) diff --git a/tests/feature/RelationshipTypesTest.php b/tests/feature/RelationshipTypesTest.php index a8fff29..176386e 100644 --- a/tests/feature/RelationshipTypesTest.php +++ b/tests/feature/RelationshipTypesTest.php @@ -40,5 +40,10 @@ class RelationshipTypesTest extends AbstractTestCase $this->markTestIncomplete(); } + public function test_polymorphic_create_update() + { + $this->markTestIncomplete(); + } + // to_many... }