Passed
Push — relationships ( cfabf1 )
by Alex
02:45
created

JsonApiController   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 310
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 9
Bugs 0 Features 7
Metric Value
wmc 37
c 9
b 0
f 7
lcom 1
cbo 5
dl 0
loc 310
rs 8.6

15 Methods

Rating   Name   Duplication   Size   Complexity  
getModel() 0 1 ?
A getModelType() 0 4 1
A getModelRelationships() 0 4 1
C indexAction() 0 32 7
A storeAction() 0 6 1
A showAction() 0 7 2
A updateAction() 0 7 2
A destroyAction() 0 7 2
A updateToOneRelationshipAction() 0 13 4
C updateToManyRelationshipAction() 0 27 7
A findModelInstance() 0 4 1
A getRequestParameters() 0 9 1
A getRequestQuerySet() 0 4 1
A transformCollection() 0 10 2
B transformRecord() 0 41 5
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 the names of relationships that can be updated.
36
     *
37
     * @return string
38
     */
39
    protected function getModelRelationships()
40
    {
41
        return [];
42
    }
43
44
    /**
45
     * Return a listing of the resource.
46
     *
47
     * @param Request $request
48
     *
49
     * @return JsonApiResponse
50
     */
51
    public function indexAction(Request $request)
52
    {
53
        $records = $this->getModel()->newQuery();
54
        $params = $this->getRequestParameters($request);
55
56
        foreach ($params['sort'] as $expression) {
57
            $direction = substr($expression, 0, 1) === '-' ? 'desc' : 'asc';
58
            $column = preg_replace('/^\-/', '', $expression);
59
            $records = $records->orderby($column, $direction);
60
        }
61
62
        foreach ($params['filter'] as $attribute => $value) {
63
            if (is_numeric($value)) {
64
                // Exact numeric match
65
                $records = $records->where($attribute, $value);
66
            } else if (in_array(strtolower($value), ['true', 'false'])) {
67
                // Boolean match
68
                $records = $records->where($attribute, filter_var($value, FILTER_VALIDATE_BOOLEAN));
69
            } else {
70
                // Partial string match
71
                $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...
72
            }
73
        }
74
75
        try {
76
            $records = $records->get();
77
        } catch (QueryException $e) {
78
            return $this->error(Response::HTTP_BAD_REQUEST, 'Invalid query parameters');
79
        }
80
81
        return new JsonApiResponse($this->transformCollection($records, $params['fields']));
82
    }
83
84
    /**
85
     * Store a new record.
86
     *
87
     * @param Request $request
88
     *
89
     * @return JsonApiResponse
90
     */
91
    public function storeAction(Request $request)
92
    {
93
        $record = $this->getModel()->create((array) $request->input('data.attributes'));
94
95
        return new JsonApiResponse($this->transformRecord($record), Response::HTTP_CREATED);
96
    }
97
98
    /**
99
     * Return a specified record.
100
     *
101
     * @param Request   $request
102
     * @param Model|int $record
103
     *
104
     * @return JsonApiResponse
105
     */
106
    public function showAction(Request $request, $record)
107
    {
108
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
109
        $params = $this->getRequestParameters($request);
110
111
        return new JsonApiResponse($this->transformRecord($record, $params['fields'], $params['include']));
112
    }
113
114
    /**
115
     * Update a specified record.
116
     *
117
     * @param Request   $request
118
     * @param Model|int $record
119
     *
120
     * @return JsonApiResponse
121
     */
122
    public function updateAction(Request $request, $record)
123
    {
124
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
125
        $record->update((array) $request->input('data.attributes'));
126
127
        return $this->showAction($request, $record);
128
    }
129
130
    /**
131
     * Destroy a specified record.
132
     *
133
     * @param Request   $request
134
     * @param Model|int $record
135
     *
136
     * @return JsonApiResponse
137
     */
138
    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...
139
    {
140
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
141
        $record->delete();
142
143
        return new JsonApiResponse(null, Response::HTTP_NO_CONTENT);
144
    }
145
146
    /**
147
     * Update a named many-to-one relationship association on a specified record.
148
     * http://jsonapi.org/format/#crud-updating-to-one-relationships
149
     *
150
     * @param Request     $request
151
     * @param Model|int   $record
152
     * @param string      $relation
153
     * @param string|null $foreignKey
154
     *
155
     * @return JsonApiResponse
156
     */
157
    public function updateToOneRelationshipAction(Request $request, $record, $relation, $foreignKey = null)
158
    {
159
        if (!in_array($relation, $this->getModelRelationships())) {
160
            return $this->error(Reponse::HTTP_NOT_FOUND);
0 ignored issues
show
Bug introduced by
The call to error() misses a required argument $title.

This check looks for function calls that miss required arguments.

Loading history...
161
        }
162
163
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
164
        $data = (array) $request->input('data');
165
166
        $record->update([($foreignKey ?: $relation . '_id') => $data['id']]);
167
168
        return new JsonApiResponse();
169
    }
170
171
    /**
172
     * Update named many-to-many relationship entries on a specified record.
173
     * http://jsonapi.org/format/#crud-updating-to-many-relationships
174
     *
175
     * @param Request   $request
176
     * @param Model|int $record
177
     * @param string    $relation
178
     *
179
     * @return JsonApiResponse
180
     */
181
    public function updateToManyRelationshipAction(Request $request, $record, $relation)
182
    {
183
        if (!in_array($relation, $this->getModelRelationships())) {
184
            return $this->error(Reponse::HTTP_NOT_FOUND);
0 ignored issues
show
Bug introduced by
The call to error() misses a required argument $title.

This check looks for function calls that miss required arguments.

Loading history...
185
        }
186
187
        $record = $record instanceof Model ? $record : $this->findModelInstance($record);
188
        $relationships = (array) $request->input('data');
189
        $items = [];
190
191
        foreach ($relationships as $item) {
192
            $items[$item['id']] = $item['attributes'];
193
        }
194
195
        switch ($request->method()) {
196
            case 'PATCH':
197
                $record->{$relation}()->sync($items);
198
                break;
199
            case 'POST':
200
                $record->{$relation}()->sync($items, false);
201
                break;
202
            case 'DELETE':
203
                $record->{$relation}()->detach(array_keys($items));
204
        }
205
206
        return new JsonApiResponse();
207
    }
208
209
    /**
210
     * Return an instance of the resource by primary key.
211
     *
212
     * @param int $key
213
     *
214
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
215
     *
216
     * @return Model
217
     */
218
    protected function findModelInstance($key)
219
    {
220
        return $this->getModel()->findOrFail($key);
221
    }
222
223
    /**
224
     * Return any JSON API resource parameters from a request.
225
     *
226
     * @param Request $request
227
     *
228
     * @return array
229
     */
230
    protected function getRequestParameters($request)
231
    {
232
        return [
233
            'fields' => $this->getRequestQuerySet($request, 'fields.' . $this->getModelType()),
234
            'include' => $this->getRequestQuerySet($request, 'include'),
235
            'sort' => $this->getRequestQuerySet($request, 'sort'),
236
            'filter' => (array) $request->input('filter'),
237
        ];
238
    }
239
240
    /**
241
     * Return any comma separated values in a request query field as an array.
242
     *
243
     * @param Request $request
244
     * @param string  $key
245
     *
246
     * @return array
247
     */
248
    protected function getRequestQuerySet($request, $key)
249
    {
250
        return preg_split('/,/', $request->input($key), null, PREG_SPLIT_NO_EMPTY);
251
    }
252
253
    /**
254
     * Transform a set of models into a JSON API collection.
255
     *
256
     * @param \Illuminate\Support\Collection $records
257
     * @param array                          $fields
258
     *
259
     * @return array
260
     */
261
    protected function transformCollection($records, array $fields = [])
262
    {
263
        $data = [];
264
265
        foreach ($records as $record) {
266
            $data[] = $this->transformRecord($record, $fields)['data'];
267
        }
268
269
        return compact('data');
270
    }
271
272
    /**
273
     * Transform a model instance into a JSON API object.
274
     *
275
     * @param Model      $record
276
     * @param array|null $fields  Field names of attributes to limit to
277
     * @param array|null $include Relations to include
278
     *
279
     * @return array
280
     */
281
    protected function transformRecord($record, array $fields = [], array $include = [])
282
    {
283
        $relations = array_unique(array_merge($record->getRelations(), $include));
284
        $attributes = $record->load($relations)->toArray();
285
        $relationships = [];
286
        $included = [];
287
288
        foreach ($relations as $relation) {
289
            $relationships[$relation] = [
290
                'data' => []
291
            ];
292
293
            foreach (array_pull($attributes, $relation) as $relatedRecord) {
294
                $relationships[$relation]['data'][] = [
295
                    'type' => $relation,
296
                    'id' => $relatedRecord['id'],
297
                ];
298
299
                if (in_array($relation, $include)) {
300
                    $included[] = [
301
                        'type' => $relation,
302
                        'id' => $relatedRecord['id'],
303
                        'attributes' => array_except($relatedRecord, ['id', 'pivot']),
304
                    ];
305
                }
306
            }
307
        }
308
309
        if (!empty($fields)) {
310
            $attributes = array_only($attributes, $fields);
311
        }
312
313
        $data = array_filter([
314
            'type' => $record->getTable(),
315
            'id' => $record->id,
316
            'attributes' => array_except($attributes, ['id']),
317
            'relationships' => $relationships,
318
        ]);
319
320
        return array_filter(compact('data', 'included'));
321
    }
322
}
323