Passed
Pull Request — develop (#5)
by Alex
11:38 queued 07:05
created

JsonApiController::storeAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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