This commit is contained in:
Toby Zerner 2019-08-22 12:26:36 +09:30
parent ca9cd62711
commit 480a0fa9b6
11 changed files with 67 additions and 58 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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)}();
}

View File

@ -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);
}
}
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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)
]);
}
}

View File

@ -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)

View File

@ -40,5 +40,10 @@ class RelationshipTypesTest extends AbstractTestCase
$this->markTestIncomplete();
}
public function test_polymorphic_create_update()
{
$this->markTestIncomplete();
}
// to_many...
}