Skip to content

Commit 9ee46b3

Browse files
committed
wip
1 parent 602a6cc commit 9ee46b3

29 files changed

+831
-204
lines changed

src/Adapter/AdapterInterface.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,5 @@ public function sortByAttribute($query, Attribute $attribute, string $direction)
4444

4545
public function paginate($query, int $limit, int $offset);
4646

47-
public function include($query, array $relationships);
48-
49-
public function load($model, array $relationships);
47+
public function load(array $models, array $relationships);
5048
}

src/Adapter/EloquentAdapter.php

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
use Illuminate\Database\Eloquent\Collection;
66
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
78
use Tobscure\JsonApiServer\Schema\Attribute;
89
use Tobscure\JsonApiServer\Schema\HasMany;
910
use Tobscure\JsonApiServer\Schema\HasOne;
11+
use Tobscure\JsonApiServer\Schema\Relationship;
1012

1113
class EloquentAdapter implements AdapterInterface
1214
{
@@ -44,34 +46,60 @@ public function get($query): array
4446
return $query->get()->all();
4547
}
4648

49+
public function count($query): int
50+
{
51+
return $query->count();
52+
}
53+
4754
public function getId($model): string
4855
{
4956
return $model->getKey();
5057
}
5158

5259
public function getAttribute($model, Attribute $field)
5360
{
54-
return $model->{$field->property};
61+
return $model->{$this->getAttributeProperty($field)};
62+
}
63+
64+
public function getHasOneId($model, HasOne $field)
65+
{
66+
$relation = $model->{$this->getRelationshipProperty($field)}();
67+
68+
if ($relation instanceof BelongsTo) {
69+
$related = $relation->getRelated();
70+
71+
$key = $model->{$relation->getForeignKeyName()};
72+
73+
if ($key) {
74+
return $related->forceFill([$related->getKeyName() => $key]);
75+
}
76+
77+
return null;
78+
}
79+
80+
return $model->{$this->getRelationshipProperty($field)};
5581
}
5682

5783
public function getHasOne($model, HasOne $field)
5884
{
59-
return $model->{$field->property};
85+
return $model->{$this->getRelationshipProperty($field)};
6086
}
6187

6288
public function getHasMany($model, HasMany $field): array
6389
{
64-
return $model->{$field->property}->all();
90+
$collection = $model->{$this->getRelationshipProperty($field)};
91+
92+
return $collection ? $collection->all() : [];
6593
}
6694

6795
public function applyAttribute($model, Attribute $field, $value)
6896
{
69-
$model->{$field->property} = $value;
97+
$model->{$this->getAttributeProperty($field)} = $value;
7098
}
7199

72100
public function applyHasOne($model, HasOne $field, $related)
73101
{
74-
$model->{$field->property}()->associate($related);
102+
$model->{$this->getRelationshipProperty($field)}()->associate($related);
75103
}
76104

77105
public function save($model)
@@ -81,7 +109,7 @@ public function save($model)
81109

82110
public function saveHasMany($model, HasMany $field, array $related)
83111
{
84-
$model->{$field->property}()->sync(Collection::make($related));
112+
$model->{$this->getRelationshipProperty($field)}()->sync(Collection::make($related));
85113
}
86114

87115
public function delete($model)
@@ -91,20 +119,20 @@ public function delete($model)
91119

92120
public function filterByAttribute($query, Attribute $field, $value)
93121
{
94-
$query->where($field->property, $value);
122+
$query->where($this->getAttributeProperty($field), $value);
95123
}
96124

97125
public function filterByHasOne($query, HasOne $field, array $ids)
98126
{
99-
$property = $field->property;
127+
$property = $this->getRelationshipProperty($field);
100128
$foreignKey = $query->getModel()->{$property}()->getQualifiedForeignKey();
101129

102130
$query->whereIn($foreignKey, $ids);
103131
}
104132

105133
public function filterByHasMany($query, HasMany $field, array $ids)
106134
{
107-
$property = $field->property;
135+
$property = $this->getRelationshipProperty($field);
108136
$relation = $query->getModel()->{$property}();
109137
$relatedKey = $relation->getRelated()->getQualifiedKeyName();
110138

@@ -115,28 +143,56 @@ public function filterByHasMany($query, HasMany $field, array $ids)
115143

116144
public function sortByAttribute($query, Attribute $field, string $direction)
117145
{
118-
$query->orderBy($field->property, $direction);
146+
$query->orderBy($this->getAttributeProperty($field), $direction);
119147
}
120148

121149
public function paginate($query, int $limit, int $offset)
122150
{
123151
$query->take($limit)->skip($offset);
124152
}
125153

126-
public function include($query, array $trail)
154+
public function load(array $models, array $trail)
155+
{
156+
(new Collection($models))->load($this->relationshipTrailToPath($trail));
157+
}
158+
159+
public function loadIds(array $models, Relationship $relationship)
160+
{
161+
if (empty($models)) {
162+
return;
163+
}
164+
165+
$property = $this->getRelationshipProperty($relationship);
166+
$relation = $models[0]->$property();
167+
168+
if ($relation instanceof BelongsTo) {
169+
return;
170+
}
171+
172+
(new Collection($models))->load([
173+
$property => function ($query) use ($relation) {
174+
$query->select([
175+
$relation->getRelated()->getKeyName(),
176+
$relation->getForeignKeyName()
177+
]);
178+
}
179+
]);
180+
}
181+
182+
private function getAttributeProperty(Attribute $field)
127183
{
128-
$query->with($this->relationshipTrailToPath($trail));
184+
return $field->property ?: strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $field->name));
129185
}
130186

131-
public function load($model, array $trail)
187+
private function getRelationshipProperty(Relationship $field)
132188
{
133-
$model->load($this->relationshipTrailToPath($trail));
189+
return $field->property ?: $field->name;
134190
}
135191

136192
private function relationshipTrailToPath(array $trail)
137193
{
138194
return implode('.', array_map(function ($relationship) {
139-
return $relationship->property;
195+
return $this->getRelationshipProperty($relationship);
140196
}, $trail));
141197
}
142198
}

src/Api.php

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
use Psr\Http\Message\ResponseInterface as Response;
88
use Psr\Http\Message\ServerRequestInterface as Request;
99
use Psr\Http\Server\RequestHandlerInterface;
10+
use Tobscure\JsonApiServer\Exception\BadRequestException;
1011
use Tobscure\JsonApiServer\Exception\MethodNotAllowedException;
12+
use Tobscure\JsonApiServer\Exception\NotImplementedException;
1113
use Tobscure\JsonApiServer\Exception\ResourceNotFoundException;
1214
use Tobscure\JsonApiServer\Handler\Concerns\FindsResources;
1315

@@ -51,10 +53,10 @@ public function handle(Request $request): Response
5153
if ($count === 1) {
5254
switch ($request->getMethod()) {
5355
case 'GET':
54-
return (new Handler\Index($this, $resource))->handle($request);
56+
return $this->handleWithHandler($request, new Handler\Index($this, $resource));
5557

5658
case 'POST':
57-
return (new Handler\Create($this, $resource))->handle($request);
59+
return $this->handleWithHandler($request, new Handler\Create($this, $resource));
5860

5961
default:
6062
throw new MethodNotAllowedException;
@@ -66,28 +68,32 @@ public function handle(Request $request): Response
6668
if ($count === 2) {
6769
switch ($request->getMethod()) {
6870
case 'PATCH':
69-
return (new Handler\Update($this, $resource, $model))->handle($request);
71+
return $this->handleWithHandler($request, new Handler\Update($this, $resource, $model));
7072

7173
case 'GET':
72-
return (new Handler\Show($this, $resource, $model))->handle($request);
74+
return $this->handleWithHandler($request, new Handler\Show($this, $resource, $model));
7375

7476
case 'DELETE':
75-
return (new Handler\Delete($resource, $model))->handle($request);
77+
return $this->handleWithHandler($request, new Handler\Delete($resource, $model));
7678

7779
default:
7880
throw new MethodNotAllowedException;
7981
}
8082
}
8183

82-
// if ($count === 3) {
83-
// return $this->handleRelated($request, $resource, $model, $segments[2]);
84-
// }
84+
if ($count === 3) {
85+
throw new NotImplementedException;
8586

86-
// if ($count === 4 && $segments[2] === 'relationship') {
87-
// return $this->handleRelationship($request, $resource, $model, $segments[3]);
88-
// }
87+
// return $this->handleRelated($request, $resource, $model, $segments[2]);
88+
}
89+
90+
if ($count === 4 && $segments[2] === 'relationships') {
91+
throw new NotImplementedException;
92+
93+
// return $this->handleRelationship($request, $resource, $model, $segments[3]);
94+
}
8995

90-
throw new \RuntimeException;
96+
throw new BadRequestException;
9197
}
9298

9399
private function stripBasePath(string $path): string
@@ -103,13 +109,23 @@ private function stripBasePath(string $path): string
103109
return $path;
104110
}
105111

106-
public function error(\Throwable $e)
112+
private function handleWithHandler(Request $request, RequestHandlerInterface $handler)
107113
{
114+
$request = $request->withAttribute('jsonApiHandler', $handler);
115+
116+
return $handler->handle($request);
117+
}
118+
119+
public function handleError($e)
120+
{
121+
if (! $e instanceof ErrorProviderInterface) {
122+
$e = new Exception\InternalServerErrorException;
123+
}
124+
125+
$errors = $e->getJsonApiErrors();
126+
108127
$data = new JsonApi\ErrorDocument(
109-
new JsonApi\Error(
110-
new JsonApi\Error\Title($e->getMessage()),
111-
new JsonApi\Error\Detail((string) $e)
112-
)
128+
...$errors
113129
);
114130

115131
return new JsonApiResponse($data);

src/ErrorProviderInterface.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Tobscure\JsonApiServer;
4+
5+
interface ErrorProviderInterface
6+
{
7+
public function getJsonApiErrors(): array;
8+
}

src/Exception/BadRequestException.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,41 @@
22

33
namespace Tobscure\JsonApiServer\Exception;
44

5-
class BadRequestException extends \DomainException
5+
use JsonApiPhp\JsonApi\Error;
6+
use Tobscure\JsonApiServer\ErrorProviderInterface;
7+
8+
class BadRequestException extends \DomainException implements ErrorProviderInterface
69
{
10+
/**
11+
* @var string
12+
*/
13+
private $sourceParameter;
14+
15+
public function __construct(string $message = '', string $sourceParameter = '')
16+
{
17+
parent::__construct($message);
18+
19+
$this->sourceParameter = $sourceParameter;
20+
}
21+
22+
public function getJsonApiErrors(): array
23+
{
24+
$members = [];
25+
26+
if ($this->message) {
27+
$members[] = new Error\Detail($this->message);
28+
}
29+
30+
if ($this->sourceParameter) {
31+
$members[] = new Error\SourceParameter($this->sourceParameter);
32+
}
33+
34+
return [
35+
new Error(
36+
new Error\Title('Bad Request'),
37+
new Error\Status('400'),
38+
...$members
39+
)
40+
];
41+
}
742
}

src/Exception/ForbiddenException.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
namespace Tobscure\JsonApiServer\Exception;
44

5-
class ForbiddenException extends \DomainException
5+
use JsonApiPhp\JsonApi\Error;
6+
use Tobscure\JsonApiServer\ErrorProviderInterface;
7+
8+
class BadRequestException extends \DomainException implements ErrorProviderInterface
69
{
10+
public function getJsonApiErrors(): array
11+
{
12+
return [
13+
new Error(
14+
new Error\Title('Forbidden'),
15+
new Error\Status('403')
16+
)
17+
];
18+
}
719
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Tobscure\JsonApiServer\Exception;
4+
5+
use JsonApiPhp\JsonApi\Error;
6+
use Tobscure\JsonApiServer\ErrorProviderInterface;
7+
8+
class InternalServerErrorException extends \RuntimeException implements ErrorProviderInterface
9+
{
10+
public function getJsonApiErrors(): array
11+
{
12+
return [
13+
new Error(
14+
new Error\Title('Internal Server Error'),
15+
new Error\Status('500')
16+
)
17+
];
18+
}
19+
}

src/Exception/MethodNotAllowedException.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22

33
namespace Tobscure\JsonApiServer\Exception;
44

5-
use Exception;
5+
use JsonApiPhp\JsonApi\Error;
6+
use Tobscure\JsonApiServer\ErrorProviderInterface;
67

7-
class MethodNotAllowedException extends \DomainException
8+
class BadRequestException extends \DomainException implements ErrorProviderInterface
89
{
9-
public function __construct($message = null, $code = 405, Exception $previous = null)
10+
public function getJsonApiErrors(): array
1011
{
11-
parent::__construct($message, $code, $previous);
12+
return [
13+
new Error(
14+
new Error\Title('Method Not Allowed'),
15+
new Error\Status('405')
16+
)
17+
];
1218
}
1319
}

0 commit comments

Comments
 (0)