wip
This commit is contained in:
parent
269207eedb
commit
ca9cd62711
|
|
@ -138,15 +138,15 @@ class EloquentAdapter implements AdapterInterface
|
|||
|
||||
public function filterByAttribute($query, Attribute $attribute, $value): void
|
||||
{
|
||||
$property = $this->getAttributeProperty($attribute);
|
||||
$column = $this->getAttributeColumn($attribute);
|
||||
|
||||
// TODO: extract this into non-adapter territory
|
||||
if (preg_match('/(.+)\.\.(.+)/', $value, $matches)) {
|
||||
if ($matches[1] !== '*') {
|
||||
$query->where($property, '>=', $matches[1]);
|
||||
$query->where($column, '>=', $matches[1]);
|
||||
}
|
||||
if ($matches[2] !== '*') {
|
||||
$query->where($property, '<=', $matches[2]);
|
||||
$query->where($column, '<=', $matches[2]);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -154,13 +154,13 @@ class EloquentAdapter implements AdapterInterface
|
|||
|
||||
foreach (['>=', '>', '<=', '<'] as $operator) {
|
||||
if (strpos($value, $operator) === 0) {
|
||||
$query->where($property, $operator, substr($value, strlen($operator)));
|
||||
$query->where($column, $operator, substr($value, strlen($operator)));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$query->where($property, $value);
|
||||
$query->where($column, $value);
|
||||
}
|
||||
|
||||
public function filterByHasOne($query, HasOne $relationship, array $ids): void
|
||||
|
|
@ -181,9 +181,9 @@ class EloquentAdapter implements AdapterInterface
|
|||
});
|
||||
}
|
||||
|
||||
public function sortByAttribute($query, Attribute $field, string $direction): void
|
||||
public function sortByAttribute($query, Attribute $attribute, string $direction): void
|
||||
{
|
||||
$query->orderBy($this->getAttributeProperty($field), $direction);
|
||||
$query->orderBy($this->getAttributeColumn($attribute), $direction);
|
||||
}
|
||||
|
||||
public function paginate($query, int $limit, int $offset): void
|
||||
|
|
@ -224,6 +224,11 @@ class EloquentAdapter implements AdapterInterface
|
|||
return $attribute->getProperty() ?: strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $attribute->getName()));
|
||||
}
|
||||
|
||||
private function getAttributeColumn(Attribute $attribute): string
|
||||
{
|
||||
return $this->model->getTable().'.'.$this->getAttributeProperty($attribute);
|
||||
}
|
||||
|
||||
private function getRelationshipProperty(Relationship $relationship): string
|
||||
{
|
||||
return $relationship->getProperty() ?: $relationship->getName();
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ trait IncludesData
|
|||
$adapter = $this->resource->getAdapter();
|
||||
$fields = $this->resource->getSchema()->getFields();
|
||||
|
||||
// TODO: don't load IDs for relationships which are included below
|
||||
foreach ($fields as $name => $field) {
|
||||
if (! $field instanceof Relationship || ! evaluate($field->getLinkage(), [$request]) || ! $field->getLoadable()) {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -13,13 +13,11 @@ use Psr\Http\Message\ServerRequestInterface as Request;
|
|||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Tobyz\JsonApiServer\JsonApi;
|
||||
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
||||
use Tobyz\JsonApiServer\Exception\ForbiddenException;
|
||||
use Tobyz\JsonApiServer\JsonApiResponse;
|
||||
use Tobyz\JsonApiServer\ResourceType;
|
||||
use Tobyz\JsonApiServer\Schema\Attribute;
|
||||
use Tobyz\JsonApiServer\Schema\HasMany;
|
||||
use Tobyz\JsonApiServer\Schema\HasOne;
|
||||
use Tobyz\JsonApiServer\Schema\Type;
|
||||
use Tobyz\JsonApiServer\Serializer;
|
||||
|
||||
class Index implements RequestHandlerInterface
|
||||
|
|
@ -47,7 +45,7 @@ class Index implements RequestHandlerInterface
|
|||
$query = $adapter->query();
|
||||
|
||||
foreach ($schema->getScopes() as $scope) {
|
||||
$request = $scope($request, $query) ?: $request;
|
||||
$request = $scope($query, $request, null) ?: $request;
|
||||
}
|
||||
|
||||
if ($filter = $request->getAttribute('jsonApiFilter')) {
|
||||
|
|
|
|||
|
|
@ -162,6 +162,9 @@ abstract class Field
|
|||
return $this->default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|Closure
|
||||
*/
|
||||
public function getFilterable()
|
||||
{
|
||||
return $this->filterable;
|
||||
|
|
|
|||
|
|
@ -48,12 +48,12 @@ abstract class Relationship extends Field
|
|||
return $this;
|
||||
}
|
||||
|
||||
// public function notLoadable()
|
||||
// {
|
||||
// $this->loadable = false;
|
||||
//
|
||||
// return $this;
|
||||
// }
|
||||
public function notLoadable()
|
||||
{
|
||||
$this->loadable = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function includable()
|
||||
{
|
||||
|
|
@ -98,6 +98,9 @@ abstract class Relationship extends Field
|
|||
return $this->links;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|Closure
|
||||
*/
|
||||
public function getLoadable()
|
||||
{
|
||||
return $this->loadable;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ final class Type
|
|||
private $limit = 50;
|
||||
private $countable = true;
|
||||
private $defaultSort;
|
||||
private $defaultFilter;
|
||||
private $scopes = [];
|
||||
private $saver;
|
||||
private $creatable = false;
|
||||
|
|
@ -80,6 +81,15 @@ final class Type
|
|||
return $this->meta;
|
||||
}
|
||||
|
||||
public function filter(string $name, Closure $callback)
|
||||
{
|
||||
$this->attribute($name)
|
||||
->hidden()
|
||||
->filterable($callback);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function paginate(int $perPage)
|
||||
{
|
||||
$this->paginate = $perPage;
|
||||
|
|
@ -90,7 +100,7 @@ final class Type
|
|||
$this->paginate = null;
|
||||
}
|
||||
|
||||
public function getPaginate(): int
|
||||
public function getPaginate(): ?int
|
||||
{
|
||||
return $this->paginate;
|
||||
}
|
||||
|
|
@ -279,4 +289,16 @@ final class Type
|
|||
{
|
||||
return $this->defaultSort;
|
||||
}
|
||||
|
||||
public function defaultFilter(?array $filter)
|
||||
{
|
||||
$this->defaultFilter = $filter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDefaultFilter()
|
||||
{
|
||||
return $this->defaultFilter;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ final class Serializer
|
|||
ksort($metas);
|
||||
|
||||
foreach ($metas as $name => $meta) {
|
||||
$data['meta'][$name] = new Structure\Meta($meta->name, ($meta->value)($this->request, $model));
|
||||
$data['meta'][$name] = new Structure\Meta($meta->getName(), ($meta->getValue())($model, $this->request));
|
||||
}
|
||||
|
||||
$this->merge($data);
|
||||
|
|
@ -270,11 +270,11 @@ final class Serializer
|
|||
|
||||
private function relatedResourceIdentifier(Schema\Relationship $field, $model)
|
||||
{
|
||||
$relatedResource = $this->api->getResource($field->resource);
|
||||
$relatedResource = $this->api->getResource($type = $field->getType());
|
||||
|
||||
return $this->resourceIdentifier([
|
||||
'type' => $field->resource,
|
||||
'id' => $relatedResource->getAdapter()->getId($model)
|
||||
'type' => $type,
|
||||
'id' => is_string($model) ? $model : $relatedResource->getAdapter()->getId($model)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,202 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of JSON-API.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tobyz\Tests\JsonApiServer;
|
||||
|
||||
use Tobyz\JsonApiServer\JsonApi;
|
||||
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
||||
use Tobyz\JsonApiServer\Exception\ForbiddenException;
|
||||
use Tobyz\JsonApiServer\Serializer;
|
||||
use Tobyz\JsonApiServer\Schema\Type;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use JsonApiPhp\JsonApi;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
use Zend\Diactoros\Uri;
|
||||
|
||||
class CreateTest extends AbstractTestCase
|
||||
{
|
||||
public function testResourceNotCreatableByDefault()
|
||||
{
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', new MockAdapter(), function (Type $schema) {
|
||||
//
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('POST', '/users');
|
||||
|
||||
$this->expectException(ForbiddenException::class);
|
||||
$this->expectExceptionMessage('You cannot create this resource');
|
||||
|
||||
$api->handle($request);
|
||||
}
|
||||
|
||||
public function testCreateResourceValidatesBody()
|
||||
{
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', new MockAdapter(), function (Type $schema) {
|
||||
$schema->creatable();
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('POST', '/users');
|
||||
|
||||
$this->expectException(BadRequestException::class);
|
||||
|
||||
$api->handle($request);
|
||||
}
|
||||
|
||||
public function testCreateResource()
|
||||
{
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $adapter = new MockAdapter(), function (Type $schema) {
|
||||
$schema->creatable();
|
||||
|
||||
$schema->attribute('name')->writable();
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('POST', '/users')
|
||||
->withParsedBody([
|
||||
'data' => [
|
||||
'type' => 'users',
|
||||
'id' => '1',
|
||||
'attributes' => [
|
||||
'name' => 'Toby'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertTrue($adapter->createdModel->saveWasCalled);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
$this->assertEquals(
|
||||
[
|
||||
'type' => 'users',
|
||||
'id' => '1',
|
||||
'attributes' => [
|
||||
'name' => 'Toby'
|
||||
],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/users/1'
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
}
|
||||
|
||||
public function testAttributeWritable()
|
||||
{
|
||||
$request = $this->buildRequest('POST', '/users')
|
||||
->withParsedBody([
|
||||
'data' => [
|
||||
'type' => 'users',
|
||||
'id' => '1',
|
||||
'attributes' => [
|
||||
'writable1' => 'value',
|
||||
'writable2' => 'value',
|
||||
'writable3' => 'value',
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $adapter = new MockAdapter(), function (Type $schema) use ($adapter, $request) {
|
||||
$schema->creatable();
|
||||
|
||||
$schema->attribute('writable1')->writable();
|
||||
|
||||
$schema->attribute('writable2')->writableIf(function ($arg1, $arg2) use ($adapter, $request) {
|
||||
$this->assertInstanceOf(Request::class, $arg1);
|
||||
$this->assertEquals($adapter->createdModel, $arg2);
|
||||
return true;
|
||||
});
|
||||
|
||||
$schema->attribute('writable3')->readonlyIf(function ($arg1, $arg2) use ($adapter, $request) {
|
||||
$this->assertInstanceOf(Request::class, $arg1);
|
||||
$this->assertEquals($adapter->createdModel, $arg2);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertEquals(201, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testAttributeReadonly()
|
||||
{
|
||||
$request = $this->buildRequest('POST', '/users')
|
||||
->withParsedBody([
|
||||
'data' => [
|
||||
'type' => 'users',
|
||||
'id' => '1',
|
||||
'attributes' => [
|
||||
'readonly' => 'value',
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $adapter = new MockAdapter(), function (Type $schema) use ($adapter, $request) {
|
||||
$schema->creatable();
|
||||
|
||||
$schema->attribute('readonly')->readonly();
|
||||
});
|
||||
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('Field [readonly] is not writable');
|
||||
|
||||
$api->handle($request);
|
||||
}
|
||||
|
||||
public function testAttributeDefault()
|
||||
{
|
||||
$request = $this->buildRequest('POST', '/users')
|
||||
->withParsedBody([
|
||||
'data' => [
|
||||
'type' => 'users',
|
||||
'id' => '1',
|
||||
'attributes' => [
|
||||
'attribute3' => 'userValue'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $adapter = new MockAdapter(), function (Type $schema) use ($request) {
|
||||
$schema->creatable();
|
||||
|
||||
$schema->attribute('attribute1')->default('defaultValue');
|
||||
$schema->attribute('attribute2')->default(function ($arg1) use ($request) {
|
||||
$this->assertInstanceOf(Request::class, $arg1);
|
||||
return 'defaultValue';
|
||||
});
|
||||
$schema->attribute('attribute3')->writable()->default('defaultValue');
|
||||
});
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
'attribute1' => 'defaultValue',
|
||||
'attribute2' => 'defaultValue',
|
||||
'attribute3' => 'userValue'
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']['attributes']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of JSON-API.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tobyz\Tests\JsonApiServer;
|
||||
|
||||
use Tobyz\JsonApiServer\JsonApi;
|
||||
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
||||
use Tobyz\JsonApiServer\Exception\ForbiddenException;
|
||||
use Tobyz\JsonApiServer\Serializer;
|
||||
use Tobyz\JsonApiServer\Schema\Type;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use JsonApiPhp\JsonApi;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
use Zend\Diactoros\Uri;
|
||||
|
||||
class DeleteTest extends AbstractTestCase
|
||||
{
|
||||
public function testResourceNotDeletableByDefault()
|
||||
{
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', new MockAdapter(), function (Type $schema) {
|
||||
//
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('DELETE', '/users/1');
|
||||
|
||||
$this->expectException(ForbiddenException::class);
|
||||
$this->expectExceptionMessage('You cannot delete this resource');
|
||||
|
||||
$api->handle($request);
|
||||
}
|
||||
|
||||
public function testDeleteResource()
|
||||
{
|
||||
$usersAdapter = new MockAdapter([
|
||||
'1' => $user = (object)['id' => '1']
|
||||
]);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $usersAdapter, function (Type $schema) {
|
||||
$schema->deletable();
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('DELETE', '/users/1');
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertEquals(204, $response->getStatusCode());
|
||||
$this->assertTrue($user->deleteWasCalled);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,600 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of JSON-API.
|
||||
*
|
||||
* (c) Toby Zerner <toby.zerner@gmail.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Tobyz\Tests\JsonApiServer;
|
||||
|
||||
use Tobyz\JsonApiServer\JsonApi;
|
||||
use Tobyz\JsonApiServer\Exception\BadRequestException;
|
||||
use Tobyz\JsonApiServer\Schema\Type;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
class ShowTest extends AbstractTestCase
|
||||
{
|
||||
public function test_resource_with_no_fields()
|
||||
{
|
||||
$api = new JsonApi('http://example.com');
|
||||
$api->resource('users', new MockAdapter(), function (Type $schema) {
|
||||
// no fields
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1');
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertEquals($response->getStatusCode(), 200);
|
||||
$this->assertEquals(
|
||||
[
|
||||
'data' => [
|
||||
'type' => 'users',
|
||||
'id' => '1',
|
||||
'links' => [
|
||||
'self' => 'http://example.com/users/1'
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)
|
||||
);
|
||||
}
|
||||
|
||||
public function test_attributes()
|
||||
{
|
||||
$adapter = new MockAdapter([
|
||||
'1' => (object) [
|
||||
'id' => '1',
|
||||
'attribute1' => 'value1',
|
||||
'property2' => 'value2',
|
||||
'property3' => 'value3'
|
||||
]
|
||||
]);
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1');
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $adapter, function (Type $schema) {
|
||||
$schema->attribute('attribute1');
|
||||
$schema->attribute('attribute2', 'property2');
|
||||
$schema->attribute('attribute3')->property('property3');
|
||||
});
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'attributes' => [
|
||||
'attribute1' => 'value1',
|
||||
'attribute2' => 'value2',
|
||||
'attribute3' => 'value3'
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
}
|
||||
|
||||
public function testAttributeGetter()
|
||||
{
|
||||
$adapter = new MockAdapter([
|
||||
'1' => $model = (object) ['id' => '1']
|
||||
]);
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1');
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $adapter, function (Type $schema) use ($model, $request) {
|
||||
$schema->attribute('attribute1')
|
||||
->get(function ($arg1, $arg2) use ($model, $request) {
|
||||
$this->assertInstanceOf(Request::class, $arg1);
|
||||
$this->assertEquals($model, $arg2);
|
||||
return 'value1';
|
||||
});
|
||||
});
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertEquals($response->getStatusCode(), 200);
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'attributes' => [
|
||||
'attribute1' => 'value1'
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
}
|
||||
|
||||
public function testAttributeVisibility()
|
||||
{
|
||||
$adapter = new MockAdapter([
|
||||
'1' => $model = (object) ['id' => '1']
|
||||
]);
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1');
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
$api->resource('users', $adapter, function (Type $schema) use ($model, $request) {
|
||||
$schema->attribute('visible1');
|
||||
|
||||
$schema->attribute('visible2')->visible();
|
||||
|
||||
$schema->attribute('visible3')->visibleIf(function ($arg1, $arg2) use ($model, $request) {
|
||||
$this->assertInstanceOf(Request::class, $arg1);
|
||||
$this->assertEquals($model, $arg2);
|
||||
return true;
|
||||
});
|
||||
|
||||
$schema->attribute('visible4')->hiddenIf(function ($arg1, $arg2) use ($model, $request) {
|
||||
$this->assertInstanceOf(Request::class, $arg1);
|
||||
$this->assertEquals($model, $arg2);
|
||||
return false;
|
||||
});
|
||||
|
||||
$schema->attribute('hidden1')->hidden();
|
||||
|
||||
$schema->attribute('hidden2')->visibleIf(function () {
|
||||
return false;
|
||||
});
|
||||
|
||||
$schema->attribute('hidden3')->hiddenIf(function () {
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$attributes = json_decode($response->getBody(), true)['data']['attributes'];
|
||||
|
||||
$this->assertArrayHasKey('visible1', $attributes);
|
||||
$this->assertArrayHasKey('visible2', $attributes);
|
||||
$this->assertArrayHasKey('visible3', $attributes);
|
||||
$this->assertArrayHasKey('visible4', $attributes);
|
||||
|
||||
$this->assertArrayNotHasKey('hidden1', $attributes);
|
||||
$this->assertArrayNotHasKey('hidden2', $attributes);
|
||||
$this->assertArrayNotHasKey('hidden3', $attributes);
|
||||
}
|
||||
|
||||
public function testHasOneRelationship()
|
||||
{
|
||||
$phonesAdapter = new MockAdapter([
|
||||
'1' => $phone1 = (object) ['id' => '1', 'number' => '8881'],
|
||||
'2' => $phone2 = (object) ['id' => '2', 'number' => '8882'],
|
||||
'3' => $phone3 = (object) ['id' => '3', 'number' => '8883']
|
||||
]);
|
||||
|
||||
$usersAdapter = new MockAdapter([
|
||||
'1' => (object) [
|
||||
'id' => '1',
|
||||
'phone' => $phone1,
|
||||
'property2' => $phone2,
|
||||
'property3' => $phone3,
|
||||
]
|
||||
]);
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1')
|
||||
->withQueryParams(['include' => 'phone,phone2,phone3']);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $usersAdapter, function (Type $schema) {
|
||||
$schema->hasOne('phone');
|
||||
|
||||
$schema->hasOne('phone2', 'phones', 'property2');
|
||||
|
||||
$schema->hasOne('phone3')
|
||||
->type('phones')
|
||||
->property('property3');
|
||||
});
|
||||
|
||||
$api->resource('phones', $phonesAdapter, function (Type $schema) {
|
||||
$schema->attribute('number');
|
||||
});
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'relationships' => [
|
||||
'phone' => [
|
||||
'data' => ['type' => 'phones', 'id' => '1']
|
||||
],
|
||||
'phone2' => [
|
||||
'data' => ['type' => 'phones', 'id' => '2']
|
||||
],
|
||||
'phone3' => [
|
||||
'data' => ['type' => 'phones', 'id' => '3']
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
[
|
||||
'type' => 'phones',
|
||||
'id' => '1',
|
||||
'attributes' => ['number' => '8881'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/phones/1'
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'phones',
|
||||
'id' => '2',
|
||||
'attributes' => ['number' => '8882'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/phones/2'
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'phones',
|
||||
'id' => '3',
|
||||
'attributes' => ['number' => '8883'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/phones/3'
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['included']
|
||||
);
|
||||
}
|
||||
|
||||
public function testHasOneRelationshipInclusion()
|
||||
{
|
||||
$phonesAdapter = new MockAdapter([
|
||||
'1' => $phone1 = (object) ['id' => '1', 'number' => '8881'],
|
||||
'2' => $phone2 = (object) ['id' => '2', 'number' => '8882']
|
||||
]);
|
||||
|
||||
$usersAdapter = new MockAdapter([
|
||||
'1' => (object) [
|
||||
'id' => '1',
|
||||
'phone' => $phone1,
|
||||
'property2' => $phone2
|
||||
]
|
||||
]);
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1')
|
||||
->withQueryParams(['include' => 'phone2']);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $usersAdapter, function (Type $schema) {
|
||||
$schema->hasOne('phone');
|
||||
|
||||
$schema->hasOne('phone2', 'phones', 'property2');
|
||||
});
|
||||
|
||||
$api->resource('phones', $phonesAdapter, function (Type $schema) {
|
||||
$schema->attribute('number');
|
||||
});
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'relationships' => [
|
||||
'phone2' => [
|
||||
'data' => ['type' => 'phones', 'id' => '2']
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
[
|
||||
'type' => 'phones',
|
||||
'id' => '2',
|
||||
'attributes' => ['number' => '8882'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/phones/2'
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['included']
|
||||
);
|
||||
|
||||
$response = $api->handle(
|
||||
$request->withQueryParams(['include' => 'phone'])
|
||||
);
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'relationships' => [
|
||||
'phone' => [
|
||||
'data' => ['type' => 'phones', 'id' => '1']
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
[
|
||||
'type' => 'phones',
|
||||
'id' => '1',
|
||||
'attributes' => ['number' => '8881'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/phones/1'
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['included']
|
||||
);
|
||||
}
|
||||
|
||||
public function testHasManyRelationshipNotIncludableByDefault()
|
||||
{
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', new MockAdapter(), function (Type $schema) {
|
||||
$schema->hasMany('groups');
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1')
|
||||
->withQueryParams(['include' => 'groups']);
|
||||
|
||||
$this->expectException(BadRequestException::class);
|
||||
$this->expectExceptionMessage('Invalid include [groups]');
|
||||
|
||||
$api->handle($request);
|
||||
}
|
||||
|
||||
public function testHasManyRelationshipNotIncludedByDefault()
|
||||
{
|
||||
$usersAdapter = new MockAdapter([
|
||||
'1' => (object) [
|
||||
'id' => '1',
|
||||
'groups' => [
|
||||
(object) ['id' => '1'],
|
||||
(object) ['id' => '2'],
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$api->resource('users', $usersAdapter, function (Type $schema) {
|
||||
$schema->hasMany('groups');
|
||||
});
|
||||
|
||||
$api->resource('groups', new MockAdapter());
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1');
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$body = json_decode($response->getBody(), true);
|
||||
|
||||
$this->assertArrayNotHasKey('data', $body['data']['relationships']);
|
||||
$this->assertArrayNotHasKey('included', $body);
|
||||
}
|
||||
|
||||
public function testHasManyRelationshipInclusion()
|
||||
{
|
||||
$groupsAdapter = new MockAdapter([
|
||||
'1' => $group1 = (object) ['id' => '1', 'name' => 'Admin'],
|
||||
'2' => $group2 = (object) ['id' => '2', 'name' => 'Mod'],
|
||||
'3' => $group3 = (object) ['id' => '3', 'name' => 'Member'],
|
||||
'4' => $group4 = (object) ['id' => '4', 'name' => 'Guest']
|
||||
]);
|
||||
|
||||
$usersAdapter = new MockAdapter([
|
||||
'1' => $user = (object) [
|
||||
'id' => '1',
|
||||
'property1' => [$group1, $group2],
|
||||
'property2' => [$group3, $group4],
|
||||
]
|
||||
]);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$relationships = [];
|
||||
|
||||
$api->resource('users', $usersAdapter, function (Type $schema) use (&$relationships) {
|
||||
$relationships[] = $schema->hasMany('groups1', 'groups', 'property1')
|
||||
->includable();
|
||||
|
||||
$relationships[] = $schema->hasMany('groups2', 'groups', 'property2')
|
||||
->includable();
|
||||
});
|
||||
|
||||
$api->resource('groups', $groupsAdapter, function (Type $schema) {
|
||||
$schema->attribute('name');
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('GET', '/users/1')
|
||||
->withQueryParams(['include' => 'groups1']);
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertEquals([[$relationships[0]]], $user->load);
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'relationships' => [
|
||||
'groups1' => [
|
||||
'data' => [
|
||||
['type' => 'groups', 'id' => '1'],
|
||||
['type' => 'groups', 'id' => '2']
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
[
|
||||
'type' => 'groups',
|
||||
'id' => '1',
|
||||
'attributes' => ['name' => 'Admin'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/groups/1'
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'groups',
|
||||
'id' => '2',
|
||||
'attributes' => ['name' => 'Mod'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/groups/2'
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['included']
|
||||
);
|
||||
|
||||
$user->load = [];
|
||||
|
||||
$response = $api->handle(
|
||||
$request->withQueryParams(['include' => 'groups2'])
|
||||
);
|
||||
|
||||
$this->assertEquals([[$relationships[1]]], $user->load);
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'relationships' => [
|
||||
'groups2' => [
|
||||
'data' => [
|
||||
['type' => 'groups', 'id' => '3'],
|
||||
['type' => 'groups', 'id' => '4'],
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
[
|
||||
'type' => 'groups',
|
||||
'id' => '3',
|
||||
'attributes' => ['name' => 'Member'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/groups/3'
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'groups',
|
||||
'id' => '4',
|
||||
'attributes' => ['name' => 'Guest'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/groups/4'
|
||||
]
|
||||
],
|
||||
],
|
||||
json_decode($response->getBody(), true)['included']
|
||||
);
|
||||
}
|
||||
|
||||
public function testNestedRelationshipInclusion()
|
||||
{
|
||||
$groupsAdapter = new MockAdapter([
|
||||
'1' => $group1 = (object) ['id' => '1', 'name' => 'Admin'],
|
||||
'2' => $group2 = (object) ['id' => '2', 'name' => 'Mod']
|
||||
]);
|
||||
|
||||
$usersAdapter = new MockAdapter([
|
||||
'1' => $user = (object) ['id' => '1', 'groups' => [$group1, $group2]]
|
||||
]);
|
||||
|
||||
$postsAdapter = new MockAdapter([
|
||||
'1' => $post = (object) ['id' => '1', 'user' => $user]
|
||||
]);
|
||||
|
||||
$api = new JsonApi('http://example.com');
|
||||
|
||||
$relationships = [];
|
||||
|
||||
$api->resource('posts', $postsAdapter, function (Type $schema) use (&$relationships) {
|
||||
$relationships[] = $schema->hasOne('user');
|
||||
});
|
||||
|
||||
$api->resource('users', $usersAdapter, function (Type $schema) use (&$relationships) {
|
||||
$relationships[] = $schema->hasMany('groups')->includable();
|
||||
});
|
||||
|
||||
$api->resource('groups', $groupsAdapter, function (Type $schema) {
|
||||
$schema->attribute('name');
|
||||
});
|
||||
|
||||
$request = $this->buildRequest('GET', '/posts/1')
|
||||
->withQueryParams(['include' => 'user,user.groups']);
|
||||
|
||||
$response = $api->handle($request);
|
||||
|
||||
$this->assertEquals([$relationships[0]], $post->load[0]);
|
||||
$this->assertEquals($relationships, $post->load[1]);
|
||||
|
||||
$this->assertArraySubset(
|
||||
[
|
||||
'relationships' => [
|
||||
'user' => [
|
||||
'data' => ['type' => 'users', 'id' => '1']
|
||||
]
|
||||
]
|
||||
],
|
||||
json_decode($response->getBody(), true)['data']
|
||||
);
|
||||
|
||||
$included = json_decode($response->getBody(), true)['included'];
|
||||
|
||||
// $this->assertContains(
|
||||
// [
|
||||
// 'type' => 'users',
|
||||
// 'id' => '1',
|
||||
// 'relationships' => [
|
||||
// 'groups' => [
|
||||
// 'data' => [
|
||||
// ['type' => 'groups', 'id' => '1'],
|
||||
// ['type' => 'groups', 'id' => '2']
|
||||
// ]
|
||||
// ]
|
||||
// ],
|
||||
// 'links' => [
|
||||
// 'self' => 'http://example.com/users/1'
|
||||
// ]
|
||||
// ],
|
||||
// $included
|
||||
// );
|
||||
|
||||
$this->assertContains(
|
||||
[
|
||||
'type' => 'groups',
|
||||
'id' => '1',
|
||||
'attributes' => ['name' => 'Admin'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/groups/1'
|
||||
]
|
||||
],
|
||||
$included
|
||||
);
|
||||
|
||||
$this->assertContains(
|
||||
[
|
||||
'type' => 'groups',
|
||||
'id' => '2',
|
||||
'attributes' => ['name' => 'Mod'],
|
||||
'links' => [
|
||||
'self' => 'http://example.com/groups/2'
|
||||
]
|
||||
],
|
||||
$included
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,13 +11,28 @@
|
|||
|
||||
namespace Tobyz\Tests\JsonApiServer\feature;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobyz\JsonApiServer\JsonApi;
|
||||
use Tobyz\JsonApiServer\Schema\Type;
|
||||
use Tobyz\Tests\JsonApiServer\AbstractTestCase;
|
||||
use Tobyz\Tests\JsonApiServer\MockAdapter;
|
||||
|
||||
class FieldFiltersTest extends AbstractTestCase
|
||||
{
|
||||
/**
|
||||
* @var JsonApi
|
||||
*/
|
||||
private $api;
|
||||
|
||||
/**
|
||||
* @var MockAdapter
|
||||
*/
|
||||
private $adapter;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
||||
$this->api = new JsonApi('http://example.com');
|
||||
$this->adapter = new MockAdapter();
|
||||
}
|
||||
|
||||
public function test_resources_can_be_filtered_by_id()
|
||||
|
|
@ -53,6 +68,28 @@ class FieldFiltersTest extends AbstractTestCase
|
|||
// to_one, to_many...
|
||||
|
||||
public function test_types_can_have_custom_filters()
|
||||
{
|
||||
$called = false;
|
||||
|
||||
$this->api->resource('users', $this->adapter, function (Type $type) use (&$called) {
|
||||
$type->filter('name', function (...$args) use (&$called) {
|
||||
$this->assertSame($this->adapter->query, $args[0]);
|
||||
$this->assertEquals('value', $args[1]);
|
||||
$this->assertInstanceOf(ServerRequestInterface::class, $args[2]);
|
||||
|
||||
$called = true;
|
||||
});
|
||||
});
|
||||
|
||||
$this->api->handle(
|
||||
$this->buildRequest('GET', '/users')
|
||||
->withQueryParams(['filter' => ['name' => 'value']])
|
||||
);
|
||||
|
||||
$this->assertTrue($called);
|
||||
}
|
||||
|
||||
public function test_types_can_have_a_default_filter()
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,11 @@ class RelationshipInclusionTest extends AbstractTestCase
|
|||
$this->markTestIncomplete();
|
||||
}
|
||||
|
||||
public function test_to_one_relationships_can_be_not_loadable()
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
}
|
||||
|
||||
// to_many...
|
||||
|
||||
public function test_multiple_relationships_can_be_included()
|
||||
|
|
|
|||
|
|
@ -30,30 +30,64 @@ class ScopesTest extends AbstractTestCase
|
|||
*/
|
||||
private $adapter;
|
||||
|
||||
private $scopeWasCalled = false;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->api = new JsonApi('http://example.com');
|
||||
|
||||
$this->adapter = new MockAdapter();
|
||||
$this->scopeWasCalled = false;
|
||||
|
||||
$this->api = new JsonApi('http://example.com');
|
||||
$this->api->resource('users', $this->adapter, function (Type $type) {
|
||||
$type->updatable();
|
||||
$type->deletable();
|
||||
$type->scope(function (...$args) {
|
||||
$this->assertSame($this->adapter->query, $args[0]);
|
||||
$this->assertInstanceOf(ServerRequestInterface::class, $args[1]);
|
||||
$this->scopeWasCalled = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public function test_scopes_are_applied_to_the_resource_listing_query()
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
$this->api->handle(
|
||||
$this->buildRequest('GET', '/users')
|
||||
);
|
||||
|
||||
$this->assertTrue($this->scopeWasCalled);
|
||||
}
|
||||
|
||||
public function test_scopes_are_applied_to_the_show_resource_query()
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
$this->api->handle(
|
||||
$this->buildRequest('GET', '/users/1')
|
||||
);
|
||||
|
||||
$this->assertTrue($this->scopeWasCalled);
|
||||
}
|
||||
|
||||
public function test_scopes_are_applied_to_the_update_resource_query()
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
$this->api->handle(
|
||||
$this->buildRequest('PATCH', '/users/1')
|
||||
->withParsedBody([
|
||||
'data' => [
|
||||
'type' => 'users',
|
||||
'id' => '1'
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertTrue($this->scopeWasCalled);
|
||||
}
|
||||
|
||||
public function test_scopes_are_applied_to_the_delete_resource_query()
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
$this->api->handle(
|
||||
$this->buildRequest('DELETE', '/users/1')
|
||||
);
|
||||
|
||||
$this->assertTrue($this->scopeWasCalled);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue