Passed
Push — pagination ( 963a3b...d66803 )
by Alex
02:36
created

JsonApiController::sortQuery()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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