Skip to content

Commit 08c931e

Browse files
[Feature] Support encoded resource ids (#55)
Adds support for encoding and decoding resource IDs.
1 parent 66b2b45 commit 08c931e

34 files changed

+333
-135
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"laravel/framework": "^8.0"
3535
},
3636
"require-dev": {
37+
"laravel-json-api/hashids": "dev-develop",
3738
"laravel-json-api/testing": "^1.0.0-alpha.1",
3839
"orchestra/testbench": "^6.9",
3940
"phpunit/phpunit": "^9.5"

src/Routing/Route.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,43 @@ public function relation(): Relation
210210
*/
211211
public function substituteBindings(): void
212212
{
213+
if ($this->hasSubstitutedBindings()) {
214+
$this->checkBinding();
215+
return;
216+
}
217+
213218
if ($this->hasResourceId()) {
214219
$this->setModel($this->schema()->repository()->find(
215220
$this->resourceId()
216221
));
217222
}
218223
}
219224

225+
/**
226+
* Has the model binding already been substituted?
227+
*
228+
* In a normal Laravel application setup, the `api` middleware group will
229+
* include Laravel's binding substitution middleware. This means that
230+
* typically the boot JSON:API middleware will run *after* bindings have been
231+
* substituted.
232+
*
233+
* If the route that is being executed has type-hinted the model, this means
234+
* the model will already be substituted into the route. For example, this
235+
* can occur if the developer has written their own controller action, or
236+
* for custom actions.
237+
*
238+
* @return bool
239+
*/
240+
private function hasSubstitutedBindings(): bool
241+
{
242+
if ($name = $this->resourceIdName()) {
243+
$expected = $this->schema()->model();
244+
return $this->route->parameter($name) instanceof $expected;
245+
}
246+
247+
return false;
248+
}
249+
220250
/**
221251
* @param object|null $model
222252
* @return void
@@ -235,6 +265,30 @@ private function setModel(?object $model): void
235265
throw new NotFoundHttpException();
236266
}
237267

268+
/**
269+
* Check the model that has already been substituted.
270+
*
271+
* If Laravel has substituted bindings before the JSON:API binding substitution
272+
* is triggered, we need to check that the model that has been set on the route
273+
* by Laravel does exist in our API. This is because the API's existence logic
274+
* may not match the route binding query that Laravel executed to substitute
275+
* the binding. E.g. if the developer has applied global scopes in the Server's
276+
* `serving()` method, these global scopes may have been applied *after* the
277+
* binding was substituted.
278+
*
279+
* @return void
280+
*/
281+
private function checkBinding(): void
282+
{
283+
$resourceId = $this->server->resources()->create(
284+
$this->model(),
285+
)->id();
286+
287+
if (!$this->schema()->repository()->exists($resourceId)) {
288+
throw new NotFoundHttpException();
289+
}
290+
}
291+
238292
/**
239293
* @return string|null
240294
*/

stubs/schema.stub

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use {{ namespacedModel }};
66
use LaravelJsonApi\Eloquent\Contracts\Paginator;
77
use LaravelJsonApi\Eloquent\Fields\DateTime;
88
use LaravelJsonApi\Eloquent\Fields\ID;
9-
use LaravelJsonApi\Eloquent\Filters\WhereIn;
9+
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
1010
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
1111
use LaravelJsonApi\Eloquent\{{ schema }};
1212

@@ -42,7 +42,7 @@ class {{ class }} extends {{ schema }}
4242
public function filters(): array
4343
{
4444
return [
45-
WhereIn::make('id', $this->idColumn()),
45+
WhereIdIn::make($this),
4646
];
4747
}
4848

tests/dummy/app/JsonApi/V1/Comments/CommentSchema.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@
2525
use LaravelJsonApi\Eloquent\Fields\ID;
2626
use LaravelJsonApi\Eloquent\Fields\Relations\BelongsTo;
2727
use LaravelJsonApi\Eloquent\Fields\Str;
28-
use LaravelJsonApi\Eloquent\Filters\WhereIn;
28+
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
2929
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
3030
use LaravelJsonApi\Eloquent\Schema;
31+
use LaravelJsonApi\HashIds\HashId;
3132

3233
class CommentSchema extends Schema
3334
{
@@ -45,7 +46,7 @@ class CommentSchema extends Schema
4546
public function fields(): array
4647
{
4748
return [
48-
ID::make(),
49+
HashId::make()->alreadyEncoded(),
4950
Str::make('content'),
5051
DateTime::make('createdAt')->sortable()->readOnly(),
5152
BelongsTo::make('post'),
@@ -60,7 +61,7 @@ public function fields(): array
6061
public function filters(): array
6162
{
6263
return [
63-
WhereIn::make('id', $this->idColumn())->delimiter(','),
64+
WhereIdIn::make($this)->delimiter(','),
6465
];
6566
}
6667

tests/dummy/app/JsonApi/V1/Images/ImageSchema.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
use LaravelJsonApi\Eloquent\Fields\DateTime;
2525
use LaravelJsonApi\Eloquent\Fields\ID;
2626
use LaravelJsonApi\Eloquent\Fields\Str;
27-
use LaravelJsonApi\Eloquent\Filters\WhereIn;
27+
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
2828
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
2929
use LaravelJsonApi\Eloquent\Schema;
3030

@@ -57,7 +57,7 @@ public function fields(): array
5757
public function filters(): array
5858
{
5959
return [
60-
WhereIn::make('id', $this->idColumn())->delimiter(','),
60+
WhereIdIn::make($this)->delimiter(','),
6161
];
6262
}
6363

tests/dummy/app/JsonApi/V1/Posts/PostSchema.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
use App\Models\Post;
2323
use LaravelJsonApi\Eloquent\Contracts\Paginator;
2424
use LaravelJsonApi\Eloquent\Fields\DateTime;
25-
use LaravelJsonApi\Eloquent\Fields\ID;
2625
use LaravelJsonApi\Eloquent\Fields\Relations\BelongsTo;
2726
use LaravelJsonApi\Eloquent\Fields\Relations\BelongsToMany;
2827
use LaravelJsonApi\Eloquent\Fields\Relations\HasMany;
@@ -32,10 +31,11 @@
3231
use LaravelJsonApi\Eloquent\Filters\OnlyTrashed;
3332
use LaravelJsonApi\Eloquent\Filters\Scope;
3433
use LaravelJsonApi\Eloquent\Filters\Where;
35-
use LaravelJsonApi\Eloquent\Filters\WhereIn;
34+
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
3635
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
3736
use LaravelJsonApi\Eloquent\Schema;
3837
use LaravelJsonApi\Eloquent\SoftDeletes;
38+
use LaravelJsonApi\HashIds\HashId;
3939

4040
class PostSchema extends Schema
4141
{
@@ -62,7 +62,7 @@ class PostSchema extends Schema
6262
public function fields(): array
6363
{
6464
return [
65-
ID::make(),
65+
HashId::make()->alreadyEncoded(),
6666
BelongsTo::make('author')->type('users')->readOnly(),
6767
HasMany::make('comments')->readOnly(),
6868
Str::make('content'),
@@ -87,7 +87,7 @@ public function fields(): array
8787
public function filters(): array
8888
{
8989
return [
90-
WhereIn::make('id', $this->idColumn())->delimiter(','),
90+
WhereIdIn::make($this)->delimiter(','),
9191
Scope::make('published', 'wherePublished')->asBoolean(),
9292
Where::make('slug')->singular(),
9393
OnlyTrashed::make('trashed'),

tests/dummy/app/JsonApi/V1/Tags/TagSchema.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
use App\Models\Tag;
2323
use LaravelJsonApi\Eloquent\Contracts\Paginator;
2424
use LaravelJsonApi\Eloquent\Fields\DateTime;
25-
use LaravelJsonApi\Eloquent\Fields\ID;
2625
use LaravelJsonApi\Eloquent\Fields\Relations\BelongsToMany;
2726
use LaravelJsonApi\Eloquent\Fields\Str;
28-
use LaravelJsonApi\Eloquent\Filters\WhereIn;
27+
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
2928
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
3029
use LaravelJsonApi\Eloquent\Schema;
30+
use LaravelJsonApi\HashIds\HashId;
3131

3232
class TagSchema extends Schema
3333
{
@@ -45,7 +45,7 @@ class TagSchema extends Schema
4545
public function fields(): array
4646
{
4747
return [
48-
ID::make(),
48+
HashId::make()->alreadyEncoded(),
4949
DateTime::make('createdAt')->sortable()->readOnly(),
5050
Str::make('name')->sortable(),
5151
BelongsToMany::make('posts')
@@ -64,7 +64,7 @@ public function fields(): array
6464
public function filters(): array
6565
{
6666
return [
67-
WhereIn::make('id', $this->idColumn())->delimiter(','),
67+
WhereIdIn::make($this)->delimiter(','),
6868
];
6969
}
7070

tests/dummy/app/JsonApi/V1/Users/UserSchema.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@
2222
use App\Models\User;
2323
use LaravelJsonApi\Eloquent\Contracts\Paginator;
2424
use LaravelJsonApi\Eloquent\Fields\DateTime;
25-
use LaravelJsonApi\Eloquent\Fields\ID;
2625
use LaravelJsonApi\Eloquent\Fields\Str;
2726
use LaravelJsonApi\Eloquent\Filters\Where;
28-
use LaravelJsonApi\Eloquent\Filters\WhereIn;
27+
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
2928
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
3029
use LaravelJsonApi\Eloquent\Schema;
30+
use LaravelJsonApi\HashIds\HashId;
3131

3232
class UserSchema extends Schema
3333
{
@@ -45,7 +45,7 @@ class UserSchema extends Schema
4545
public function fields(): array
4646
{
4747
return [
48-
ID::make(),
48+
HashId::make()->alreadyEncoded(),
4949
DateTime::make('createdAt')->readOnly(),
5050
Str::make('name'),
5151
DateTime::make('updatedAt')->readOnly(),
@@ -58,7 +58,7 @@ public function fields(): array
5858
public function filters(): array
5959
{
6060
return [
61-
WhereIn::make('id', $this->idColumn())->delimiter(','),
61+
WhereIdIn::make($this)->delimiter(','),
6262
Where::make('email')->singular(),
6363
];
6464
}

tests/dummy/app/JsonApi/V1/Videos/VideoSchema.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
use LaravelJsonApi\Eloquent\Fields\ID;
2626
use LaravelJsonApi\Eloquent\Fields\Relations\BelongsToMany;
2727
use LaravelJsonApi\Eloquent\Fields\Str;
28-
use LaravelJsonApi\Eloquent\Filters\WhereIn;
28+
use LaravelJsonApi\Eloquent\Filters\WhereIdIn;
2929
use LaravelJsonApi\Eloquent\Pagination\PagePagination;
3030
use LaravelJsonApi\Eloquent\Schema;
3131

@@ -60,7 +60,7 @@ public function fields(): array
6060
public function filters(): array
6161
{
6262
return [
63-
WhereIn::make('id', $this->idColumn())->delimiter(','),
63+
WhereIdIn::make($this)->delimiter(','),
6464
];
6565
}
6666

tests/dummy/app/Models/Comment.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Comment extends Model
2727
{
2828

2929
use HasFactory;
30+
use Concerns\HashRouteKey;
3031

3132
/**
3233
* @var string[]

0 commit comments

Comments
 (0)