This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace TomHart\Restful; |
||
4 | |||
5 | use BadMethodCallException; |
||
6 | use Exception; |
||
7 | use Illuminate\Contracts\Pagination\LengthAwarePaginator; |
||
8 | use Illuminate\Contracts\Routing\ResponseFactory; |
||
9 | use Illuminate\Contracts\View\Factory; |
||
10 | use Illuminate\Database\Eloquent\Builder; |
||
0 ignored issues
–
show
|
|||
11 | use Illuminate\Database\Eloquent\Collection; |
||
12 | use Illuminate\Database\Eloquent\Model; |
||
13 | use Illuminate\Database\Eloquent\Relations\BelongsToMany; |
||
14 | use Illuminate\Database\Eloquent\Relations\HasMany; |
||
15 | use Illuminate\Database\Eloquent\Relations\HasOneOrMany; |
||
16 | use Illuminate\Database\Eloquent\Relations\MorphMany; |
||
17 | use Illuminate\Database\Eloquent\Relations\MorphOneOrMany; |
||
18 | use Illuminate\Database\Eloquent\Relations\MorphToMany; |
||
19 | use Illuminate\Database\Eloquent\Relations\Relation; |
||
20 | use Illuminate\Foundation\Auth\Access\AuthorizesRequests; |
||
21 | use Illuminate\Foundation\Validation\ValidatesRequests; |
||
22 | use Illuminate\Http\JsonResponse; |
||
23 | use Illuminate\Http\RedirectResponse; |
||
24 | use Illuminate\Http\Request; |
||
25 | use Illuminate\Http\Response; |
||
26 | use Illuminate\Routing\Controller as BaseController; |
||
27 | use Illuminate\Routing\Redirector; |
||
28 | use Illuminate\Routing\Route; |
||
29 | use Illuminate\Support\Str; |
||
30 | use Illuminate\View\View; |
||
31 | use InvalidArgumentException; |
||
32 | use Symfony\Component\HttpFoundation\Response as SymResponse; |
||
33 | use TomHart\Restful\Concerns\HasLinks; |
||
34 | |||
35 | abstract class AbstractRestfulController extends BaseController |
||
36 | { |
||
37 | use AuthorizesRequests; |
||
38 | use ValidatesRequests; |
||
39 | |||
40 | /** |
||
41 | * The views to render. |
||
42 | * @var string[] |
||
43 | */ |
||
44 | protected $views = []; |
||
45 | |||
46 | /** |
||
47 | * Return a list of matching models. |
||
48 | * @param Request $request |
||
49 | * @return JsonResponse|RedirectResponse|ResponseFactory|Response|Redirector |
||
50 | */ |
||
51 | public function index(Request $request) |
||
52 | { |
||
53 | $builder = $this->createModelQueryBuilder(); |
||
54 | |||
55 | $input = collect($request->input())->except(config('restful.query_string_keys.page'))->toArray(); |
||
56 | foreach ($input as $column => $value) { |
||
57 | $this->filterValue($builder, $column, $value); |
||
58 | } |
||
59 | |||
60 | $data = $builder->paginate( |
||
61 | $request->input('limit', null), |
||
62 | ['*'], |
||
63 | config('restful.query_string_keys.page') |
||
64 | ); |
||
65 | |||
66 | return $this->return($request, $data, 'index'); |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Generate a new query builder for the model. |
||
71 | * @return Builder |
||
72 | */ |
||
73 | protected function createModelQueryBuilder(): Builder |
||
74 | { |
||
75 | return $this->newModelInstance()->newQuery(); |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * Creates a new model instance. |
||
80 | * @return Model |
||
81 | */ |
||
82 | protected function newModelInstance(): Model |
||
83 | { |
||
84 | $classFQDN = $this->getModelClass(); |
||
85 | |||
86 | return app($classFQDN); |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * What Model class to search for entities. |
||
91 | * @return string |
||
92 | */ |
||
93 | abstract protected function getModelClass(): string; |
||
94 | |||
95 | /** |
||
96 | * Apply causes to the builder. |
||
97 | * @param Builder $builder |
||
98 | * @param string $column |
||
99 | * @param mixed $value |
||
100 | */ |
||
101 | protected function filterValue(Builder $builder, string $column, $value): void |
||
102 | { |
||
103 | $builder->where($column, $value); |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Build and return a response. |
||
108 | * @param Request $request |
||
109 | * @param mixed $data |
||
110 | * @param string $method |
||
111 | * @return JsonResponse|ResponseFactory|Response|RedirectResponse|Redirector |
||
112 | */ |
||
113 | protected function return(Request $request, $data, string $method) |
||
114 | { |
||
115 | $status = SymResponse::HTTP_OK; |
||
116 | switch ($method) { |
||
117 | case 'store': |
||
118 | $status = SymResponse::HTTP_CREATED; |
||
119 | break; |
||
120 | } |
||
121 | |||
122 | if ($request->wantsJson()) { |
||
123 | return app(ResponseFactory::class)->json($data, $status); |
||
124 | } |
||
125 | |||
126 | if (isset($this->views[$method]) && app(Factory::class)->exists($this->views[$method])) { |
||
127 | /** @var View $view */ |
||
128 | $view = view( |
||
129 | $this->views[$method], |
||
130 | [ |
||
131 | 'data' => $data |
||
132 | ] |
||
133 | ); |
||
134 | |||
135 | return response($view, $status); |
||
136 | } |
||
137 | |||
138 | switch ($method) { |
||
139 | case 'store': |
||
140 | case 'update': |
||
141 | // If it's store/update, and the user isn't asking for JSON, we want to |
||
142 | // try and redirect them to the related show record page. |
||
143 | if (($redirect = $this->redirectToShowRoute($request, $data))) { |
||
144 | return $redirect; |
||
145 | } |
||
146 | } |
||
147 | |||
148 | return app(ResponseFactory::class)->json($data, $status); |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Redirects to the show route for the model if one exists. |
||
153 | * @param Request $request |
||
154 | * @param mixed $data |
||
155 | * @return RedirectResponse|Redirector|null |
||
156 | */ |
||
157 | protected function redirectToShowRoute(Request $request, $data) |
||
158 | { |
||
159 | if ($data instanceof HasLinks) { |
||
160 | $route = $data->getRouteName() . '.show'; |
||
161 | $key = $data->getRouteKey(); |
||
162 | |||
163 | return redirect( |
||
164 | route( |
||
165 | $route, |
||
166 | [ |
||
167 | $key => $data->id |
||
0 ignored issues
–
show
Accessing
id on the interface TomHart\Restful\Concerns\HasLinks suggest that you code against a concrete implementation. How about adding an instanceof check?
If you access a property on an interface, you most likely code against a concrete implementation of the interface. Available Fixes
![]() |
|||
168 | ] |
||
169 | ) |
||
170 | ); |
||
171 | } |
||
172 | |||
173 | /** @var Route|null $route */ |
||
174 | $route = $request->route(); |
||
175 | if (!$route) { |
||
176 | return null; |
||
177 | } |
||
178 | |||
179 | $name = $route->getName(); |
||
180 | if (!$name) { |
||
0 ignored issues
–
show
The expression
$name of type string|null is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
181 | return null; |
||
182 | } |
||
183 | |||
184 | $exploded = explode('.', $name); |
||
185 | array_pop($exploded); |
||
186 | $topLevel = array_pop($exploded); |
||
187 | |||
188 | if (!$topLevel) { |
||
189 | return null; |
||
190 | } |
||
191 | |||
192 | $key = Str::singular(str_replace('-', '_', $topLevel)); |
||
193 | |||
194 | return redirect( |
||
195 | route( |
||
196 | "$topLevel.show", |
||
197 | [ |
||
198 | $key => $data->id |
||
199 | ] |
||
200 | ) |
||
201 | ); |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Handles creating a model. The C of CRUD |
||
206 | * @param Request $request |
||
207 | * @return JsonResponse|RedirectResponse|ResponseFactory|Response|Redirector |
||
208 | */ |
||
209 | View Code Duplication | public function store(Request $request) |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
210 | { |
||
211 | $model = $this->newModelInstance(); |
||
212 | |||
213 | foreach ((array)$request->input() as $column => $value) { |
||
214 | $model->$column = $value; |
||
215 | } |
||
216 | |||
217 | $this->saveModel($model); |
||
218 | |||
219 | return $this->return($request, $this->findModel($model->getAttribute('id')), 'store'); |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Save a model, either from a store or an update. |
||
224 | * @param Model $model |
||
225 | * @return bool |
||
226 | */ |
||
227 | protected function saveModel(Model $model) |
||
228 | { |
||
229 | return $model->save(); |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Finds the model instance. |
||
234 | * @param int|string $id |
||
235 | * @param Request|null $request |
||
236 | * @return Model |
||
237 | */ |
||
238 | protected function findModel($id, Request $request = null): Model |
||
239 | { |
||
240 | $builder = $this->createModelQueryBuilder(); |
||
241 | |||
242 | if ($request) { |
||
243 | $this->preloadRelationships($builder, $request); |
||
244 | } |
||
245 | |||
246 | |||
247 | $builder = $builder->findOrFail($id); |
||
248 | return $builder; |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * Preload any relationships required. |
||
253 | * @param Builder $builder |
||
254 | * @param Request $request |
||
255 | * @return void |
||
256 | */ |
||
257 | protected function preloadRelationships(Builder &$builder, Request $request): void |
||
258 | { |
||
259 | $headers = $request->headers; |
||
260 | if (!$headers) { |
||
261 | return; |
||
262 | } |
||
263 | |||
264 | $header = $headers->get('X-Load-Relationship'); |
||
265 | if (!$header || empty($header)) { |
||
266 | return; |
||
267 | } |
||
268 | |||
269 | $relationships = array_filter(explode(',', $header)); |
||
270 | |||
271 | $builder = $builder->with($relationships); |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * Shows a model. The R of CRUD. |
||
276 | * @param Request $request |
||
277 | * @param int $id |
||
278 | * @return JsonResponse|RedirectResponse|ResponseFactory|Response|Redirector |
||
279 | */ |
||
280 | public function show(Request $request, $id) |
||
281 | { |
||
282 | $model = $this->findModel($id, $request); |
||
283 | |||
284 | $model = $this->iterateThroughChildren($model, $request); |
||
285 | |||
286 | return $this->return($request, $model, 'show'); |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Looks for an "extra" param in the route, and if it exists, looks for relationships |
||
291 | * based on that route. |
||
292 | * @param Model $model |
||
293 | * @param Request $request |
||
294 | * @return LengthAwarePaginator|Collection|Model|mixed |
||
295 | * @throws BadMethodCallException |
||
296 | */ |
||
297 | protected function iterateThroughChildren(Model $model, Request $request) |
||
298 | { |
||
299 | // If there's no route or extra param, just return. |
||
300 | if (!$request->route() || |
||
301 | !($request->route() instanceof Route) || |
||
302 | !$request->route()->parameter('extra')) { |
||
303 | return $model; |
||
304 | } |
||
305 | |||
306 | $parts = array_filter(explode('/', (string)$request->route()->parameter('extra'))); |
||
307 | |||
308 | // Loop through the parts. |
||
309 | foreach ($parts as $part) { |
||
310 | // Look for an array accessor, "children[5]" for example. |
||
311 | preg_match('/\[(\d+)]$/', $part, $matches); |
||
312 | $offset = false; |
||
313 | |||
314 | // If one was found, save the offset and remove it from $part. |
||
315 | if (!empty($matches[0])) { |
||
316 | $part = str_replace(array_shift($matches), '', $part); |
||
317 | $offset = array_shift($matches); |
||
318 | } |
||
319 | |||
320 | $model = $model->$part(); |
||
321 | |||
322 | // If it's a relationship, see if it's paginate-able. |
||
323 | if (stripos(get_class($model), 'Many') !== false) { |
||
324 | /** @var BelongsToMany|HasMany|HasOneOrMany|MorphMany|MorphOneOrMany|MorphToMany $model */ |
||
325 | $model = $model->paginate(); |
||
0 ignored issues
–
show
The method
paginate does only exist in Illuminate\Database\Eloq...Relations\BelongsToMany , but not in Illuminate\Database\Eloq...\Relations\HasOneOrMany .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() |
|||
326 | } elseif ($model instanceof Relation) { |
||
327 | $model = $model->getResults(); |
||
328 | } |
||
329 | |||
330 | // If there is an offset, get it. |
||
331 | if ($offset !== false) { |
||
332 | $model = $model[$offset]; |
||
333 | } |
||
334 | } |
||
335 | |||
336 | return $model; |
||
337 | } |
||
338 | |||
339 | /** |
||
340 | * Update a record. The U of CRUD. |
||
341 | * @param Request $request |
||
342 | * @param int $id |
||
343 | * @return JsonResponse|RedirectResponse|ResponseFactory|Response|Redirector |
||
344 | */ |
||
345 | View Code Duplication | public function update(Request $request, $id) |
|
0 ignored issues
–
show
This method seems to be duplicated in your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
346 | { |
||
347 | $model = $this->findModel($id); |
||
348 | |||
349 | foreach ((array)$request->input() as $column => $value) { |
||
350 | $model->$column = $value; |
||
351 | } |
||
352 | |||
353 | $this->saveModel($model); |
||
354 | |||
355 | return $this->return($request, $model, 'update'); |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Destroy a model. The D of CRUD. |
||
360 | * @param Request $request |
||
361 | * @param int $id |
||
362 | * @return ResponseFactory|Response |
||
363 | * @throws Exception |
||
364 | */ |
||
365 | public function destroy(Request $request, $id) |
||
0 ignored issues
–
show
|
|||
366 | { |
||
367 | $model = $this->findModel($id); |
||
368 | |||
369 | $model->delete(); |
||
370 | |||
371 | return response(null, SymResponse::HTTP_NO_CONTENT); |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Return the _links. The O of CRUD..... |
||
376 | * @param Request $request |
||
377 | * @return ResponseFactory|JsonResponse|RedirectResponse|Response|Redirector |
||
378 | */ |
||
379 | public function options(Request $request) |
||
380 | { |
||
381 | $class = $this->newModelInstance(); |
||
382 | |||
383 | if (!($class instanceof HasLinks)) { |
||
384 | throw new InvalidArgumentException('OPTIONS only works for models implementing HasLinks'); |
||
385 | } |
||
386 | |||
387 | foreach ($request->input() as $key => $value) { |
||
388 | $class->$key = $value; |
||
389 | } |
||
390 | |||
391 | return $this->return($request, $class->buildLinks(), 'options'); |
||
392 | } |
||
393 | } |
||
394 |
Let’s assume that you have a directory layout like this:
and let’s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: