Passed
Push — develop ( bfa90e...bacff2 )
by Alex
08:54
created

JsonApiController::showAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 7
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
3
namespace Huntie\JsonApi\Http\Controllers;
4
5
use Huntie\JsonApi\Http\JsonApiResponse;
6
use Huntie\JsonApi\Support\JsonApiErrors;
7
use Huntie\JsonApi\Support\JsonApiTransforms;
8
use Illuminate\Database\QueryException;
9
use Illuminate\Database\Eloquent\Model;
10
use \Illuminate\Database\Eloquent\ModelNotFoundException;
11
use Illuminate\Database\Eloquent\Relations\BelongsTo;
12
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
13
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
14
use Illuminate\Foundation\Validation\ValidatesRequests;
15
use Illuminate\Http\Request;
16
use Illuminate\Http\Response;
17
use Illuminate\Routing\Controller;
18
19
abstract class JsonApiController extends Controller
20
{
21
    use JsonApiErrors, JsonApiTransforms, AuthorizesRequests, ValidatesRequests;
22
23
    /**
24
     * Return the Eloquent Model for the resource.
25
     *
26
     * @return Model
27
     */
28
    abstract protected function getModel();
29
30
    /**
31
     * Return the type name of the resource.
32
     *
33
     * @return string
34
     */
35
    protected function getModelType()
36
    {
37
        return str_slug($this->getModel()->getTable());
38
    }
39
40
    /**
41
     * The model relationships that can be updated.
42
     *
43
     * @return string
44
     */
45
    protected function getModelRelationships()
46
    {
47
        return [];
48
    }
49
50
    /**
51
     * Return a listing of the resource.
52
     *
53
     * @param Request                                    $request
54
     * @param \Illuminate\Database\Eloquent\Builder|null $query   Custom resource query
55
     *
56
     * @return JsonApiResponse
57
     */
58
    public function indexAction(Request $request, $query = null)
59
    {
60
        $records = $query ?: $this->getModel()->newQuery();
61
        $params = $this->getRequestParameters($request);
62
63
        $records = $this->sortQuery($records, $params['sort']);
64
        $records = $this->filterQuery($records, $params['filter']);
65
66
        try {
67
            $pageSize = min($this->getModel()->getPerPage(), $request->input('page.size'));
68
            $pageNumber = $request->input('page.number') ?: 1;
69
70
            $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...
71
        } catch (QueryException $e) {
72
            return $this->error(Response::HTTP_BAD_REQUEST, 'Invalid query parameters');
73
        }
74
75
        return new JsonApiResponse($this->transformCollection($records, $params['fields'], $params['include']));
76
    }
77
78
    /**
79
     * Store a new record.
80
     *
81
     * @param Request $request
82
     *
83
     * @return JsonApiResponse
84
     */
85
    public function storeAction(Request $request)
86
    {
87
        $record = $this->getModel()->create((array) $request->input('data.attributes'));
88
89
        if ($relationships = $request->input('data.relationships')) {
90
            $this->updateRecordRelationships($record, (array) $relationships);
91
        }
92
93
        return new JsonApiResponse($this->transformRecord($record), Response::HTTP_CREATED);
94
    }
95
96
    /**
97
     * Return a specified record.
98
     *
99
     * @param Request   $request
100
     * @param Model|int $record
101
     *
102
     * @return JsonApiResponse
103
     */
104
    public function showAction(Request $request, $record)
105
    {
106
        $record = $this->findModelInstance($record);
107
        $params = $this->getRequestParameters($request);
108
109
        return new JsonApiResponse($this->transformRecord($record, $params['fields'], $params['include']));
110
    }
111
112
    /**
113
     * Update a specified record.
114
     *
115
     * @param Request   $request
116
     * @param Model|int $record
117
     *
118
     * @return JsonApiResponse
119
     */
120
    public function updateAction(Request $request, $record)
121
    {
122
        $record = $this->findModelInstance($record);
123
        $record->fill((array) $request->input('data.attributes'));
124
        $record->save();
125
126
        if ($relationships = $request->input('data.relationships')) {
127
            $this->updateRecordRelationships($record, (array) $relationships);
128
        }
129
130
        return $this->showAction($request, $record);
131
    }
132
133
    /**
134
     * Destroy a specified record.
135
     *
136
     * @param Request   $request
137
     * @param Model|int $record
138
     *
139
     * @return JsonApiResponse
140
     */
141
    public function destroyAction(Request $request, $record)
142
    {
143
        $record = $this->findModelInstance($record);
144
        $record->delete();
145
146
        return new JsonApiResponse(null, Response::HTTP_NO_CONTENT);
147
    }
148
149
    /**
150
     * Return a specified record relationship.
151
     *
152
     * @param Request   $request
153
     * @param Model|int $record
154
     * @param string    $relation
155
     *
156
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
157
     *
158
     * @return JsonApiResponse
159
     */
160
    public function relationshipAction(Request $request, $record, $relation)
161
    {
162
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
163
164
        $record = $this->findModelInstance($record);
165
166
        return new JsonApiResponse($this->transformRelationship($record->{$relation}));
167
    }
168
169
    /**
170
     * Update a named many-to-one relationship association on a specified record.
171
     * http://jsonapi.org/format/#crud-updating-to-one-relationships
172
     *
173
     * @param Request     $request
174
     * @param Model|int   $record
175
     * @param string      $relation
176
     *
177
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
178
     *
179
     * @return JsonApiResponse
180
     */
181
    public function updateToOneRelationshipAction(Request $request, $record, $relation)
182
    {
183
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
184
185
        $record = $this->findModelInstance($record);
186
        $relation = $this->getModelRelationships()[$relation];
187
        $data = (array) $request->input('data');
188
189
        $record->{$relation->getForeignKey()} = $data['id'];
190
        $record->save();
191
192
        return new JsonApiResponse();
193
    }
194
195
    /**
196
     * Update named many-to-many relationship entries on a specified record.
197
     * http://jsonapi.org/format/#crud-updating-to-many-relationships
198
     *
199
     * @param Request   $request
200
     * @param Model|int $record
201
     * @param string    $relation
202
     *
203
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
204
     *
205
     * @return JsonApiResponse
206
     */
207
    public function updateToManyRelationshipAction(Request $request, $record, $relation)
208
    {
209
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
210
211
        $record = $this->findModelInstance($record);
212
        $relationships = (array) $request->input('data');
213
        $items = [];
214
215
        foreach ($relationships as $item) {
216
            if (isset($item['attributes'])) {
217
                $items[$item['id']] = $item['attributes'];
218
            } else {
219
                $items[] = $item['id'];
220
            }
221
        }
222
223
        switch ($request->method()) {
224
            case 'PATCH':
225
                $record->{$relation}()->sync($items);
226
                break;
227
            case 'POST':
228
                $record->{$relation}()->sync($items, false);
229
                break;
230
            case 'DELETE':
231
                $record->{$relation}()->detach(array_keys($items));
232
        }
233
234
        return new JsonApiResponse();
235
    }
236
237
    /**
238
     * Return existing instance of the resource or find by primary key.
239
     *
240
     * @param Model|int $record
241
     *
242
     * @throws ModelNotFoundException
243
     *
244
     * @return Model
245
     */
246
    protected function findModelInstance($record)
247
    {
248
        if ($record instanceof Model) {
249
            if (is_null($record->getKey())) {
250
                throw new ModelNotFoundException();
251
            }
252
253
            return $record;
254
        }
255
256
        return $this->getModel()->findOrFail($record);
257
    }
258
259
    /**
260
     * Return any JSON API resource parameters from a request.
261
     *
262
     * @param Request $request
263
     *
264
     * @return array
265
     */
266
    protected function getRequestParameters($request)
267
    {
268
        return [
269
            'fields' => $this->getRequestQuerySet($request, 'fields.' . $this->getModelType()),
270
            'include' => $this->getRequestQuerySet($request, 'include'),
271
            'sort' => $this->getRequestQuerySet($request, 'sort'),
272
            'filter' => (array) $request->input('filter'),
273
        ];
274
    }
275
276
    /**
277
     * Return any comma separated values in a request query field as an array.
278
     *
279
     * @param Request $request
280
     * @param string  $key
281
     *
282
     * @return array
283
     */
284
    protected function getRequestQuerySet($request, $key)
285
    {
286
        return preg_split('/,/', $request->input($key), null, PREG_SPLIT_NO_EMPTY);
287
    }
288
289
    /**
290
     * Sort a resource query by one or more attributes.
291
     *
292
     * @param \Illuminate\Database\Eloquent\Builder $query
293
     * @param array                                 $attributes
294
     *
295
     * @return \Illuminate\Database\Eloquent\Builder
296
     */
297
    protected function sortQuery($query, $attributes)
298
    {
299
        foreach ($attributes as $expression) {
300
            $direction = substr($expression, 0, 1) === '-' ? 'desc' : 'asc';
301
            $column = preg_replace('/^\-/', '', $expression);
302
            $query = $query->orderBy($column, $direction);
303
        }
304
305
        return $query;
306
    }
307
308
    /**
309
     * Filter a resource query by one or more attributes.
310
     *
311
     * @param \Illuminate\Database\Eloquent\Builder $query
312
     * @param array                                 $attributes
313
     *
314
     * @return \Illuminate\Database\Eloquent\Builder
315
     */
316
    protected function filterQuery($query, $attributes)
317
    {
318
        foreach ($attributes 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