Completed
Pull Request — master (#38)
by Arjay
01:09
created

DataTablesEditor::uploadMessages()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Yajra\DataTables;
4
5
use Illuminate\Http\Request;
6
use Illuminate\Http\JsonResponse;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Database\QueryException;
9
use Illuminate\Support\Facades\Storage;
10
use Illuminate\Contracts\Validation\Validator;
11
use Illuminate\Validation\ValidationException;
12
use Illuminate\Foundation\Validation\ValidatesRequests;
13
14
abstract class DataTablesEditor
15
{
16
    use ValidatesRequests;
17
18
    /**
19
     * Allowed dataTables editor actions.
20
     *
21
     * @var array
22
     */
23
    protected $actions = ['create', 'edit', 'remove', 'upload'];
24
25
    /**
26
     * @var \Illuminate\Database\Eloquent\Model
27
     */
28
    protected $model = null;
29
30
    /**
31
     * Indicates if all mass assignment is enabled on model.
32
     *
33
     * @var bool
34
     */
35
    protected $unguarded = false;
36
37
    /**
38
     * Upload directory relative to storage path.
39
     *
40
     * @var string
41
     */
42
    protected $uploadDir = 'editor';
43
44
    /**
45
     * Filesystem disk config to use for upload.
46
     *
47
     * @var string
48
     */
49
    protected $disk = 'public';
50
51
    /**
52
     * Process dataTables editor action request.
53
     *
54
     * @param Request $request
55
     * @return JsonResponse|mixed
56
     * @throws DataTablesEditorException
57
     */
58
    public function process(Request $request)
59
    {
60
        $action = $request->get('action');
61
62
        if (! in_array($action, $this->actions)) {
63
            throw new DataTablesEditorException('Requested action not supported!');
64
        }
65
66
        return $this->{$action}($request);
67
    }
68
69
    /**
70
     * Process create action request.
71
     *
72
     * @param Request $request
73
     * @return JsonResponse
74
     */
75 View Code Duplication
    public function create(Request $request)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
76
    {
77
        $instance   = $this->resolveModel();
78
        $connection = $instance->getConnection();
79
        $affected   = [];
80
        $errors     = [];
81
82
        $connection->beginTransaction();
83
        foreach ($request->get('data') as $data) {
84
            $validator = $this->getValidationFactory()
85
                              ->make($data, $this->createRules(), $this->createMessages(), $this->attributes());
86
            if ($validator->fails()) {
87
                foreach ($this->formatErrors($validator) as $error) {
88
                    $errors[] = $error;
89
                }
90
91
                continue;
92
            }
93
94
            if (method_exists($this, 'creating')) {
95
                $data = $this->creating($instance, $data);
0 ignored issues
show
Bug introduced by
The method creating() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
96
            }
97
98
            if (method_exists($this, 'saving')) {
99
                $data = $this->saving($instance, $data);
0 ignored issues
show
Bug introduced by
The method saving() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
100
            }
101
102
            $instance->fill($data)->save();
103
104
            if (method_exists($this, 'created')) {
105
                $instance = $this->created($instance, $data);
0 ignored issues
show
Bug introduced by
The method created() does not exist on Yajra\DataTables\DataTablesEditor. Did you maybe mean create()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
106
            }
107
108
            if (method_exists($this, 'saved')) {
109
                $instance = $this->saved($instance, $data);
0 ignored issues
show
Bug introduced by
The method saved() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
110
            }
111
112
            $instance->setAttribute('DT_RowId', $instance->getKey());
113
            $affected[] = $instance;
114
        }
115
116
        if (! $errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
117
            $connection->commit();
118
        } else {
119
            $connection->rollBack();
120
        }
121
122
        return $this->toJson($affected, $errors);
123
    }
124
125
    /**
126
     * Resolve model to used.
127
     *
128
     * @return Model|\Illuminate\Database\Eloquent\SoftDeletes
129
     */
130
    protected function resolveModel()
131
    {
132
        if (! $this->model instanceof Model) {
133
            $this->model = new $this->model;
134
        }
135
136
        $this->model->unguard($this->unguarded);
137
138
        return $this->model;
139
    }
140
141
    /**
142
     * Get create action validation rules.
143
     *
144
     * @return array
145
     */
146
    abstract public function createRules();
147
148
    /**
149
     * Get create validation messages.
150
     *
151
     * @return array
152
     */
153
    protected function createMessages()
154
    {
155
        return [];
156
    }
157
158
    /**
159
     * Get custom attributes for validator errors.
160
     *
161
     * @return array
162
     */
163
    public function attributes()
164
    {
165
        return [];
166
    }
167
168
    /**
169
     * @param Validator $validator
170
     * @return array
171
     */
172
    protected function formatErrors(Validator $validator)
173
    {
174
        $errors = [];
175
176
        collect($validator->errors())->each(function ($error, $key) use (&$errors) {
177
            $errors[] = [
178
                'name'   => $key,
179
                'status' => $error[0],
180
            ];
181
        });
182
183
        return $errors;
184
    }
185
186
    /**
187
     * Display success data in dataTables editor format.
188
     *
189
     * @param array $data
190
     * @param array $errors
191
     * @return JsonResponse
192
     */
193
    protected function toJson(array $data, array $errors = [])
194
    {
195
        $response = ['data' => $data];
196
        if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
197
            $response['fieldErrors'] = $errors;
198
        }
199
200
        return new JsonResponse($response, 200);
201
    }
202
203
    /**
204
     * Process edit action request.
205
     *
206
     * @param Request $request
207
     * @return JsonResponse
208
     */
209 View Code Duplication
    public function edit(Request $request)
0 ignored issues
show
Duplication introduced by
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.

Loading history...
210
    {
211
        $connection = $this->getBuilder()->getConnection();
212
        $affected   = [];
213
        $errors     = [];
214
215
        $connection->beginTransaction();
216
        foreach ($request->get('data') as $key => $data) {
217
            $model     = $this->getBuilder()->findOrFail($key);
218
            $validator = $this->getValidationFactory()
219
                              ->make($data, $this->editRules($model), $this->editMessages(), $this->attributes());
0 ignored issues
show
Documentation introduced by
$model is of type object|null, but the function expects a object<Illuminate\Database\Eloquent\Model>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
220
            if ($validator->fails()) {
221
                foreach ($this->formatErrors($validator) as $error) {
222
                    $errors[] = $error;
223
                }
224
225
                continue;
226
            }
227
228
            if (method_exists($this, 'updating')) {
229
                $data = $this->updating($model, $data);
0 ignored issues
show
Bug introduced by
The method updating() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
230
            }
231
232
            if (method_exists($this, 'saving')) {
233
                $data = $this->saving($model, $data);
0 ignored issues
show
Bug introduced by
The method saving() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
234
            }
235
236
            $model->fill($data)->save();
237
238
            if (method_exists($this, 'updated')) {
239
                $model = $this->updated($model, $data);
0 ignored issues
show
Bug introduced by
The method updated() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
240
            }
241
242
            if (method_exists($this, 'saved')) {
243
                $model = $this->saved($model, $data);
0 ignored issues
show
Bug introduced by
The method saved() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
244
            }
245
246
            $model->setAttribute('DT_RowId', $model->getKey());
247
            $affected[] = $model;
248
        }
249
250
        if (! $errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
251
            $connection->commit();
252
        } else {
253
            $connection->rollBack();
254
        }
255
256
        return $this->toJson($affected, $errors);
257
    }
258
259
    /**
260
     * Get elqouent builder of the model.
261
     *
262
     * @return \Illuminate\Database\Eloquent\Builder
263
     */
264
    protected function getBuilder()
265
    {
266
        $model = $this->resolveModel();
267
268
        if (in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, class_uses($model))) {
269
            return $model->newQuery()->withTrashed();
270
        }
271
272
        return $model->newQuery();
273
    }
274
275
    /**
276
     * Get edit action validation rules.
277
     *
278
     * @param Model $model
279
     * @return array
280
     */
281
    abstract public function editRules(Model $model);
282
283
    /**
284
     * Get edit validation messages.
285
     *
286
     * @return array
287
     */
288
    protected function editMessages()
289
    {
290
        return [];
291
    }
292
293
    /**
294
     * Process remove action request.
295
     *
296
     * @param Request $request
297
     * @return JsonResponse
298
     */
299
    public function remove(Request $request)
300
    {
301
        $connection = $this->getBuilder()->getConnection();
302
        $affected   = [];
303
        $errors     = [];
304
305
        $connection->beginTransaction();
306
        foreach ($request->get('data') as $key => $data) {
307
            $model     = $this->getBuilder()->findOrFail($key);
308
            $validator = $this->getValidationFactory()
309
                              ->make($data, $this->removeRules($model), $this->removeMessages(), $this->attributes());
0 ignored issues
show
Documentation introduced by
$model is of type object|null, but the function expects a object<Illuminate\Database\Eloquent\Model>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
310
            if ($validator->fails()) {
311
                foreach ($this->formatErrors($validator) as $error) {
312
                    $errors[] = $error['status'];
313
                }
314
315
                continue;
316
            }
317
318
            try {
319
                $deleted = clone $model;
320
                if (method_exists($this, 'deleting')) {
321
                    $this->deleting($model, $data);
0 ignored issues
show
Bug introduced by
The method deleting() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
322
                }
323
324
                $model->delete();
325
326
                if (method_exists($this, 'deleted')) {
327
                    $this->deleted($deleted, $data);
0 ignored issues
show
Bug introduced by
The method deleted() does not seem to exist on object<Yajra\DataTables\DataTablesEditor>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
328
                }
329
            } catch (QueryException $exception) {
330
                $error = config('app.debug')
331
                    ? $exception->errorInfo[2]
332
                    : $this->removeExceptionMessage($exception, $model);
0 ignored issues
show
Documentation introduced by
$model is of type object|null, but the function expects a object<Illuminate\Database\Eloquent\Model>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
333
334
                $errors[] = $error;
335
            }
336
337
            $affected[] = $deleted;
338
        }
339
340
        if (! $errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
341
            $connection->commit();
342
        } else {
343
            $connection->rollBack();
344
        }
345
346
        $response = ['data' => $affected];
347
        if ($errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $errors of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
348
            $response['error'] = implode("\n", $errors);
349
        }
350
351
        return new JsonResponse($response, 200);
352
    }
353
354
    /**
355
     * Get remove action validation rules.
356
     *
357
     * @param Model $model
358
     * @return array
359
     */
360
    abstract public function removeRules(Model $model);
361
362
    /**
363
     * Get remove validation messages.
364
     *
365
     * @return array
366
     */
367
    protected function removeMessages()
368
    {
369
        return [];
370
    }
371
372
    /**
373
     * Get remove query exception message.
374
     *
375
     * @param QueryException $exception
376
     * @param Model $model
377
     * @return string
378
     */
379
    protected function removeExceptionMessage(QueryException $exception, Model $model)
0 ignored issues
show
Unused Code introduced by
The parameter $exception is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
380
    {
381
        return "Record {$model->getKey()} is protected and cannot be deleted!";
382
    }
383
384
    /**
385
     * Get dataTables model.
386
     *
387
     * @return Model
388
     */
389
    public function getModel()
390
    {
391
        return $this->model;
392
    }
393
394
    /**
395
     * Set the dataTables model on runtime.
396
     *
397
     * @param Model|string $model
398
     * @return DataTablesEditor
399
     */
400
    public function setModel($model)
401
    {
402
        $this->model = $model;
0 ignored issues
show
Documentation Bug introduced by
It seems like $model can also be of type string. However, the property $model is declared as type object<Illuminate\Database\Eloquent\Model>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
403
404
        return $this;
405
    }
406
407
    /**
408
     * Set model unguard state.
409
     *
410
     * @param bool $state
411
     * @return $this
412
     */
413
    public function unguard($state = true)
414
    {
415
        $this->unguarded = $state;
416
417
        return $this;
418
    }
419
420
    /**
421
     * Handle uploading of file.
422
     *
423
     * @param \Illuminate\Http\Request $request
424
     * @return \Illuminate\Http\JsonResponse
425
     */
426
    public function upload(Request $request)
427
    {
428
        $field   = $request->input('uploadField');
429
        $storage = Storage::disk($this->disk);
430
431
        try {
432
            $rules      = $this->uploadRules();
433
            $fieldRules = ['upload' => data_get($rules, $field, [])];
0 ignored issues
show
Bug introduced by
It seems like $field defined by $request->input('uploadField') on line 428 can also be of type null; however, data_get() does only seem to accept string|array|integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
434
435
            $this->validate($request, $fieldRules, $this->uploadMessages(), $this->attributes());
436
437
            $id = $storage->putFile($this->uploadDir, $request->file('upload'));
438
439
            if (method_exists($this, 'uploaded')) {
440
                $id = $this->uploaded($id);
0 ignored issues
show
Bug introduced by
The method uploaded() does not exist on Yajra\DataTables\DataTablesEditor. Did you maybe mean upload()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
441
            }
442
443
            return response()->json([
0 ignored issues
show
Bug introduced by
The method json does only exist in Illuminate\Contracts\Routing\ResponseFactory, but not in Illuminate\Http\Response.

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

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
444
                'data'   => [],
445
                'file'   => [
446
                    'filename'  => $id,
447
                    'size'      => $request->file('upload')->getSize(),
448
                    'directory' => $this->uploadDir,
449
                    'disk'      => $this->disk,
450
                    'url'       => $storage->url($id),
451
                ],
452
                'upload' => [
453
                    'id' => $id,
454
                ],
455
            ]);
456
        } catch (ValidationException $exception) {
457
            return response()->json([
458
                'data'        => [],
459
                'fieldErrors' => [
460
                    [
461
                        'name'   => $field,
462
                        'status' => str_replace('upload', $field, $exception->errors()['upload'][0]),
463
                    ],
464
                ],
465
            ]);
466
        }
467
    }
468
469
    /**
470
     * Upload validation rules.
471
     *
472
     * @return array
473
     */
474
    public function uploadRules()
475
    {
476
        return [];
477
    }
478
479
    /**
480
     * Upload validation messages.
481
     *
482
     * @return array
483
     */
484
    protected function uploadMessages()
485
    {
486
        return [];
487
    }
488
489
    /**
490
     * Display dataTables editor validation errors.
491
     *
492
     * @param Validator $validator
493
     * @return JsonResponse
494
     */
495
    protected function displayValidationErrors(Validator $validator)
496
    {
497
        $errors = $this->formatErrors($validator);
498
499
        return new JsonResponse([
500
            'data'        => [],
501
            'fieldErrors' => $errors,
502
        ]);
503
    }
504
}
505