wip
This commit is contained in:
parent
ca9cd62711
commit
480a0fa9b6
|
|
@ -507,10 +507,6 @@ You should indicate to the server if the consumer is authenticated using the `au
|
||||||
$api->authenticated();
|
$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
|
## 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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -150,27 +150,20 @@ interface AdapterInterface
|
||||||
*
|
*
|
||||||
* @param $model
|
* @param $model
|
||||||
* @param HasOne $relationship
|
* @param HasOne $relationship
|
||||||
|
* @param array $fields
|
||||||
* @return mixed|null
|
* @return mixed|null
|
||||||
*/
|
*/
|
||||||
public function getHasOne($model, HasOne $relationship);
|
public function getHasOne($model, HasOne $relationship, array $fields = null);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of models for a has-many relationship for the model.
|
* Get a list of models for a has-many relationship for the model.
|
||||||
*
|
*
|
||||||
* @param $model
|
* @param $model
|
||||||
* @param HasMany $relationship
|
* @param HasMany $relationship
|
||||||
|
* @param array $fields
|
||||||
* @return array
|
* @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.
|
* Apply an attribute value to the model.
|
||||||
|
|
|
||||||
|
|
@ -69,28 +69,25 @@ class EloquentAdapter implements AdapterInterface
|
||||||
return $model->{$this->getAttributeProperty($attribute)};
|
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
|
// comment
|
||||||
// the foreign key on the model. Otherwise we will have to fetch the
|
if ($fields === ['id'] && $relation instanceof BelongsTo) {
|
||||||
// full related model and return its key.
|
if ($key = $model->{$relation->getForeignKeyName()}) {
|
||||||
if ($relation instanceof BelongsTo) {
|
$related = $relation->getRelated();
|
||||||
return $model->{$relation->getForeignKeyName()};
|
|
||||||
|
return $related->newInstance()->forceFill([$related->getKeyName() => $key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$related = $this->getRelationValue($model, $relationship);
|
return null;
|
||||||
|
|
||||||
return $related ? $related->getKey() : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHasOne($model, HasOne $relationship)
|
|
||||||
{
|
|
||||||
return $this->getRelationValue($model, $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);
|
$collection = $this->getRelationValue($model, $relationship);
|
||||||
|
|
||||||
|
|
@ -104,7 +101,7 @@ class EloquentAdapter implements AdapterInterface
|
||||||
|
|
||||||
public function setHasOne($model, HasOne $relationship, $related): void
|
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
|
public function save($model): void
|
||||||
|
|
@ -114,7 +111,7 @@ class EloquentAdapter implements AdapterInterface
|
||||||
|
|
||||||
public function saveHasMany($model, HasMany $relationship, array $related): void
|
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
|
public function delete($model): void
|
||||||
|
|
@ -165,7 +162,7 @@ class EloquentAdapter implements AdapterInterface
|
||||||
|
|
||||||
public function filterByHasOne($query, HasOne $relationship, array $ids): void
|
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);
|
$query->whereIn($relation->getQualifiedForeignKeyName(), $ids);
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +170,7 @@ class EloquentAdapter implements AdapterInterface
|
||||||
public function filterByHasMany($query, HasMany $relationship, array $ids): void
|
public function filterByHasMany($query, HasMany $relationship, array $ids): void
|
||||||
{
|
{
|
||||||
$property = $this->getRelationshipProperty($relationship);
|
$property = $this->getRelationshipProperty($relationship);
|
||||||
$relation = $this->getRelation($query->getModel(), $relationship);
|
$relation = $this->getEloquentRelation($query->getModel(), $relationship);
|
||||||
$relatedKey = $relation->getRelated()->getQualifiedKeyName();
|
$relatedKey = $relation->getRelated()->getQualifiedKeyName();
|
||||||
|
|
||||||
$query->whereHas($property, function ($query) use ($relatedKey, $ids) {
|
$query->whereHas($property, function ($query) use ($relatedKey, $ids) {
|
||||||
|
|
@ -239,7 +236,7 @@ class EloquentAdapter implements AdapterInterface
|
||||||
return implode('.', array_map([$this, 'getRelationshipProperty'], $trail));
|
return implode('.', array_map([$this, 'getRelationshipProperty'], $trail));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getRelation($model, Relationship $relationship)
|
private function getEloquentRelation($model, Relationship $relationship)
|
||||||
{
|
{
|
||||||
return $model->{$this->getRelationshipProperty($relationship)}();
|
return $model->{$this->getRelationshipProperty($relationship)}();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use function Tobyz\JsonApiServer\evaluate;
|
use function Tobyz\JsonApiServer\evaluate;
|
||||||
use function Tobyz\JsonApiServer\get_value;
|
use function Tobyz\JsonApiServer\get_value;
|
||||||
use function Tobyz\JsonApiServer\has_value;
|
use function Tobyz\JsonApiServer\has_value;
|
||||||
|
use function Tobyz\JsonApiServer\set_value;
|
||||||
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
||||||
use Tobyz\JsonApiServer\Exception\UnprocessableEntityException;
|
use Tobyz\JsonApiServer\Exception\UnprocessableEntityException;
|
||||||
use function Tobyz\JsonApiServer\run_callbacks;
|
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'])) {
|
if (! isset($identifier['type'])) {
|
||||||
throw new BadRequestException('type not specified');
|
throw new BadRequestException('type not specified');
|
||||||
|
|
@ -54,6 +55,10 @@ trait SavesData
|
||||||
throw new BadRequestException('id not specified');
|
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']);
|
$resource = $this->api->getResource($identifier['type']);
|
||||||
|
|
||||||
return $this->findResource($request, $resource, $identifier['id']);
|
return $this->findResource($request, $resource, $identifier['id']);
|
||||||
|
|
@ -94,16 +99,20 @@ trait SavesData
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$value = &get_value($data, $field);
|
$value = get_value($data, $field);
|
||||||
|
|
||||||
if (isset($value['data'])) {
|
if (isset($value['data'])) {
|
||||||
|
$allowedTypes = $field->getAllowedTypes();
|
||||||
|
|
||||||
if ($field instanceof HasOne) {
|
if ($field instanceof HasOne) {
|
||||||
$value = $this->getModelForIdentifier($request, $value['data']);
|
set_value($data, $field, $this->getModelForIdentifier($request, $value['data'], $allowedTypes));
|
||||||
} elseif ($field instanceof HasMany) {
|
} elseif ($field instanceof HasMany) {
|
||||||
$value = array_map(function ($identifier) use ($request) {
|
set_value($data, $field, array_map(function ($identifier) use ($request, $allowedTypes) {
|
||||||
return $this->getModelForIdentifier($request, $identifier);
|
return $this->getModelForIdentifier($request, $identifier, $allowedTypes);
|
||||||
}, $value['data']);
|
}, $value['data']));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
set_value($data, $field, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,11 @@ class Create implements RequestHandlerInterface
|
||||||
$this->assertDataValid($data, $model, $request, true);
|
$this->assertDataValid($data, $model, $request, true);
|
||||||
$this->setValues($data, $model, $request);
|
$this->setValues($data, $model, $request);
|
||||||
|
|
||||||
run_callbacks($schema->getListeners('creating'), [$request, $model]);
|
run_callbacks($schema->getListeners('creating'), [$model, $request]);
|
||||||
|
|
||||||
$this->save($data, $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))
|
return (new Show($this->api, $this->resource, $model))
|
||||||
->handle($request)
|
->handle($request)
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,15 @@ class Delete implements RequestHandlerInterface
|
||||||
{
|
{
|
||||||
$schema = $this->resource->getSchema();
|
$schema = $this->resource->getSchema();
|
||||||
|
|
||||||
if (! evaluate($schema->getDeletable(), [$request, $this->model])) {
|
if (! evaluate($schema->getDeletable(), [$this->model, $request])) {
|
||||||
throw new ForbiddenException;
|
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);
|
$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;
|
return new EmptyResponse;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class Update implements RequestHandlerInterface
|
||||||
{
|
{
|
||||||
$schema = $this->resource->getSchema();
|
$schema = $this->resource->getSchema();
|
||||||
|
|
||||||
if (! evaluate($schema->getUpdatable(), [$request, $this->model])) {
|
if (! evaluate($schema->getUpdatable(), [$this->model, $request])) {
|
||||||
throw new ForbiddenException;
|
throw new ForbiddenException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,11 +41,11 @@ class Update implements RequestHandlerInterface
|
||||||
$this->assertDataValid($data, $this->model, $request, false);
|
$this->assertDataValid($data, $this->model, $request, false);
|
||||||
$this->setValues($data, $this->model, $request);
|
$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);
|
$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);
|
return (new Show($this->api, $this->resource, $this->model))->handle($request);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use function Tobyz\JsonApiServer\negate;
|
||||||
abstract class Relationship extends Field
|
abstract class Relationship extends Field
|
||||||
{
|
{
|
||||||
private $type;
|
private $type;
|
||||||
|
private $allowedTypes;
|
||||||
private $linkage = false;
|
private $linkage = false;
|
||||||
private $links = true;
|
private $links = true;
|
||||||
private $loadable = true;
|
private $loadable = true;
|
||||||
|
|
@ -20,9 +21,10 @@ abstract class Relationship extends Field
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function polymorphic()
|
public function polymorphic(array $types = null)
|
||||||
{
|
{
|
||||||
$this->type = null;
|
$this->type = null;
|
||||||
|
$this->allowedTypes = $types;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
@ -69,11 +71,16 @@ abstract class Relationship extends Field
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getType()
|
public function getType(): ?string
|
||||||
{
|
{
|
||||||
return $this->type;
|
return $this->type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAllowedTypes(): ?array
|
||||||
|
{
|
||||||
|
return $this->allowedTypes;
|
||||||
|
}
|
||||||
|
|
||||||
public function links()
|
public function links()
|
||||||
{
|
{
|
||||||
$this->links = true;
|
$this->links = true;
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ final class Serializer
|
||||||
{
|
{
|
||||||
$links = $this->getRelationshipLinks($field, $resourceUrl);
|
$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) {
|
if (! $value) {
|
||||||
return new Structure\ToNull(
|
return new Structure\ToNull(
|
||||||
|
|
@ -207,7 +207,7 @@ final class Serializer
|
||||||
private function resourceForModel($model)
|
private function resourceForModel($model)
|
||||||
{
|
{
|
||||||
foreach ($this->api->getResources() as $resource) {
|
foreach ($this->api->getResources() as $resource) {
|
||||||
if ($resource->getAdapter()->handles($model)) {
|
if ($resource->getAdapter()->represents($model)) {
|
||||||
return $resource;
|
return $resource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -270,11 +270,13 @@ final class Serializer
|
||||||
|
|
||||||
private function relatedResourceIdentifier(Schema\Relationship $field, $model)
|
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([
|
return $this->resourceIdentifier([
|
||||||
'type' => $type,
|
'type' => $relatedResource->getType(),
|
||||||
'id' => is_string($model) ? $model : $relatedResource->getAdapter()->getId($model)
|
'id' => $relatedResource->getAdapter()->getId($model)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ function has_value(array $data, Field $field)
|
||||||
return isset($data[$field->getLocation()][$field->getName()]);
|
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)
|
function set_value(array &$data, Field $field, $value)
|
||||||
|
|
|
||||||
|
|
@ -40,5 +40,10 @@ class RelationshipTypesTest extends AbstractTestCase
|
||||||
$this->markTestIncomplete();
|
$this->markTestIncomplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_polymorphic_create_update()
|
||||||
|
{
|
||||||
|
$this->markTestIncomplete();
|
||||||
|
}
|
||||||
|
|
||||||
// to_many...
|
// to_many...
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue