Passed
Pull Request — develop (#3)
by Alex
02:50
created

JsonApiController::updateAction()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 11
rs 9.4285
cc 3
eloc 6
nc 4
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
     *
52
     * @return JsonApiResponse
53
     */
54
    public function indexAction(Request $request)
55
    {
56
        $records = $this->getModel()->newQuery();
57
        $params = $this->getRequestParameters($request);
58
59
        $records = $this->sortQuery($records, $params['sort']);
60
        $records = $this->filterQuery($records, $params['filter']);
61
62
        try {
63
            $records = $records->get();
64
        } catch (QueryException $e) {
65
            return $this->error(Response::HTTP_BAD_REQUEST, 'Invalid query parameters');
66
        }
67
68
        return new JsonApiResponse($this->transformCollection($records, $params['fields']));
69
    }
70
71
    /**
72
     * Store a new record.
73
     *
74
     * @param Request $request
75
     *
76
     * @return JsonApiResponse
77
     */
78
    public function storeAction(Request $request)
79
    {
80
        $record = $this->getModel()->create((array) $request->input('data.attributes'));
81
82
        if ($relationships = $request->input('data.relationships')) {
83
            $this->updateRecordRelationships($record, (array) $relationships);
84
        }
85
86
        return new JsonApiResponse($this->transformRecord($record), Response::HTTP_CREATED);
87
    }
88
89
    /**
90
     * Return a specified record.
91
     *
92
     * @param Request   $request
93
     * @param Model|int $record
94
     *
95
     * @return JsonApiResponse
96
     */
97
    public function showAction(Request $request, $record)
98
    {
99
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
100
        $params = $this->getRequestParameters($request);
101
102
        return new JsonApiResponse($this->transformRecord($record, $params['fields'], $params['include']));
103
    }
104
105
    /**
106
     * Update a specified record.
107
     *
108
     * @param Request   $request
109
     * @param Model|int $record
110
     *
111
     * @return JsonApiResponse
112
     */
113
    public function updateAction(Request $request, $record)
114
    {
115
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
116
        $record->update((array) $request->input('data.attributes'));
117
118
        if ($relationships = $request->input('data.relationships')) {
119
            $this->updateRecordRelationships($record, (array) $relationships);
120
        }
121
122
        return $this->showAction($request, $record);
123
    }
124
125
    /**
126
     * Destroy a specified record.
127
     *
128
     * @param Request   $request
129
     * @param Model|int $record
130
     *
131
     * @return JsonApiResponse
132
     */
133
    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...
134
    {
135
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
136
        $record->delete();
137
138
        return new JsonApiResponse(null, Response::HTTP_NO_CONTENT);
139
    }
140
141
    /**
142
     * Return a specified record relationship.
143
     *
144
     * @param Request   $request
145
     * @param Model|int $record
146
     * @param string    $relation
147
     *
148
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
149
     *
150
     * @return JsonApiResponse
151
     */
152
    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...
153
    {
154
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Reponse::HTTP_NOT_FOUND);
155
156
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
157
        $modelRelation = $this->getModelRelationships()[$relation];
158
159
        if ($modelRelation instanceof BelongsTo) {
160
            $relatedRecord = $record->{$relation};
161
162
            return new JsonApiResponse([
163
                'type' => $relatedRecord->getTable(),
164
                'id' => $relatedRecord->id,
165
            ]);
166
        } else if ($modelRelation instanceof BelongsToMany) {
167
            return new JsonApiResponse($this->transformCollectionIds($record->{$relation}));
168
        }
169
    }
170
171
    /**
172
     * Update a named many-to-one relationship association on a specified record.
173
     * http://jsonapi.org/format/#crud-updating-to-one-relationships
174
     *
175
     * @param Request     $request
176
     * @param Model|int   $record
177
     * @param string      $relation
178
     * @param string|null $foreignKey
179
     *
180
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
181
     *
182
     * @return JsonApiResponse
183
     */
184
    public function updateToOneRelationshipAction(Request $request, $record, $relation, $foreignKey = null)
185
    {
186
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Reponse::HTTP_NOT_FOUND);
187
188
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
189
        $data = (array) $request->input('data');
190
191
        $record->update([($foreignKey ?: $relation . '_id') => $data['id']]);
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()), Reponse::HTTP_NOT_FOUND);
211
212
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
213
        $relationships = (array) $request->input('data');
214
        $items = [];
215
216
        foreach ($relationships as $item) {
217
            $items[$item['id']] = $item['attributes'];
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