Skip to content

Commit f4cc11d

Browse files
authored
feat: add nested api resource controller (#105)
1 parent b9836c4 commit f4cc11d

File tree

4 files changed

+87
-33
lines changed

4 files changed

+87
-33
lines changed

src/Generator/ApiControllerMakeCommand.php

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,6 @@ class ApiControllerMakeCommand extends ControllerMakeCommand
2323
*/
2424
protected $description = 'Create a new controller class';
2525

26-
protected $customClasses = [
27-
'request' => false,
28-
'rawName' => null,
29-
'resources' => false,
30-
'resourceSingle' => null,
31-
'resourceCollection' => null,
32-
];
3326

3427
public function handle()
3528
{
@@ -46,7 +39,7 @@ public function handle()
4639
}
4740

4841

49-
protected function qualifyResource(string $resource)
42+
protected function qualifyResource(string $resource): string
5043
{
5144
$resource = ltrim($resource, '\\/');
5245

@@ -65,7 +58,10 @@ protected function qualifyResource(string $resource)
6558

6659
protected function buildModelReplacements(array $replace)
6760
{
68-
$modelClass = $this->parseModel($this->option('model'));
61+
/** @var string $model */
62+
$model = $this->option('model');
63+
64+
$modelClass = $this->parseModel($model);
6965

7066
if (! class_exists($modelClass)) {
7167
if ($this->confirm("A {$modelClass} model does not exist. Do you want to generate it?", true)) {
@@ -79,14 +75,14 @@ protected function buildModelReplacements(array $replace)
7975
}
8076
}
8177

82-
$resourceClass = $this->qualifyResource($this->option('model') . 'Resource');
78+
$resourceClass = $this->qualifyResource($model . 'Resource');
8379
if (! class_exists($resourceClass)) {
84-
$this->call('make:api:resource', ['name' => $this->option('model') . 'Resource']);
80+
$this->call('make:api:resource', ['name' => $model . 'Resource']);
8581
}
8682

87-
$resourceCollection = $this->qualifyResource($this->option('model') . 'Collection');
83+
$resourceCollection = $this->qualifyResource($model . 'Collection');
8884
if (! class_exists($resourceCollection)) {
89-
$this->call('make:api:resource', ['name' => $this->option('model') . 'Collection']);
85+
$this->call('make:api:resource', ['name' => $model . 'Collection']);
9086
}
9187

9288
$replace = $this->buildFormRequestReplacements($replace, $modelClass);
@@ -150,7 +146,7 @@ protected function getDefaultNamespace($rootNamespace)
150146
}
151147

152148

153-
protected function addRoutes()
149+
protected function addRoutes(): void
154150
{
155151
$stub = $this->resolveStubPath('route.stub');
156152
$routesFile = app_path(config('laravel-api-controller.routes_file'));
@@ -174,11 +170,14 @@ protected function addRoutes()
174170
$this->error('could not read routes file, add the following to your routes file:');
175171
$this->info("\n".$stub."\n");
176172

177-
return false;
173+
return;
178174
}
179175

180176
// read file
181177
$lines = file($routesFile);
178+
if($lines === false){
179+
return;
180+
}
182181

183182
$lastLine = trim($lines[count($lines) - 1]);
184183
// modify file
@@ -195,7 +194,7 @@ protected function addRoutes()
195194
$this->error('could not read routes file, add the following to your routes file:');
196195
$this->info("\n".$stub."\n");
197196

198-
return false;
197+
return;
199198
}
200199
fwrite($fileResource, implode('', $lines));
201200
fclose($fileResource);

src/Generator/stubs/controller.nested.api.stub

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,19 @@
33
namespace {{ namespace }};
44

55
use {{ namespacedModel }};
6-
use {{ rootNamespace }}Http\Controllers\Controller;
7-
use Illuminate\Http\Request;
6+
use Phpsa\LaravelApiController\Http\Api\Controller;
7+
use {{ namespacedRequests }}
8+
use {{ useResourceSingle }};
9+
use {{ useResourceCollection }};
810
use {{ namespacedParentModel }};
911

1012
class {{ class }} extends Controller
1113
{
14+
15+
protected string $resourceModel = {{ model }}::class;
16+
protected $resourceSingle = {{ resourceSingle }}::class;
17+
protected $resourceCollection = {{ resourceCollection }}::class;
18+
protected string $parentModel = Viewing::class;
1219
/**
1320
* Display a listing of the resource.
1421
*
@@ -17,19 +24,19 @@ class {{ class }} extends Controller
1724
*/
1825
public function index({{ parentModel }} ${{ parentModelVariable }})
1926
{
20-
//
27+
return $this->handleIndexAction();
2128
}
2229

2330
/**
2431
* Store a newly created resource in storage.
2532
*
26-
* @param \Illuminate\Http\Request $request
33+
* @param \{{ namespacedStoreRequest }} $request
2734
* @param \{{ namespacedParentModel }} ${{ parentModelVariable }}
2835
* @return \Illuminate\Http\Response
2936
*/
30-
public function store(Request $request, {{ parentModel }} ${{ parentModelVariable }})
37+
public function store({{ storeRequest }} $request, {{ parentModel }} ${{ parentModelVariable }})
3138
{
32-
//
39+
return $this->handleStoreAction($request);
3340
}
3441

3542
/**
@@ -41,20 +48,20 @@ class {{ class }} extends Controller
4148
*/
4249
public function show({{ parentModel }} ${{ parentModelVariable }}, {{ model }} ${{ modelVariable }})
4350
{
44-
//
51+
return $this->handleShowAction(${{ modelVariable }});
4552
}
4653

4754
/**
4855
* Update the specified resource in storage.
4956
*
50-
* @param \Illuminate\Http\Request $request
57+
* @param \{{ namespacedUpdateRequest }} $request
5158
* @param \{{ namespacedParentModel }} ${{ parentModelVariable }}
5259
* @param \{{ namespacedModel }} ${{ modelVariable }}
5360
* @return \Illuminate\Http\Response
5461
*/
55-
public function update(Request $request, {{ parentModel }} ${{ parentModelVariable }}, {{ model }} ${{ modelVariable }})
62+
public function update({{ updateRequest }} $request, {{ parentModel }} ${{ parentModelVariable }}, {{ model }} ${{ modelVariable }})
5663
{
57-
//
64+
return $this->handleUpdateAction(${{ modelVariable }}, $request);
5865
}
5966

6067
/**
@@ -66,6 +73,6 @@ class {{ class }} extends Controller
6673
*/
6774
public function destroy({{ parentModel }} ${{ parentModelVariable }}, {{ model }} ${{ modelVariable }})
6875
{
69-
//
76+
return $this->handleDestroyAction(${{ modelVariable }});
7077
}
7178
}

src/Http/Api/Contracts/HasParser.php

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Phpsa\LaravelApiController\Http\Api\Contracts;
44

55
use Illuminate\Support\Collection;
6+
use Illuminate\Database\Eloquent\Model;
67
use Phpsa\LaravelApiController\Helpers;
78
use Phpsa\LaravelApiController\UriParser;
89

@@ -34,16 +35,63 @@ protected function getUriParser()
3435
* @param mixed $request
3536
* @param array $extraParams
3637
*/
37-
protected function addCustomParams(array $extraParams = []): void
38+
protected function addCustomParams(array $extraParams = []): void
3839
{
3940
$this->originalQueryParams = $this->request->query();
4041

4142
$all = $this->request->all();
42-
$new = Helpers::array_merge_request($all, $extraParams);
43+
$new = Helpers::array_merge_request($all, $extraParams, $this->filterByParent());
4344
$this->request->replace($new);
4445
}
4546

4647

48+
protected function filterByParent(): array
49+
{
50+
$parent = $this->parentModel ?? null;
51+
if($parent === null){
52+
return [];
53+
}
54+
55+
if(is_array($parent)) {
56+
$key = key($parent);
57+
$param = reset($parent);
58+
}else{
59+
$key = strtolower(class_basename($parent));
60+
$param = $key;
61+
}
62+
63+
$routeRelation = $this->request->route()->parameter($param);
64+
65+
$child = resolve($this->model());
66+
67+
if(!$routeRelation instanceof Model){
68+
$bindingField = $this->request->route()->bindingFieldFor($param) ?? $child->{$key}()->getRelated()->getKeyName();
69+
$routeRelation = $child->{$key}()->getRelated()->where($bindingField, $routeRelation)->firstOrFail();
70+
}
71+
72+
$this->authoriseUserAction('view', $routeRelation);
73+
74+
if($this->request->isMethod('get') || $this->request->isMethod('options'))
75+
{
76+
return [
77+
'filter' => [
78+
$child->{$key}()->getForeignKeyName() => $routeRelation->getKey()
79+
]
80+
];
81+
}
82+
83+
if($this->request->isMethod('post') || $this->request->isMethod('put') || $this->request->isMethod('patch'))
84+
{
85+
return [
86+
$child->{$key}()->getForeignKeyName() => $routeRelation->getKey()
87+
];
88+
}
89+
90+
return [];
91+
92+
}
93+
94+
4795
/**
4896
* Parses our sort parameters.
4997
*/

src/Http/Api/Contracts/HasPolicies.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ protected function qualifyUpdateQuery(array $data): array
8181
*
8282
* @return bool
8383
*/
84-
protected function authoriseUserAction(string $ability, $arguments = null): bool
84+
protected function authoriseUserAction(string $ability, $arguments = null, bool $excludeMissing = false): bool
8585
{
86-
if (! $this->testUserPolicyAction($ability, $arguments)) {
86+
if (! $this->testUserPolicyAction($ability, $arguments, $excludeMissing)) {
8787
/** @scrutinizer ignore-call */
8888
$this->errorUnauthorized();
8989
}
@@ -101,7 +101,7 @@ protected function authoriseUserAction(string $ability, $arguments = null): bool
101101
*
102102
* @return bool
103103
*/
104-
protected function testUserPolicyAction(string $ability, $arguments = null): bool
104+
protected function testUserPolicyAction(string $ability, $arguments = null, bool $excludeMissing): bool
105105
{
106106
// If no arguments are specified, set it to the controller's model (default)
107107
if ($arguments === null) {
@@ -117,7 +117,7 @@ protected function testUserPolicyAction(string $ability, $arguments = null): boo
117117

118118
$modelPolicy = Gate::getPolicyFor($model);
119119
// If no policy exists for this model, then there's nothing to check
120-
if (is_null($modelPolicy)) {
120+
if (is_null($modelPolicy) || ($excludeMissing && !method_exists($modelPolicy, $ability)) ) {
121121
return true;
122122
}
123123

0 commit comments

Comments
 (0)