Passed
Push — develop ( 7f9ed1...7fd325 )
by Alex
02:45
created

JsonApiController::relationshipAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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