1 | <?php |
||
2 | |||
3 | namespace Crystoline\LaraRestApi; |
||
4 | |||
5 | use Illuminate\Database\Eloquent\Builder; |
||
6 | use Illuminate\Database\Eloquent\Model; |
||
7 | use Illuminate\Http\JsonResponse; |
||
8 | use Illuminate\Http\Request; |
||
9 | use Illuminate\Support\Facades\DB; |
||
10 | use Illuminate\Support\Facades\Storage; |
||
11 | use Illuminate\Support\Facades\Validator; |
||
12 | |||
13 | /** |
||
14 | * Rest Api trait for Api Controller. Provide CRUD. |
||
15 | */ |
||
16 | trait RestApiTrait |
||
17 | { |
||
18 | /** |
||
19 | * Status Codes response ok. |
||
20 | * @var int |
||
21 | */ |
||
22 | public static $STATUS_CODE_DONE = 200; |
||
23 | /** |
||
24 | * Status Codes response created. |
||
25 | * @var int |
||
26 | */ |
||
27 | public static $STATUS_CODE_CREATED = 201; |
||
28 | /** |
||
29 | * Status Codes response deleted. |
||
30 | * @var int |
||
31 | */ |
||
32 | public static $STATUS_CODE_REMOVED = 204; |
||
33 | /** |
||
34 | * Status Codes invalid response. |
||
35 | * @var int |
||
36 | */ |
||
37 | public static $STATUS_CODE_NOT_VALID = 400; |
||
38 | /** |
||
39 | * Status Codes response not allowed. |
||
40 | * @var int |
||
41 | */ |
||
42 | public static $STATUS_CODE_NOT_ALLOWED = 405; |
||
43 | /** |
||
44 | * Status Codes response not created. |
||
45 | * @var int |
||
46 | */ |
||
47 | public static $STATUS_CODE_NOT_CREATED = 406; |
||
48 | /** |
||
49 | * Status Codes response not found. |
||
50 | * @var int |
||
51 | */ |
||
52 | public static $STATUS_CODE_NOT_FOUND = 404; |
||
53 | /** |
||
54 | * Status Codes response duplicate. |
||
55 | * @var int |
||
56 | */ |
||
57 | public static $STATUS_CODE_CONFLICT = 409; |
||
58 | /** |
||
59 | * Status Codes response Unauthorized. |
||
60 | * @var int |
||
61 | */ |
||
62 | public static $STATUS_CODE_PERMISSION = 401; |
||
63 | /** |
||
64 | * Status Codes response Access Denied. |
||
65 | * @var int |
||
66 | */ |
||
67 | public static $STATUS_CODE_FORBIDDEN = 403; |
||
68 | /** |
||
69 | * Status Codes response Server Error. |
||
70 | * @var int |
||
71 | */ |
||
72 | public static $STATUS_CODE_SERVER_ERROR = 500; |
||
73 | /** |
||
74 | * Status Codes response no data. |
||
75 | * @var int |
||
76 | */ |
||
77 | public static $STATUS_CODE_NO_RECORD = 407; |
||
78 | protected $statusCodes = [ |
||
79 | 'done' => 200, |
||
80 | 'created' => 201, |
||
81 | 'removed' => 204, |
||
82 | 'not_valid' => 400, |
||
83 | 'not_found' => 404, |
||
84 | 'not_record' => 407, |
||
85 | 'conflict' => 409, |
||
86 | 'permissions' => 401, |
||
87 | 'server_error' => 500, |
||
88 | ]; |
||
89 | |||
90 | |||
91 | /** |
||
92 | * @var int Number of pages |
||
93 | */ |
||
94 | protected $pages = 50; |
||
95 | |||
96 | /** |
||
97 | * List Objects. |
||
98 | * @param Request $request |
||
99 | * @return JsonResponse |
||
100 | */ |
||
101 | public function index(Request $request): JsonResponse |
||
102 | { |
||
103 | |||
104 | /** @var Model $m */ |
||
105 | $m = self::getModel(); |
||
106 | $data = $m::query(); |
||
107 | $pages = self::getPages(); |
||
108 | $searchables = self::searchable(); |
||
109 | $orderBy = self::orderBy() ?: []; |
||
110 | self::filter($request, $data); |
||
111 | self::doSearch($request, $data, $searchables); |
||
112 | self::doOrderBy($request, $data, $orderBy); |
||
113 | $data = self::paginate($request, $data, $pages); |
||
114 | |||
115 | if ($data instanceof Builder) { |
||
116 | $data = $data->get(); |
||
117 | } |
||
118 | |||
119 | $this->beforeList($data); |
||
120 | return $this->respond(self::$STATUS_CODE_DONE, $data); |
||
121 | |||
122 | } |
||
123 | |||
124 | /** |
||
125 | * get The Model name used. with full namespace |
||
126 | * @return string |
||
127 | */ |
||
128 | public static function getModel(): string |
||
129 | { |
||
130 | return Model::class; |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * return number pages for pagination |
||
135 | * @return int |
||
136 | */ |
||
137 | public static function getPages(): int |
||
138 | { |
||
139 | return 50; |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * Return array of searchable fields |
||
144 | * @return array |
||
145 | */ |
||
146 | public static function searchable(): array |
||
147 | { |
||
148 | return []; |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * @return array |
||
153 | */ |
||
154 | public static function orderBy(): array |
||
155 | { |
||
156 | return [ |
||
157 | ]; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * Filter data using request |
||
162 | * @param Request $request |
||
163 | * @param $query |
||
164 | */ |
||
165 | public static function filter(Request $request, $query) |
||
166 | { |
||
167 | } |
||
168 | |||
169 | /** |
||
170 | * Perform wild-card search |
||
171 | * @param Request $request |
||
172 | * @param Builder $builder |
||
173 | * @param $searchables |
||
174 | * return none, Builder passed by reference |
||
175 | */ |
||
176 | public static function doSearch(Request $request, Builder $builder, $searchables) /*:Builder*/ |
||
177 | { |
||
178 | $builder->where(function (Builder $builder) use ($request, $searchables) { |
||
179 | if ($search = $request->input('search')) { |
||
180 | $keywords = explode(' ', trim($search)); |
||
181 | if ($searchables) { |
||
182 | $i = 0; |
||
183 | foreach ($searchables as $searchable) { |
||
184 | foreach ($keywords as $keyword) { |
||
185 | $builder->orWhere($searchable, 'like', "%{$keyword}%"); |
||
186 | } |
||
187 | } |
||
188 | } |
||
189 | } |
||
190 | if ($search = $request->input('qsearch')) { |
||
191 | if ($searchables) { |
||
192 | $i = 0; |
||
193 | foreach ($searchables as $searchable) { |
||
194 | $builder->orWhere($searchable, 'like', "%{$search}%"); |
||
195 | } |
||
196 | } |
||
197 | } |
||
198 | }); |
||
199 | |||
200 | //return $builder; |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * Order Data |
||
205 | * @param Request $request |
||
206 | * @param Builder $builder |
||
207 | * @param array $orderBy |
||
208 | */ |
||
209 | public static function doOrderBy(Request $request, Builder $builder, array $orderBy) |
||
210 | { |
||
211 | if ($orderBy) { |
||
0 ignored issues
–
show
|
|||
212 | foreach ($orderBy as $field => $direction) { |
||
213 | $builder->orderBy($field, $direction); |
||
214 | } |
||
215 | } |
||
216 | } |
||
217 | |||
218 | /** |
||
219 | * Paginate Data |
||
220 | * @param Request $request |
||
221 | * @param Builder $data |
||
222 | * @param int $pages |
||
223 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator|Builder |
||
224 | */ |
||
225 | public static function paginate(Request $request, $data, $pages = 50) |
||
226 | { |
||
227 | $should_paginate = $request->input('paginate', 'yes'); |
||
228 | |||
229 | if ('yes' == $should_paginate) { |
||
230 | $data = $data->paginate($request->input('pages', $pages)); |
||
231 | } |
||
232 | |||
233 | return $data; |
||
234 | } |
||
235 | |||
236 | /** |
||
237 | * Perform action before data list |
||
238 | * @param $data |
||
239 | */ |
||
240 | public function beforeList($data) |
||
241 | { |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Show records. |
||
246 | * @param $id |
||
247 | * @return JsonResponse |
||
248 | */ |
||
249 | public function show(int $id) |
||
250 | { |
||
251 | $m = self::getModel(); |
||
252 | $data = $m::find($id); |
||
253 | |||
254 | if (is_null($data)) { |
||
255 | return $this->respond(self::$STATUS_CODE_NOT_FOUND, ['message' => 'Record was not found']); |
||
256 | } |
||
257 | $this->beforeShow($data); |
||
258 | |||
259 | return $this->respond(self::$STATUS_CODE_DONE, $data); |
||
260 | |||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Perform action before data show |
||
265 | * @param $data |
||
266 | */ |
||
267 | public function beforeShow($data) |
||
268 | { |
||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Store Record. |
||
273 | * @param Request $request |
||
274 | * @return JsonResponse |
||
275 | */ |
||
276 | public function store(Request $request): JsonResponse |
||
277 | { |
||
278 | /** @var Model $m */ |
||
279 | $m = self::getModel(); |
||
280 | $rules = self::getValidationRules(); |
||
281 | $message = self::getValidationMessages(); |
||
282 | |||
283 | $validator = Validator::make($request->all(), $rules, $message); |
||
284 | //$this->validate($request, $rules, $message); |
||
285 | |||
286 | if ($validator->fails()) { |
||
287 | return $this->respond(self::$STATUS_CODE_NOT_VALID, $validator->errors()); |
||
288 | // return response()->json($validator->errors(), self::$STATUS_CODE_NOT_VALID); |
||
289 | } |
||
290 | |||
291 | DB::beginTransaction(); |
||
292 | |||
293 | //try{ |
||
294 | if (!$this->beforeStore($request)) { |
||
295 | DB::rollback(); |
||
296 | return $this->respond(self::$STATUS_CODE_SERVER_ERROR, ['message' => 'could not create record (Duplicate Record)']); |
||
297 | // return response()->json(['message' => 'could not create record (Duplicate Record)'], self::$STATUS_CODE_SERVER_ERROR); |
||
298 | } |
||
299 | self::doUpload($request); |
||
300 | $input = $request->input(); |
||
301 | |||
302 | |||
303 | /*unset($input['school']); |
||
304 | unset($input['staff']);*/ |
||
305 | |||
306 | //dump($input); |
||
307 | $data = $m::create($input); |
||
308 | |||
309 | //catch (\Exception $exception){ |
||
310 | //DB::rollback(); |
||
311 | //todo remove Exception message |
||
312 | //return response()->json( ['message' => 'An error occurred while creating record: '.$exception->getMessage().', Line:'.$exception->getFile().'/'.$exception->getLine()], self::$STATUS_CODE_CONFLICT); |
||
313 | //} |
||
314 | if (!$this->afterStore($request, $data)) { |
||
315 | DB::rollback(); |
||
316 | |||
317 | return $this->respond(self::$STATUS_CODE_SERVER_ERROR, ['message' => 'could not successfully create record']); |
||
318 | // return response()->json(['message' => 'could not successfully create record'], self::$STATUS_CODE_SERVER_ERROR); |
||
319 | } |
||
320 | |||
321 | DB::commit(); |
||
322 | |||
323 | |||
324 | $this->beforeShow($data); |
||
325 | return $this->respond(self::$STATUS_CODE_CREATED, $data); |
||
326 | //return response()->json($data, self::$STATUS_CODE_CREATED); |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * @return array |
||
331 | */ |
||
332 | public static function getValidationRules(): array |
||
333 | { |
||
334 | return []; |
||
335 | } |
||
336 | |||
337 | /** |
||
338 | * @return array |
||
339 | */ |
||
340 | public static function getValidationMessages(): array |
||
341 | { |
||
342 | return []; |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Perform action before data store |
||
347 | * @param Request $request |
||
348 | * @return bool |
||
349 | */ |
||
350 | public function beforeStore(Request $request): bool |
||
351 | { |
||
352 | return true; |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * Perform file upload for request |
||
357 | * @param Request $request |
||
358 | * @param Model $object |
||
359 | */ |
||
360 | public static function doUpload(Request $request, $object = null) |
||
361 | { |
||
362 | //dd('kdkd'); |
||
363 | $data = $request->all(); |
||
364 | foreach ($data as $key => $val) { |
||
365 | |||
366 | if ($request->hasFile($key) && $request->file($key)->isValid()) { |
||
367 | |||
368 | $original = $object->$key ?? null; |
||
369 | |||
370 | $interfaces = class_implements(self::class); |
||
371 | $base = isset($interfaces[IFileUpload::class]) ? self::fileBasePath($request) : ''; |
||
372 | if ($base) { |
||
373 | $base = trim($base, '/,\\') . '/'; |
||
374 | } |
||
375 | $path = $request->$key->store('public/' . $base . $key); |
||
376 | $path = str_replace('public/', 'storage/', $path); |
||
377 | |||
378 | $path_url = asset($path); |
||
379 | |||
380 | $request->files->remove($key); |
||
381 | $request->merge([$key => $path_url]); |
||
382 | |||
383 | if (!is_null($original)) { |
||
384 | Storage::delete(str_replace('storage/', 'public/', $original)); |
||
385 | } |
||
386 | } |
||
387 | } |
||
388 | |||
389 | } |
||
390 | |||
391 | /** |
||
392 | * Perform action after data store |
||
393 | * @param Request $request |
||
394 | * @param $data |
||
395 | * @return bool |
||
396 | */ |
||
397 | public function afterStore(Request $request, $data): bool |
||
398 | { |
||
399 | $this->beforeShow($data); |
||
400 | return true; |
||
401 | } |
||
402 | |||
403 | /** |
||
404 | * Update Record. |
||
405 | * |
||
406 | * @param Request $request |
||
407 | * @param $id |
||
408 | * |
||
409 | * @return JsonResponse |
||
410 | */ |
||
411 | public function update(Request $request, int $id): JsonResponse |
||
412 | { |
||
413 | /** @var Model $m */ |
||
414 | $m = self::getModel(); |
||
415 | $model = $m::find($id); |
||
416 | |||
417 | if ($model === null) { |
||
418 | return $this->respond(self::$STATUS_CODE_NOT_FOUND, ['message' => 'Record was not found']); |
||
419 | } |
||
420 | |||
421 | $rules = self::getValidationRulesForUpdate($model); |
||
422 | $message = self::getValidationMessages(); |
||
423 | //$this->validate($request, $rules, $message); |
||
424 | $validator = Validator::make($request->all(), $rules, $message); |
||
425 | if ($validator->fails()) { |
||
426 | return $this->respond(self::$STATUS_CODE_NOT_VALID, $validator->errors()); |
||
427 | //return response()->json($validator->errors(), self::$STATUS_CODE_NOT_VALID); |
||
428 | } |
||
429 | |||
430 | DB::beginTransaction(); |
||
431 | if (!$this->beforeUpdate($request)) { |
||
432 | DB::rollback(); |
||
433 | return $this->respond(self::$STATUS_CODE_SERVER_ERROR, ['message' => 'could not update record']); |
||
434 | //return response()->json(['message' => 'could not update record'], self::$STATUS_CODE_SERVER_ERROR); |
||
435 | } |
||
436 | self::doUpload($request, $model); |
||
437 | $fieldsToUpdate = (method_exists($model, 'fieldsToUpdate') |
||
438 | and !empty(self::fieldsToUpdate())) ? |
||
439 | $request->only(self::fieldsToUpdate()) : $request->input(); |
||
440 | |||
441 | try { |
||
442 | $model->update($fieldsToUpdate); |
||
443 | } catch (\Exception $exception) { |
||
444 | DB::rollback(); |
||
445 | //todo remove Exception message |
||
446 | // return response()->json(['message' => 'An error occurred while updating record: '], 500); |
||
447 | return $this->respond(self::$STATUS_CODE_SERVER_ERROR, ['message' => 'An error occurred while updating record: ']); |
||
448 | } |
||
449 | if (!$this->afterUpdate($request, $model)) { |
||
450 | DB::rollback(); |
||
451 | return $this->respond(self::$STATUS_CODE_SERVER_ERROR, ['message' => 'could not successfully update record']); |
||
452 | //return response()->json(['message' => 'could not successfully update record'], self::$STATUS_CODE_SERVER_ERROR); |
||
453 | } |
||
454 | |||
455 | DB::commit(); |
||
456 | |||
457 | return $this->respond(self::$STATUS_CODE_DONE, $model); |
||
458 | //return response()->json($model, self::$STATUS_CODE_DONE); |
||
459 | } |
||
460 | |||
461 | /** |
||
462 | * @param Model $model |
||
463 | * @return array |
||
464 | */ |
||
465 | public static function getValidationRulesForUpdate(Model $model): array |
||
466 | { |
||
467 | $id = $model->id; |
||
468 | $rules = self::getValidationRules(); |
||
469 | $fields = self::getUniqueFields(); |
||
470 | foreach ($fields as $field) { |
||
471 | if (isset($rules[$field])) { |
||
472 | $rules[$field] .= ',' . $id; |
||
473 | } |
||
474 | } |
||
475 | return $rules; |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Return array of unique field. Used for validation |
||
480 | * @return array |
||
481 | */ |
||
482 | public static function getUniqueFields(): array |
||
483 | { |
||
484 | return []; |
||
485 | } |
||
486 | |||
487 | /** |
||
488 | * Run before update action |
||
489 | * @param Request $request |
||
490 | * @return bool |
||
491 | */ |
||
492 | public function beforeUpdate(Request $request): bool |
||
493 | { |
||
494 | return true; |
||
495 | } |
||
496 | |||
497 | /** |
||
498 | * get fields to be update |
||
499 | * @return array |
||
500 | */ |
||
501 | public static function fieldsToUpdate(): array |
||
502 | { |
||
503 | return []; |
||
504 | } |
||
505 | |||
506 | /** |
||
507 | * Run after update action |
||
508 | * @param Request $request |
||
509 | * @param $data |
||
510 | * @return bool |
||
511 | */ |
||
512 | public function afterUpdate(Request $request, $data): bool |
||
513 | { |
||
514 | $this->beforeShow($data); |
||
515 | return true; |
||
516 | } |
||
517 | |||
518 | /** |
||
519 | * Delete Record. |
||
520 | * |
||
521 | * @param $id |
||
522 | * |
||
523 | * @return JsonResponse |
||
524 | */ |
||
525 | public function destroy(int $id) |
||
526 | { |
||
527 | $m = self::getModel(); |
||
528 | if ($m::find($id) === null) { |
||
529 | //return response()->json(['record was not found'], self::$STATUS_CODE_NOT_FOUND); |
||
530 | return $this->respond(self::$STATUS_CODE_NOT_FOUND, ['message' => 'record was not found']); |
||
531 | } |
||
532 | try { |
||
533 | $m::destroy($id); |
||
534 | } catch (\Exception $exception) { |
||
535 | |||
536 | } |
||
537 | |||
538 | return $this->respond(self::$STATUS_CODE_DONE, ['message' => 'record was deleted']); |
||
539 | //return response()->json(['message' => 'record was deleted'], self::$STATUS_CODE_DONE); |
||
540 | } |
||
541 | |||
542 | /** |
||
543 | * Perform action before data deletion |
||
544 | * @param Request $request |
||
545 | * @return bool |
||
546 | */ |
||
547 | public function beforeDelete(Request $request): bool |
||
548 | { |
||
549 | return true; |
||
550 | } |
||
551 | |||
552 | /** |
||
553 | * @param $status |
||
554 | * @param array $data |
||
555 | * |
||
556 | * @return JsonResponse |
||
557 | */ |
||
558 | protected function respond($status, $data = []): JsonResponse |
||
559 | { |
||
560 | $status = array_key_exists($status, $this->statusCodes)? $this->statusCodes[$status]: $status; |
||
561 | return response()->json($data, $status); |
||
562 | } |
||
563 | |||
564 | |||
565 | } |
||
566 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.