Skip to content

Commit 4b4d78f

Browse files
authored
Via relationship repositories (#179)
* Via relationship repositories * Apply fixes from StyleCI (#178) * Cache related model * Apply fixes from StyleCI (#180) * Remove support for Laravel 6
1 parent 7601953 commit 4b4d78f

File tree

7 files changed

+173
-32
lines changed

7 files changed

+173
-32
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
fail-fast: true
1515
matrix:
1616
php: [7.4]
17-
laravel: [^6.0, ^7.0]
17+
laravel: [^7.0]
1818

1919
name: P${{ matrix.php }} - L${{ matrix.laravel }}
2020

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
"php": "^7.4",
2222
"ext-json": "*",
2323
"doctrine/dbal": "^2.10",
24-
"illuminate/support": "^6.0|^7.0",
25-
"laravel/ui": "^2.0"
24+
"illuminate/support": "^7.0",
25+
"laravel/ui": "^2.0",
26+
"spatie/once": "^2.2"
2627
},
2728
"require-dev": {
2829
"mockery/mockery": "^1.3",

src/Http/Requests/InteractWithRepositories.php

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,23 +89,6 @@ public function newRepository()
8989
return $repository::resolveWith($repository::newModel());
9090
}
9191

92-
/**
93-
* Check if the route is resolved by the Repository class, or it uses the classical Models.
94-
* @return bool
95-
*/
96-
public function isResolvedByRestify()
97-
{
98-
try {
99-
$this->repository();
100-
101-
return true;
102-
} catch (EntityNotFoundException $e) {
103-
return false;
104-
} catch (UnauthorizedException $e) {
105-
return true;
106-
}
107-
}
108-
10992
/**
11093
* Get a new instance of the repository being requested.
11194
* As a model it could accept either a model instance, a collection or even paginated collection.
@@ -131,7 +114,11 @@ public function newRepositoryWith($model, $uriKey = null)
131114
*/
132115
public function newQueryWithoutScopes($uriKey = null)
133116
{
134-
return $this->model($uriKey)->newQueryWithoutScopes();
117+
if (! $this->isViaRepository()) {
118+
return $this->model($uriKey)->newQueryWithoutScopes();
119+
}
120+
121+
return $this->viaQuery();
135122
}
136123

137124
/**
@@ -164,4 +151,16 @@ public function findModelQuery($repositoryId = null, $uriKey = null)
164151
$repositoryId ?? request('repositoryId')
165152
);
166153
}
154+
155+
public function viaParentModel()
156+
{
157+
$parent = $this->repository($this->viaRepository);
158+
159+
return once(fn () => $parent::newModel()->newQueryWithoutScopes()->whereKey($this->viaRepositoryId)->firstOrFail());
160+
}
161+
162+
public function viaQuery()
163+
{
164+
return $this->viaParentModel()->{$this->viaRelationship}();
165+
}
167166
}

src/Http/Requests/RestifyRequest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,9 @@ public function isStoreRequest()
5757
{
5858
return $this instanceof RepositoryStoreRequest;
5959
}
60+
61+
public function isViaRepository()
62+
{
63+
return $this->viaRepository && $this->viaRepositoryId && $this->viaRelationship;
64+
}
6065
}

src/Repositories/Repository.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Binaryk\LaravelRestify\Traits\InteractWithSearch;
1515
use Binaryk\LaravelRestify\Traits\PerformsQueries;
1616
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
17-
use Illuminate\Database\Eloquent\Builder;
1817
use Illuminate\Database\Eloquent\Model;
1918
use Illuminate\Http\Request;
2019
use Illuminate\Http\Resources\ConditionallyLoadsAttributes;
@@ -180,9 +179,13 @@ public static function newModel(): Model
180179
return new $model;
181180
}
182181

183-
public static function query(): Builder
182+
public static function query(RestifyRequest $request)
184183
{
185-
return static::newModel()->query();
184+
if (! $request->isViaRepository()) {
185+
return static::newModel()->query();
186+
}
187+
188+
return $request->viaQuery();
186189
}
187190

188191
/**
@@ -505,7 +508,11 @@ public function index(RestifyRequest $request)
505508
* @var AbstractPaginator $paginator
506509
*/
507510
$paginator = RepositorySearchService::instance()->search($request, $this)
508-
->paginate($request->perPage ?? (static::$defaultPerPage ?? RestifySearchable::DEFAULT_PER_PAGE));
511+
->paginate(
512+
$request->isViaRepository()
513+
? static::$defaultRelatablePerPage
514+
: ($request->perPage ?? static::$defaultPerPage)
515+
);
509516

510517
$items = $paginator->getCollection()->map(function ($value) {
511518
return static::resolveWith($value);
@@ -532,7 +539,12 @@ public function store(RestifyRequest $request)
532539
$request, $this->resource, $this->storeFields($request)
533540
);
534541

535-
$this->resource->save();
542+
if ($request->isViaRepository()) {
543+
$this->resource = $request->viaQuery()
544+
->save($this->resource);
545+
} else {
546+
$this->resource->save();
547+
}
536548

537549
$this->storeFields($request)->each(fn (Field $field) => $field->invokeAfter($request, $this->resource));
538550
});

src/Services/Search/RepositorySearchService.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use Binaryk\LaravelRestify\Filter;
77
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
88
use Binaryk\LaravelRestify\Repositories\Repository;
9-
use Illuminate\Database\Eloquent\Builder;
109

1110
class RepositorySearchService extends Searchable
1211
{
@@ -16,7 +15,7 @@ public function search(RestifyRequest $request, Repository $repository)
1615
{
1716
$this->repository = $repository;
1817

19-
$query = $this->prepareMatchFields($request, $this->prepareSearchFields($request, $repository::query(), $this->fixedInput), $this->fixedInput);
18+
$query = $this->prepareMatchFields($request, $this->prepareSearchFields($request, $repository::query($request), $this->fixedInput), $this->fixedInput);
2019

2120
$query = $this->applyFilters($request, $repository, $query);
2221

@@ -69,7 +68,7 @@ public function prepareMatchFields(RestifyRequest $request, $query, $extra = [])
6968
return $query;
7069
}
7170

72-
public function prepareOrders(RestifyRequest $request, $query, $extra = []): Builder
71+
public function prepareOrders(RestifyRequest $request, $query, $extra = [])
7372
{
7473
$sort = $request->get('sort', '');
7574

@@ -92,7 +91,7 @@ public function prepareOrders(RestifyRequest $request, $query, $extra = []): Bui
9291
return $query;
9392
}
9493

95-
public function prepareRelations(RestifyRequest $request, $query, $extra = []): Builder
94+
public function prepareRelations(RestifyRequest $request, $query, $extra = [])
9695
{
9796
$relations = array_merge($extra, explode(',', $request->get('with')));
9897

@@ -105,12 +104,12 @@ public function prepareRelations(RestifyRequest $request, $query, $extra = []):
105104
return $query;
106105
}
107106

108-
public function prepareSearchFields(RestifyRequest $request, $query, $extra = []): Builder
107+
public function prepareSearchFields(RestifyRequest $request, $query, $extra = [])
109108
{
110109
$search = $request->get('search', data_get($extra, 'search', ''));
111110
$model = $query->getModel();
112111

113-
$query->where(function (Builder $query) use ($search, $model) {
112+
$query->where(function ($query) use ($search, $model) {
114113
$connectionType = $model->getConnection()->getDriverName();
115114

116115
$canSearchPrimaryKey = is_numeric($search) &&
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Tests\Controllers;
4+
5+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post;
6+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostPolicy;
7+
use Binaryk\LaravelRestify\Tests\Fixtures\User\User;
8+
use Binaryk\LaravelRestify\Tests\IntegrationTest;
9+
use Illuminate\Support\Facades\Gate;
10+
11+
class RelatedIndexControllerTest extends IntegrationTest
12+
{
13+
public function test_can_list_posts_belongs_to_a_user()
14+
{
15+
$this->mockUsers();
16+
$this->mockPosts(1, 10);
17+
18+
$this->mockPosts(
19+
factory(User::class)->create()->id
20+
);
21+
22+
$response = $this->getJson('restify-api/posts?viaRepository=users&viaRepositoryId=1&viaRelationship=posts')
23+
->assertStatus(200);
24+
25+
$this->assertCount(10, $response->json('data'));
26+
}
27+
28+
public function test_can_show_post_belongs_to_a_user()
29+
{
30+
factory(User::class)->create();
31+
factory(User::class)->create();
32+
33+
factory(Post::class)->create([
34+
'user_id' => 2,
35+
'title' => 'First Post',
36+
]);
37+
38+
factory(Post::class)->create([
39+
'user_id' => 1,
40+
'title' => 'Second Post',
41+
]);
42+
43+
$this->getJson('restify-api/posts/1?viaRepository=users&viaRepositoryId=1&viaRelationship=posts')
44+
->assertStatus(404);
45+
46+
$count = $this->getJson('restify-api/posts/2?viaRepository=users&viaRepositoryId=1&viaRelationship=posts')
47+
->assertStatus(200);
48+
49+
$this->assertCount(1, $count->json());
50+
}
51+
52+
public function test_can_store_post_belongs_to_a_user()
53+
{
54+
factory(User::class)->create();
55+
56+
factory(User::class)->create();
57+
58+
$this->postJson('restify-api/posts?viaRepository=users&viaRepositoryId=1&viaRelationship=posts', [
59+
'title' => 'Created for the user 1',
60+
])
61+
->assertStatus(201);
62+
63+
$belongsFirst = $this->getJson('restify-api/posts?viaRepository=users&viaRepositoryId=1&viaRelationship=posts')
64+
->assertStatus(200);
65+
66+
$belongsSecond = $this->getJson('restify-api/posts?viaRepository=users&viaRepositoryId=2&viaRelationship=posts')
67+
->assertStatus(200);
68+
69+
$this->assertCount(1, $belongsFirst->json('data'));
70+
$this->assertCount(0, $belongsSecond->json('data'));
71+
}
72+
73+
public function test_can_update_post_belongs_to_a_user()
74+
{
75+
factory(User::class)->create();
76+
factory(User::class)->create();
77+
78+
factory(Post::class)->create(['title' => 'Post title', 'user_id' => 1]);
79+
80+
factory(Post::class)->create(['title' => 'Post title', 'user_id' => 2]);
81+
82+
$response = $this->putJson('restify-api/posts/1?viaRepository=users&viaRepositoryId=1&viaRelationship=posts', [
83+
'title' => 'Post updated title',
84+
])->assertStatus(200);
85+
86+
$this->putJson('restify-api/posts/2?viaRepository=users&viaRepositoryId=1&viaRelationship=posts', [
87+
'title' => 'Post updated title',
88+
])->assertStatus(404);
89+
90+
$this->assertEquals('Post updated title', $response->json('data.attributes.title'));
91+
}
92+
93+
public function test_can_destroy_post_belongs_to_a_user()
94+
{
95+
factory(User::class)->create();
96+
factory(User::class)->create();
97+
98+
factory(Post::class)->create(['title' => 'Post title', 'user_id' => 1]);
99+
100+
factory(Post::class)->create(['title' => 'Post title', 'user_id' => 2]);
101+
102+
$this->deleteJson('restify-api/posts/1?viaRepository=users&viaRepositoryId=1&viaRelationship=posts')->assertStatus(204);
103+
104+
$this->deleteJson('restify-api/posts/2?viaRepository=users&viaRepositoryId=1&viaRelationship=posts')->assertStatus(404);
105+
}
106+
107+
public function test_policy_check_before_destroy_post_belongs_to_a_user()
108+
{
109+
$_SERVER['restify.post.deletable'] = false;
110+
111+
Gate::policy(Post::class, PostPolicy::class);
112+
113+
factory(User::class)->create();
114+
115+
factory(User::class)->create();
116+
117+
factory(Post::class)->create(['title' => 'Post title', 'user_id' => 1]);
118+
119+
factory(Post::class)->create(['title' => 'Post title', 'user_id' => 2]);
120+
121+
$this->deleteJson('restify-api/posts/1?viaRepository=users&viaRepositoryId=1&viaRelationship=posts')->assertStatus(403);
122+
123+
$this->deleteJson('restify-api/posts/2?viaRepository=users&viaRepositoryId=1&viaRelationship=posts')->assertStatus(404);
124+
}
125+
}

0 commit comments

Comments
 (0)