Passed
Push — develop ( 05dbed...0039ba )
by Alex
03:12
created

JsonApiController::getModel()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 1
nc 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
            $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
            $relatedRecord = $record->{$relation};
162
163
            return new JsonApiResponse([
164
                'type' => $relatedRecord->getTable(),
165
                'id' => $relatedRecord->id,
166
            ]);
167
        } else if ($modelRelation instanceof BelongsToMany) {
168
            return new JsonApiResponse($this->transformCollectionIds($record->{$relation}));
169
        }
170
    }
171
172
    /**
173
     * Update a named many-to-one relationship association on a specified record.
174
     * http://jsonapi.org/format/#crud-updating-to-one-relationships
175
     *
176
     * @param Request     $request
177
     * @param Model|int   $record
178
     * @param string      $relation
179
     * @param string|null $foreignKey
180
     *
181
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
182
     *
183
     * @return JsonApiResponse
184
     */
185
    public function updateToOneRelationshipAction(Request $request, $record, $relation, $foreignKey = null)
186
    {
187
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
188
189
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
190
        $data = (array) $request->input('data');
191
192
        $record->update([($foreignKey ?: $relation . '_id') => $data['id']]);
193
194
        return new JsonApiResponse();
195
    }
196
197
    /**
198
     * Update named many-to-many relationship entries on a specified record.
199
     * http://jsonapi.org/format/#crud-updating-to-many-relationships
200
     *
201
     * @param Request   $request
202
     * @param Model|int $record
203
     * @param string    $relation
204
     *
205
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
206
     *
207
     * @return JsonApiResponse
208
     */
209
    public function updateToManyRelationshipAction(Request $request, $record, $relation)
210
    {
211
        abort_if(!array_key_exists($relation, $this->getModelRelationships()), Response::HTTP_NOT_FOUND);
212
213
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
214
        $relationships = (array) $request->input('data');
215
        $items = [];
216
217
        foreach ($relationships as $item) {
218
            if (isset($item['attributes'])) {
219
                $items[$item['id']] = $item['attributes'];
220
            } else {
221
                $items[] = $item['id'];
222
            }
223
        }
224
225
        switch ($request->method()) {
226
            case 'PATCH':
227
                $record->{$relation}()->sync($items);
228
                break;
229
            case 'POST':
230
                $record->{$relation}()->sync($items, false);
231
                break;
232
            case 'DELETE':
233
                $record->{$relation}()->detach(array_keys($items));
234
        }
235
236
        return new JsonApiResponse();
237
    }
238
239
    /**
240
     * Return an instance of the resource by primary key.
241
     *
242
     * @param int $key
243
     *
244
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
245
     *
246
     * @return Model
247
     */
248
    protected function findModelInstance($key)
249
    {
250
        return $this->getModel()->findOrFail($key);
251
    }
252
253
    /**
254
     * Return any JSON API resource parameters from a request.
255
     *
256
     * @param Request $request
257
     *
258
     * @return array
259
     */
260
    protected function getRequestParameters($request)
261
    {
262
        return [
263
            'fields' => $this->getRequestQuerySet($request, 'fields.' . $this->getModelType()),
264
            'include' => $this->getRequestQuerySet($request, 'include'),
265
            'sort' => $this->getRequestQuerySet($request, 'sort'),
266
            'filter' => (array) $request->input('filter'),
267
        ];
268
    }
269
270
    /**
271
     * Return any comma separated values in a request query field as an array.
272
     *
273
     * @param Request $request
274
     * @param string  $key
275
     *
276
     * @return array
277
     */
278
    protected function getRequestQuerySet($request, $key)
279
    {
280
        return preg_split('/,/', $request->input($key), null, PREG_SPLIT_NO_EMPTY);
281
    }
282
283
    /**
284
     * Sort a resource query by one or more attributes.
285
     *
286
     * @param \Illuminate\Database\Eloquent\Builder $query
287
     * @param array                                 $attributes
288
     *
289
     * @return \Illuminate\Database\Eloquent\Builder
290
     */
291
    protected function sortQuery($query, $attributes)
292
    {
293
        foreach ($attributes as $expression) {
294
            $direction = substr($expression, 0, 1) === '-' ? 'desc' : 'asc';
295
            $column = preg_replace('/^\-/', '', $expression);
296
            $query = $query->orderBy($column, $direction);
297
        }
298
299
        return $query;
300
    }
301
302
    /**
303
     * Filter a resource query by one or more attributes.
304
     *
305
     * @param \Illuminate\Database\Eloquent\Builder $query
306
     * @param array                                 $attributes
307
     *
308
     * @return \Illuminate\Database\Eloquent\Builder
309
     */
310
    protected function filterQuery($query, $attributes)
311
    {
312
        foreach ($attributes as $column => $value) {
313
            if (is_numeric($value)) {
314
                // Exact numeric match
315
                $query = $query->where($column, $value);
316
            } else if (in_array(strtolower($value), ['true', 'false'])) {
317
                // Boolean match
318
                $query = $query->where($column, filter_var($value, FILTER_VALIDATE_BOOLEAN));
319
            } else {
320
                // Partial string match
321
                $query = $query->where($column, 'like', '%' . $value . '%');
322
            }
323
        }
324
325
        return $query;
326
    }
327
328
    /**
329
     * Update one or more relationships on a model instance.
330
     *
331
     * @param Model $record
332
     * @param array $relationships
333
     */
334
    protected function updateRecordRelationships($record, array $relationships)
335
    {
336
        $relationships = array_intersect_key($relationships, $this->getModelRelationships());
337
338
        foreach ($relationships as $name => $relationship) {
339
            $relation = $this->getModelRelationships()[$name];
340
            $data = $relationship['data'];
341
342
            if ($relation instanceof BelongsTo) {
343
                $record->update([$relation->getForeignKey() => $data['id']]);
344
            } else if ($relation instanceof BelongsToMany) {
345
                $record->{$name}()->sync(array_pluck($data, 'id'));
346
            }
347
        }
348
    }
349
}
350