Passed
Push — develop ( bacff2...0824c2 )
by Alex
02:37
created

JsonApiController::getModelRelationships()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Huntie\JsonApi\Http\Controllers;
4
5
use Schema;
6
use Huntie\JsonApi\Http\JsonApiResponse;
7
use Huntie\JsonApi\Support\JsonApiErrors;
8
use Huntie\JsonApi\Support\JsonApiTransforms;
9
use Illuminate\Database\QueryException;
10
use Illuminate\Database\Eloquent\Model;
11
use \Illuminate\Database\Eloquent\ModelNotFoundException;
12
use Illuminate\Database\Eloquent\Relations\BelongsTo;
13
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
14
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
15
use Illuminate\Foundation\Validation\ValidatesRequests;
16
use Illuminate\Http\Request;
17
use Illuminate\Http\Response;
18
use Illuminate\Routing\Controller;
19
20
abstract class JsonApiController extends Controller
21
{
22
    use JsonApiErrors, JsonApiTransforms, AuthorizesRequests, ValidatesRequests;
23
24
    /**
25
     * Return the Eloquent Model for the resource.
26
     *
27
     * @return Model
28
     */
29
    abstract protected function getModel();
30
31
    /**
32
     * Return the type name of the resource.
33
     *
34
     * @return string
35
     */
36
    protected function getModelType()
37
    {
38
        return str_slug($this->getModel()->getTable());
39
    }
40
41
    /**
42
     * The model relationships that can be updated.
43
     *
44
     * @return string
45
     */
46
    protected function getModelRelationships()
47
    {
48
        return [];
49
    }
50
51
    /**
52
     * Return a listing of the resource.
53
     *
54
     * @param Request                                    $request
55
     * @param \Illuminate\Database\Eloquent\Builder|null $query   Custom resource query
56
     *
57
     * @return JsonApiResponse
58
     */
59
    public function indexAction(Request $request, $query = null)
60
    {
61
        $records = $query ?: $this->getModel()->newQuery();
62
        $params = $this->getRequestParameters($request);
63
64
        $records = $this->sortQuery($records, $params['sort']);
65
        $records = $this->filterQuery($records, $params['filter']);
66
67
        try {
68
            $pageSize = min($this->getModel()->getPerPage(), $request->input('page.size'));
69
            $pageNumber = $request->input('page.number') ?: 1;
70
71
            $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...
72
        } catch (QueryException $e) {
73
            return $this->error(Response::HTTP_BAD_REQUEST, 'Invalid query parameters');
74
        }
75
76
        return new JsonApiResponse($this->transformCollection($records, $params['fields'], $params['include']));
77
    }
78
79
    /**
80
     * Store a new record.
81
     *
82
     * @param Request $request
83
     *
84
     * @return JsonApiResponse
85
     */
86
    public function storeAction(Request $request)
87
    {
88
        $record = $this->getModel()->create((array) $request->input('data.attributes'));
89
90
        if ($relationships = $request->input('data.relationships')) {
91
            $this->updateRecordRelationships($record, (array) $relationships);
92
        }
93
94
        return new JsonApiResponse($this->transformRecord($record), Response::HTTP_CREATED);
95
    }
96
97
    /**
98
     * Return a specified record.
99
     *
100
     * @param Request   $request
101
     * @param Model|int $record
102
     *
103
     * @return JsonApiResponse
104
     */
105
    public function showAction(Request $request, $record)
106
    {
107
        $record = $this->findModelInstance($record);
108
        $params = $this->getRequestParameters($request);
109
110
        return new JsonApiResponse($this->transformRecord($record, $params['fields'], $params['include']));
111
    }
112
113
    /**
114
     * Update a specified record.
115
     *
116
     * @param Request   $request
117
     * @param Model|int $record
118
     *
119
     * @return JsonApiResponse
120
     */
121
    public function updateAction(Request $request, $record)
122
    {
123
        $record = $this->findModelInstance($record);
124
        $record->fill((array) $request->input('data.attributes'));
125
        $record->save();
126
127
        if ($relationships = $request->input('data.relationships')) {
128
            $this->updateRecordRelationships($record, (array) $relationships);
129
        }
130
131
        return $this->showAction($request, $record);
132
    }
133
134
    /**
135
     * Destroy a specified record.
136
     *
137
     * @param Request   $request
138
     * @param Model|int $record
139
     *
140
     * @return JsonApiResponse
141
     */
142
    public function destroyAction(Request $request, $record)
143
    {
144
        $record = $this->findModelInstance($record);
145
        $record->delete();
146
147
        return new JsonApiResponse(null, Response::HTTP_NO_CONTENT);
148
    }
149
150
    /**
151
     * Return a specified record relationship.
152
     *
153
     * @param Request   $request
154
     * @param Model|int $record
155
     * @param string    $relation
156
     *
157
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
158
     *
159
     * @return JsonApiResponse
160
     */
161
    public function relationshipAction(Request $request, $record, $relation)
162
    {
163
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
164
165
        $record = $this->findModelInstance($record);
166
167
        return new JsonApiResponse($this->transformRelationship($record->{$relation}));
168
    }
169
170
    /**
171
     * Update a named many-to-one relationship association on a specified record.
172
     * http://jsonapi.org/format/#crud-updating-to-one-relationships
173
     *
174
     * @param Request     $request
175
     * @param Model|int   $record
176
     * @param string      $relation
177
     *
178
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
179
     *
180
     * @return JsonApiResponse
181
     */
182
    public function updateToOneRelationshipAction(Request $request, $record, $relation)
183
    {
184
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
185
186
        $record = $this->findModelInstance($record);
187
        $relation = $this->getModelRelationships()[$relation];
188
        $data = (array) $request->input('data');
189
190
        $record->{$relation->getForeignKey()} = $data['id'];
191
        $record->save();
192
193
        return new JsonApiResponse();
194
    }
195
196
    /**
197
     * Update named many-to-many relationship entries on a specified record.
198
     * http://jsonapi.org/format/#crud-updating-to-many-relationships
199
     *
200
     * @param Request   $request
201
     * @param Model|int $record
202
     * @param string    $relation
203
     *
204
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
205
     *
206
     * @return JsonApiResponse
207
     */
208
    public function updateToManyRelationshipAction(Request $request, $record, $relation)
209
    {
210
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
211
212
        $record = $this->findModelInstance($record);
213
        $relationships = (array) $request->input('data');
214
        $items = [];
215
216
        foreach ($relationships as $item) {
217
            if (isset($item['attributes'])) {
218
                $items[$item['id']] = $item['attributes'];
219
            } else {
220
                $items[] = $item['id'];
221
            }
222
        }
223
224
        switch ($request->method()) {
225
            case 'PATCH':
226
                $record->{$relation}()->sync($items);
227
                break;
228
            case 'POST':
229
                $record->{$relation}()->sync($items, false);
230
                break;
231
            case 'DELETE':
232
                $record->{$relation}()->detach(array_keys($items));
233
        }
234
235
        return new JsonApiResponse();
236
    }
237
238
    /**
239
     * Return existing instance of the resource or find by primary key.
240
     *
241
     * @param Model|int $record
242
     *
243
     * @throws ModelNotFoundException
244
     *
245
     * @return Model
246
     */
247
    protected function findModelInstance($record)
248
    {
249
        if ($record instanceof Model) {
250
            if (is_null($record->getKey())) {
251
                throw new ModelNotFoundException();
252
            }
253
254
            return $record;
255
        }
256
257
        return $this->getModel()->findOrFail($record);
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
        $searchableColumns = Schema::getColumnListing($this->getModel()->getTable());
320
321
        foreach ($attributes as $column => $value) {
322
            if (!in_array($column, $searchableColumns)) {
323
                continue;
324
            }
325
326
            if (is_numeric($value)) {
327
                // Exact numeric match
328
                $query = $query->where($column, $value);
329
            } else if (in_array(strtolower($value), ['true', 'false'])) {
330
                // Boolean match
331
                $query = $query->where($column, filter_var($value, FILTER_VALIDATE_BOOLEAN));
332
            } else {
333
                // Partial string match
334
                $query = $query->where($column, 'like', '%' . $value . '%');
335
            }
336
        }
337
338
        return $query;
339
    }
340
341
    /**
342
     * Update one or more relationships on a model instance.
343
     *
344
     * @param Model $record
345
     * @param array $relationships
346
     */
347
    protected function updateRecordRelationships($record, array $relationships)
348
    {
349
        $relationships = array_intersect_key($relationships, $this->getModelRelationships());
350
351
        foreach ($relationships as $name => $relationship) {
352
            $relation = $this->getModelRelationships()[$name];
353
            $data = $relationship['data'];
354
355
            if ($relation instanceof BelongsTo) {
356
                $record->{$relation->getForeignKey()} = $data['id'];
357
                $record->save();
358
            } else if ($relation instanceof BelongsToMany) {
359
                $record->{$name}()->sync(array_pluck($data, 'id'));
360
            }
361
        }
362
    }
363
}
364