Passed
Push — documentation ( 2be3e9...2ecb8c )
by Alex
04:22 queued 01:23
created

JsonApiController   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 335
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 27
Bugs 2 Features 11
Metric Value
wmc 35
c 27
b 2
f 11
lcom 1
cbo 11
dl 0
loc 335
rs 9

16 Methods

Rating   Name   Duplication   Size   Complexity  
A updateRecordRelationships() 0 16 4
getModel() 0 1 ?
A getModelRelationships() 0 4 1
A indexAction() 0 19 4
A storeAction() 0 10 2
A showAction() 0 7 1
A updateAction() 0 12 2
A destroyAction() 0 7 1
A relationshipAction() 0 8 1
A updateToOneRelationshipAction() 0 13 1
B updateToManyRelationshipAction() 0 29 6
A findModelInstance() 0 12 3
A getRequestParameters() 0 9 1
A getRequestQuerySet() 0 4 1
A sortQuery() 0 10 3
B filterQuery() 0 22 4
1
<?php
2
3
namespace Huntie\JsonApi\Http\Controllers;
4
5
use Schema;
6
use Huntie\JsonApi\Http\JsonApiResponse;
7
use Huntie\JsonApi\Serializers\CollectionSerializer;
8
use Huntie\JsonApi\Serializers\RelationshipSerializer;
9
use Huntie\JsonApi\Serializers\ResourceSerializer;
10
use Huntie\JsonApi\Support\JsonApiErrors;
11
use Illuminate\Database\QueryException;
12
use Illuminate\Database\Eloquent\Model;
13
use Illuminate\Database\Eloquent\ModelNotFoundException;
14
use Illuminate\Database\Eloquent\Relations\BelongsTo;
15
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
16
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
17
use Illuminate\Foundation\Validation\ValidatesRequests;
18
use Illuminate\Http\Request;
19
use Illuminate\Http\Response;
20
use Illuminate\Routing\Controller;
21
22
abstract class JsonApiController extends Controller
23
{
24
    use JsonApiErrors;
25
    use AuthorizesRequests;
26
    use ValidatesRequests;
27
28
    /**
29
     * Return the Eloquent Model for the resource.
30
     *
31
     * @return Model
32
     */
33
    abstract protected function getModel();
34
35
    /**
36
     * The model relationships that can be updated.
37
     *
38
     * @return array
39
     */
40
    protected function getModelRelationships()
41
    {
42
        return [];
43
    }
44
45
    /**
46
     * Return a listing of the resource.
47
     *
48
     * @param Request                                    $request
49
     * @param \Illuminate\Database\Eloquent\Builder|null $query   Custom resource query
50
     *
51
     * @return JsonApiResponse
52
     */
53
    public function indexAction(Request $request, $query = null)
54
    {
55
        $records = $query ?: $this->getModel()->newQuery();
56
        $params = $this->getRequestParameters($request);
57
58
        $records = $this->sortQuery($records, $params['sort']);
59
        $records = $this->filterQuery($records, $params['filter']);
60
61
        try {
62
            $pageSize = min($this->getModel()->getPerPage(), $request->input('page.size'));
63
            $pageNumber = $request->input('page.number') ?: 1;
64
65
            $records = $records->paginate($pageSize, null, 'page', $pageNumber);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a array.

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...
66
        } catch (QueryException $e) {
67
            return $this->error(Response::HTTP_BAD_REQUEST, 'Invalid query parameters');
68
        }
69
70
        return new JsonApiResponse(new CollectionSerializer($records, $params['fields'], $params['include']));
71
    }
72
73
    /**
74
     * Store a new record.
75
     *
76
     * @param Request $request
77
     *
78
     * @return JsonApiResponse
79
     */
80
    public function storeAction(Request $request)
81
    {
82
        $record = $this->getModel()->create((array) $request->input('data.attributes'));
83
84
        if ($relationships = $request->input('data.relationships')) {
85
            $this->updateRecordRelationships($record, (array) $relationships);
86
        }
87
88
        return new JsonApiResponse(new ResourceSerializer($record), Response::HTTP_CREATED);
89
    }
90
91
    /**
92
     * Return a specified record.
93
     *
94
     * @param Request   $request
95
     * @param Model|int $record
96
     *
97
     * @return JsonApiResponse
98
     */
99
    public function showAction(Request $request, $record)
100
    {
101
        $record = $this->findModelInstance($record);
102
        $params = $this->getRequestParameters($request);
103
104
        return new JsonApiResponse(new ResourceSerializer($record, $params['fields'], $params['include']));
105
    }
106
107
    /**
108
     * Update a specified record.
109
     *
110
     * @param Request   $request
111
     * @param Model|int $record
112
     *
113
     * @return JsonApiResponse
114
     */
115
    public function updateAction(Request $request, $record)
116
    {
117
        $record = $this->findModelInstance($record);
118
        $record->fill((array) $request->input('data.attributes'));
119
        $record->save();
120
121
        if ($relationships = $request->input('data.relationships')) {
122
            $this->updateRecordRelationships($record, (array) $relationships);
123
        }
124
125
        return new JsonApiResponse(new ResourceSerializer($record));
126
    }
127
128
    /**
129
     * Destroy a specified record.
130
     *
131
     * @param Request   $request
132
     * @param Model|int $record
133
     *
134
     * @return JsonApiResponse
135
     */
136
    public function destroyAction(Request $request, $record)
137
    {
138
        $record = $this->findModelInstance($record);
139
        $record->delete();
140
141
        return new JsonApiResponse(null, Response::HTTP_NO_CONTENT);
142
    }
143
144
    /**
145
     * Return a specified record relationship.
146
     *
147
     * @param Request   $request
148
     * @param Model|int $record
149
     * @param string    $relation
150
     *
151
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
152
     *
153
     * @return JsonApiResponse
154
     */
155
    public function relationshipAction(Request $request, $record, $relation)
156
    {
157
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
158
159
        $record = $this->findModelInstance($record);
160
161
        return new JsonApiResponse(new RelationshipSerializer($record, $relation));
162
    }
163
164
    /**
165
     * Update a named many-to-one relationship association on a specified record.
166
     * http://jsonapi.org/format/#crud-updating-to-one-relationships
167
     *
168
     * @param Request     $request
169
     * @param Model|int   $record
170
     * @param string      $relation
171
     *
172
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
173
     *
174
     * @return JsonApiResponse
175
     */
176
    public function updateToOneRelationshipAction(Request $request, $record, $relation)
177
    {
178
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
179
180
        $record = $this->findModelInstance($record);
181
        $relation = $this->getModelRelationships()[$relation];
182
        $data = (array) $request->input('data');
183
184
        $record->{$relation->getForeignKey()} = $data['id'];
185
        $record->save();
186
187
        return new JsonApiResponse(new RelationshipSerializer($record, $relation));
188
    }
189
190
    /**
191
     * Update named many-to-many relationship entries on a specified record.
192
     * http://jsonapi.org/format/#crud-updating-to-many-relationships
193
     *
194
     * @param Request   $request
195
     * @param Model|int $record
196
     * @param string    $relation
197
     *
198
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
199
     *
200
     * @return JsonApiResponse
201
     */
202
    public function updateToManyRelationshipAction(Request $request, $record, $relation)
203
    {
204
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
205
206
        $record = $this->findModelInstance($record);
207
        $relationships = (array) $request->input('data');
208
        $items = [];
209
210
        foreach ($relationships as $item) {
211
            if (isset($item['attributes'])) {
212
                $items[$item['id']] = $item['attributes'];
213
            } else {
214
                $items[] = $item['id'];
215
            }
216
        }
217
218
        switch ($request->method()) {
219
            case 'PATCH':
220
                $record->{$relation}()->sync($items);
221
                break;
222
            case 'POST':
223
                $record->{$relation}()->sync($items, false);
224
                break;
225
            case 'DELETE':
226
                $record->{$relation}()->detach(array_keys($items));
227
        }
228
229
        return new JsonApiResponse(new RelationshipSerializer($record, $relation));
230
    }
231
232
    /**
233
     * Return existing instance of the resource or find by primary key.
234
     *
235
     * @param Model|int $record
236
     *
237
     * @throws ModelNotFoundException
238
     *
239
     * @return Model
240
     */
241
    protected function findModelInstance($record)
242
    {
243
        if ($record instanceof Model) {
244
            if (is_null($record->getKey())) {
245
                throw new ModelNotFoundException();
246
            }
247
248
            return $record;
249
        }
250
251
        return $this->getModel()->findOrFail($record);
252
    }
253
254
    /**
255
     * Return any JSON API resource parameters from a request.
256
     *
257
     * @param Request $request
258
     *
259
     * @return array
260
     */
261
    protected function getRequestParameters($request)
262
    {
263
        return [
264
            'fields' => $this->getRequestQuerySet($request, 'fields'),
265
            'include' => $this->getRequestQuerySet($request, 'include'),
266
            'sort' => $this->getRequestQuerySet($request, 'sort'),
267
            'filter' => (array) $request->input('filter'),
268
        ];
269
    }
270
271
    /**
272
     * Return any comma separated values in a request query field as an array.
273
     *
274
     * @param Request $request
275
     * @param string  $key
276
     *
277
     * @return array
278
     */
279
    protected function getRequestQuerySet($request, $key)
280
    {
281
        return preg_split('/,/', $request->input($key), null, PREG_SPLIT_NO_EMPTY);
282
    }
283
284
    /**
285
     * Sort a resource query by one or more attributes.
286
     *
287
     * @param \Illuminate\Database\Eloquent\Builder $query
288
     * @param array                                 $attributes
289
     *
290
     * @return \Illuminate\Database\Eloquent\Builder
291
     */
292
    protected function sortQuery($query, $attributes)
293
    {
294
        foreach ($attributes as $expression) {
295
            $direction = substr($expression, 0, 1) === '-' ? 'desc' : 'asc';
296
            $column = preg_replace('/^\-/', '', $expression);
297
            $query = $query->orderBy($column, $direction);
298
        }
299
300
        return $query;
301
    }
302
303
    /**
304
     * Filter a resource query by one or more attributes.
305
     *
306
     * @param \Illuminate\Database\Eloquent\Builder $query
307
     * @param array                                 $attributes
308
     *
309
     * @return \Illuminate\Database\Eloquent\Builder
310
     */
311
    protected function filterQuery($query, $attributes)
312
    {
313
        $searchableColumns = array_diff(
314
            Schema::getColumnListing($this->getModel()->getTable()),
315
            $this->getModel()->getHidden()
316
        );
317
318
        foreach (array_intersect_key($attributes, array_flip($searchableColumns)) as $column => $value) {
319
            if (is_numeric($value)) {
320
                // Exact numeric match
321
                $query = $query->where($column, $value);
322
            } else if (in_array(strtolower($value), ['true', 'false'])) {
323
                // Boolean match
324
                $query = $query->where($column, filter_var($value, FILTER_VALIDATE_BOOLEAN));
325
            } else {
326
                // Partial string match
327
                $query = $query->where($column, 'LIKE', '%' . $value . '%');
328
            }
329
        }
330
331
        return $query;
332
    }
333
334
    /**
335
     * Update one or more relationships on a model instance.
336
     *
337
     * @param Model $record
338
     * @param array $relationships
339
     */
340
    protected function updateRecordRelationships($record, array $relationships)
341
    {
342
        $relationships = array_intersect_key($relationships, $this->getModelRelationships());
343
344
        foreach ($relationships as $name => $relationship) {
345
            $relation = $this->getModelRelationships()[$name];
346
            $data = $relationship['data'];
347
348
            if ($relation instanceof BelongsTo) {
349
                $record->{$relation->getForeignKey()} = $data['id'];
350
                $record->save();
351
            } else if ($relation instanceof BelongsToMany) {
352
                $record->{$name}()->sync(array_pluck($data, 'id'));
353
            }
354
        }
355
    }
356
}
357