Passed
Push — develop ( 7b9cb8...cb4aba )
by Alex
02:46
created

JsonApiController::indexAction()   C

Complexity

Conditions 7
Paths 24

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 4
Metric Value
c 5
b 0
f 4
dl 0
loc 32
rs 6.7272
cc 7
eloc 19
nc 24
nop 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 Illuminate\Database\QueryException;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Http\Request;
10
use Illuminate\Http\Response;
11
use Illuminate\Routing\Controller;
12
13
abstract class JsonApiController extends Controller
14
{
15
    use JsonApiErrors;
16
17
    /**
18
     * Return the Eloquent Model for the resource.
19
     *
20
     * @return Model
21
     */
22
    abstract protected function getModel();
23
24
    /**
25
     * Return the type name of the resource.
26
     *
27
     * @return string
28
     */
29
    protected function getModelType()
30
    {
31
        return $this->getModel()->getTable();
32
    }
33
34
    /**
35
     * Return a listing of the resource.
36
     *
37
     * @param Request $request
38
     *
39
     * @return JsonApiResponse
40
     */
41
    public function indexAction(Request $request)
42
    {
43
        $records = $this->getModel()->newQuery();
44
        $params = $this->getRequestParameters($request);
45
46
        foreach ($params['sort'] as $expression) {
47
            $direction = substr($expression, 0, 1) === '-' ? 'desc' : 'asc';
48
            $column = preg_replace('/^\-/', '', $expression);
49
            $records = $records->orderby($column, $direction);
50
        }
51
52
        foreach ($params['filter'] as $attribute => $value) {
53
            if (is_numeric($value)) {
54
                // Exact numeric match
55
                $records = $records->where($attribute, $value);
56
            } else if (in_array(strtolower($value), ['true', 'false'])) {
57
                // Boolean match
58
                $records = $records->where($attribute, filter_var($value, FILTER_VALIDATE_BOOLEAN));
59
            } else {
60
                // Partial string match
61
                $records = $records->where($attribute, 'like', "%$value%");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $value instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
62
            }
63
        }
64
65
        try {
66
            $records = $records->get();
67
        } catch (QueryException $e) {
68
            return $this->error(Response::HTTP_BAD_REQUEST, 'Invalid query parameters');
69
        }
70
71
        return new JsonApiResponse($this->transformCollection($records, $params['fields']));
72
    }
73
74
    /**
75
     * Store a new record.
76
     *
77
     * @param Request $request
78
     *
79
     * @return JsonApiResponse
80
     */
81
    public function storeAction(Request $request)
82
    {
83
        $record = $this->getModel()->create((array) $request->input('data.attributes'));
84
85
        return new JsonApiResponse($this->transformRecord($record), Response::HTTP_CREATED);
86
    }
87
88
    /**
89
     * Return a specified record.
90
     *
91
     * @param Request   $request
92
     * @param Model|int $record
93
     *
94
     * @return JsonApiResponse
95
     */
96
    public function showAction(Request $request, $record)
97
    {
98
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
99
        $params = $this->getRequestParameters($request);
100
101
        return new JsonApiResponse($this->transformRecord($record, $params['fields'], $params['include']));
102
    }
103
104
    /**
105
     * Update a specified record.
106
     *
107
     * @param Request   $request
108
     * @param Model|int $record
109
     *
110
     * @return JsonApiResponse
111
     */
112
    public function updateAction(Request $request, $record)
113
    {
114
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
115
        $record->update((array) $request->input('data.attributes'));
116
117
        return $this->showAction($request, $record);
118
    }
119
120
    /**
121
     * Destroy a specified record.
122
     *
123
     * @param Request   $request
124
     * @param Model|int $record
125
     *
126
     * @return JsonApiResponse
127
     */
128
    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...
129
    {
130
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
131
        $record->delete();
132
133
        return new JsonApiResponse(null, Response::HTTP_NO_CONTENT);
134
    }
135
136
    /**
137
     * Update a named many-to-one relationship association on a specified record.
138
     * http://jsonapi.org/format/#crud-updating-to-one-relationships
139
     *
140
     * @param Request     $request
141
     * @param Model|int   $record
142
     * @param string      $relation
143
     * @param string|null $foreignKey
144
     *
145
     * @return JsonApiResponse
146
     */
147
    public function updateToOneRelationship(Request $request, $record, $relation, $foreignKey = null)
148
    {
149
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
150
        $data = (array) $request->input('data');
151
152
        $record->update([($foreignKey ?: $relation . '_id') => $data['id']]);
153
154
        return new JsonApiResponse(null, Response::HTTP_OK);
155
    }
156
157
    /**
158
     * Update named many-to-many relationship entries on a specified record.
159
     * http://jsonapi.org/format/#crud-updating-to-many-relationships
160
     *
161
     * @param Request   $request
162
     * @param Model|int $record
163
     * @param string    $relation
164
     *
165
     * @return JsonApiResponse
166
     */
167
    public function updateToManyRelationship(Request $request, $record, $relation)
168
    {
169
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
170
        $relationships = (array) $request->input('data');
171
        $items = [];
172
173
        foreach ($relationships as $item) {
174
            $items[$item['id']] = $item['attributes'];
175
        }
176
177
        switch ($request->method()) {
178
            case 'PATCH':
179
                $record->{$relation}()->sync($items);
180
                break;
181
            case 'POST':
182
                $record->{$relation}()->sync($items, false);
183
                break;
184
            case 'DELETE':
185
                $record->{$relation}()->detach(array_keys($items));
186
        }
187
188
        return new JsonApiResponse(null, Response::HTTP_OK);
189
    }
190
191
    /**
192
     * Return an instance of the resource by primary key.
193
     *
194
     * @param int $key
195
     *
196
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
197
     *
198
     * @return Model
199
     */
200
    protected function findModelInstance($key)
201
    {
202
        return $this->getModel()->findOrFail($key);
203
    }
204
205
    /**
206
     * Return any JSON API resource parameters from a request.
207
     *
208
     * @param Request $request
209
     *
210
     * @return array
211
     */
212
    protected function getRequestParameters($request)
213
    {
214
        return [
215
            'fields' => $this->getRequestQuerySet($request, 'fields.' . $this->getModelType()),
216
            'include' => $this->getRequestQuerySet($request, 'include'),
217
            'sort' => $this->getRequestQuerySet($request, 'sort'),
218
            'filter' => (array) $request->input('filter'),
219
        ];
220
    }
221
222
    /**
223
     * Return any comma separated values in a request query field as an array.
224
     *
225
     * @param Request $request
226
     * @param string  $key
227
     *
228
     * @return array
229
     */
230
    protected function getRequestQuerySet($request, $key)
231
    {
232
        return preg_split('/,/', $request->input($key), null, PREG_SPLIT_NO_EMPTY);
233
    }
234
235
    /**
236
     * Transform a set of models into a JSON API collection.
237
     *
238
     * @param \Illuminate\Support\Collection $records
239
     * @param array                          $fields
240
     *
241
     * @return array
242
     */
243
    protected function transformCollection($records, array $fields = [])
244
    {
245
        $data = [];
246
247
        foreach ($records as $record) {
248
            $data[] = $this->transformRecord($record, $fields)['data'];
249
        }
250
251
        return compact('data');
252
    }
253
254
    /**
255
     * Transform a model instance into a JSON API object.
256
     *
257
     * @param Model      $record
258
     * @param array|null $fields  Field names of attributes to limit to
259
     * @param array|null $include Relations to include
260
     *
261
     * @return array
262
     */
263
    protected function transformRecord($record, array $fields = [], array $include = [])
264
    {
265
        $relations = array_unique(array_merge($record->getRelations(), $include));
266
        $attributes = $record->load($relations)->toArray();
267
        $relationships = [];
268
        $included = [];
269
270
        foreach ($relations as $relation) {
271
            $relationships[$relation] = [
272
                'data' => []
273
            ];
274
275
            foreach (array_pull($attributes, $relation) as $relatedRecord) {
276
                $relationships[$relation]['data'][] = [
277
                    'type' => $relation,
278
                    'id' => $relatedRecord['id'],
279
                ];
280
281
                if (in_array($relation, $include)) {
282
                    $included[] = [
283
                        'type' => $relation,
284
                        'id' => $relatedRecord['id'],
285
                        'attributes' => array_except($relatedRecord, ['id', 'pivot']),
286
                    ];
287
                }
288
            }
289
        }
290
291
        if (!empty($fields)) {
292
            $attributes = array_only($attributes, $fields);
293
        }
294
295
        $data = array_filter([
296
            'type' => $record->getTable(),
297
            'id' => $record->id,
298
            'attributes' => array_except($attributes, ['id']),
299
            'relationships' => $relationships,
300
        ]);
301
302
        return array_filter(compact('data', 'included'));
303
    }
304
}
305