Passed
Push — relationships ( 963a3b...90a2bf )
by Alex
02:27
created

JsonApiController::updateToManyRelationship()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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